手写简单的RPC框架(一)

一、RPC简介

1、什么是RPC

RPC(Remote Procedure Call)远程过程调用协议,一种通过网络从远程计算机上请求服务,而不需要了解底层网络技术的协议。RPC它假定某些协议的存在,例如TPC/UDP等,为通信程序之间携带信息数据。在OSI网络七层模型中,RPC跨越了传输层和应用层,RPC使得开发,包括网络分布式多程序在内的应用程序更加容易。
过程是什么? 过程就是业务处理、计算任务,更直白的说,就是程序,就是想调用本地方法一样调用远程的过程。

2、为什么会出现RPC

RPC的概念与技术早在1981年由Nelson提出。1984年,Birrell和Nelson把其用于支持异构型分布式系统间的通讯。Birrell的RPC 模型引入存根进程( stub) 作为远程的本地代理,调用RPC运行时库来传输网络中的调用。Stub和RPC runtime屏蔽了网络调用所涉及的许多细节,特别是,参数的编码/译码及网络通讯是由stub和RPC runtime完成的,因此这一模式被各类RPC所采用。由于分布式系统的异构性及分布式计算模式与计算任务的多样性,RPC作为网络通讯与委托计算的实现机制,在方法、协议、语义、实现上不断发展,种类繁多,其中SUN公司和开放软件基金会在其分布式产品中所建立和实用的RPC较为典型。
在SUN公司的网络文件系统NFS及开放网络计算环境ONC中,RPC是基本实现技术。OSF酝酿和发展的另一个重要的分布式计算软件环境DCE也是基于RPC的。在这两个系统中,RPC既是其自身的实现机制,又是提供给用户设计分布式应用程序的高级工具。由于对分布式计算的广泛需求,ONC和DCE成为Client/Server模式分布式计算环境的主流产品,而RPC也成为实现分布式计算的事实标准之一。
摘抄自:百度文库https://baike.baidu.com/item/%E8%BF%9C%E7%A8%8B%E8%BF%87%E7%A8%8B%E8%B0%83%E7%94%A8/7854346?fromtitle=RPC&fromid=609861&fr=aladdin

同时微服务的出现也进一步促进了RPC的发展,我们知道在微服务当道的今天。众多个微服务之间需要合作才能完成业务,例如“订单服务”需要调用“用户服务”的某个接口,这个场景就非常适合RPC(当然了用Http请求也是可以的)

二、RPC需要解决的问题

1、 通信协议?

所谓的协议,可以认为是一种约定,即服务端和客户端定义好数据是如何解析的。由于在网络传输的过程都是比特流(01010101),所以双方需要约定好如何解读这些数据。

2、服务提供方和调用方如何进行通信

通常RPC框架,双方的通信是基于TCP协议的。

3、调用方如何知道服务提供方

方法有很多,
1、比如最简单的服务的提供方将服务信息写入数据库,调用每次去查询。当然这个方案显然是不可能的,抛开性能问题不谈,绝大多数场景这两者并不能使用同一个数据库。
2、利用一些中间件,比如ZK就非常适合。在ZK中存储服务端暴露的信息,同时客户端可以通过添加监听器来感知服务端信息的变化。

4、如何高效的序列化和反序列化

这里引用一下其他文章:https://zhuanlan.zhihu.com/p/367295821

三、手写一个简单的PRC框架

在这里插入图片描述

上图是一个简单的RPC调用架构图,当然了实际上会更复杂。结合小结说的RPC要解决的问题,我们罗列一些一个PRC框架所需要的技术。
1、我们希望服务端(生产者)的信息不要写死,客户端(消费者)可以从某个地方动态的获取到服务端的消息,同时服务端如果宕机了,客户端可以感知到,从而不再去调用宕机的服务端接口。当然可以实现这种功能的技术有很多,不过首先想到的就是Zookeeper。所以我们第一个技术选型将Zookeeper作为注册中心。
2、既然PRC是远程调用,那么肯定离不开网络。比如我们可以用Http去实现我们的远程调用,不过相对来说性能会差一些。所以考虑到性能方面,我们可以自己写一个网络模块。提到网络通信,我们很自然的想到了Netty,Netty作为一个使用简单的NIO的高性能框架,可以快速编写服务端程序。
3、我们知道网络通信的过程中,我们的数据都是二进制,0101010的形式,但是在咱们业务上都是以具体的实体类来使用。所以我们需要有一些列的编解码器,根据一定的协议(规范)来解析网络的数据流,当我们根据协议拿到了数据流后,在业务上我们是不能直接使用的所以需要进行返序列化。提到返序列化,第一个想到的就是JDK自带的序列化,JDK自带的序列化有一定的局限性:1、效率相对较低;2、不支持跨平台。所以不使用JDK自带的序列化工具,这里我们使用protobuf 这个框架来实现序列化和反序列化。
4、由于当下多是Spring或者Springboot的项目,所以我们也使用Sping作为项目容器。

小结:

至此手写一个简单的RPC框架所需要的技术点已经够了,话不多说让我们开始coding吧。

四、搭建项目框架

1、项目分层

在PRC中有服务端(生产者)和客户端(消费者)这两种角色,所以基于这个考虑,我们把项目进行拆分。总共分为3个模块:

  • Server模块:主要负责将暴露的接口信息上报到注册中心中供消费者调用。
  • Core模块:PRC核心功能、包括网络IO、编解码器、缓存等等一些列功能。
  • Client模块:负责生成代理,调用实际接口,并处理响应等。

整体结构如下
image.png

2、POM文件

TIPS:一开始依赖并非完整的,随着项目的开发逐步完善。
1、父工程

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.cmxy</groupId>
  <artifactId>yrpc</artifactId>
  <version>1.0-SNAPSHOT</version>
  <modules>
    <module>yrpc-client</module>
    <module>yrpc-core</module>
    <module>yrpc-server</module>
  </modules>
  <packaging>pom</packaging>


  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
    <spring.version>5.1.4.RELEASE</spring.version>
    <cglib.version>3.1</cglib.version>
    <netty.version>4.1.42.Final</netty.version>
    <zkclient.version>0.1</zkclient.version>
    <objenesis.version>2.6</objenesis.version>
    <protostuff.version>1.6.0</protostuff.version>
    <slf4j.log4j.version>1.7.25</slf4j.log4j.version>
    <guava.version>19.0</guava.version>
    <reflections.version>0.9.10</reflections.version>
    <beanutils.version>1.9.3</beanutils.version>
    <commons.lang3.version>3.6</commons.lang3.version>
    <commons.collections.version>3.2.2</commons.collections.version>
  </properties>

  <dependencyManagement>
    <dependencies>
      <!-- zookeeper客户端组件依赖 -->
      <dependency>
        <groupId>com.github.sgroschupf</groupId>
        <artifactId>zkclient</artifactId>
        <version>${zkclient.version}</version>
      </dependency>
      <!-- Netty 组件依赖 -->
      <dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty-all</artifactId>
        <version>${netty.version}</version>
      </dependency>
      <!-- 实例化组件依赖 -->
      <dependency>
        <groupId>org.objenesis</groupId>
        <artifactId>objenesis</artifactId>
        <version>${objenesis.version}</version>
      </dependency>
      <!-- protostuff 核心依赖 -->
      <!--基于google protobuf的工具类 protostuff-->
      <dependency>
        <groupId>io.protostuff</groupId>
        <artifactId>protostuff-core</artifactId>
        <version>${protostuff.version}</version>
      </dependency>
      <dependency>
        <groupId>io.protostuff</groupId>
        <artifactId>protostuff-runtime</artifactId>
        <version>${protostuff.version}</version>
      </dependency>
      <!-- spring 上下文组件依赖 -->
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
      </dependency>
      <!-- 日志组件依赖 -->
      <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>${slf4j.log4j.version}</version>
      </dependency>
      <!-- Google Guava 核心扩展库-->
      <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>${guava.version}</version>
      </dependency>
      <!-- Apache 集合 扩展依赖 -->
      <dependency>
        <groupId>commons-collections</groupId>
        <artifactId>commons-collections</artifactId>
        <version>${commons.collections.version}</version>
      </dependency>
      <!-- Apache lang 包扩展依赖 -->
      <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>${commons.lang3.version}</version>
      </dependency>
      <!-- Apache BeanUtils 辅助工具依赖 -->
      <dependency>
        <groupId>commons-beanutils</groupId>
        <artifactId>commons-beanutils</artifactId>
        <version>${beanutils.version}</version>
      </dependency>
      <!-- cglib动态代理依赖-->
      <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>${cglib.version}</version>
      </dependency>
      <!-- Java元数据分析反射依赖-->
      <dependency>
        <groupId>org.reflections</groupId>
        <artifactId>reflections</artifactId>
        <version>${reflections.version}</version>
      </dependency>
    </dependencies>
  </dependencyManagement>


  <dependencies>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.16.22</version>
      <optional>true</optional>
    </dependency>
  </dependencies>

  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.3</version>
          <configuration>
            <source>${java.version}</source>
            <target>${java.version}</target>
            <encoding>UTF-8</encoding>
          </configuration>
        </plugin>
      </plugins>
    </pluginManagement>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.3</version>
        <configuration>
          <source>${java.version}</source>
          <target>${java.version}</target>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

2、Core模块

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <parent>
    <artifactId>yrpc</artifactId>
    <groupId>com.cmxy</groupId>
    <version>1.0-SNAPSHOT</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>

  <artifactId>yrpc-core</artifactId>

  <properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <!-- spring 上下文组件依赖 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
    </dependency>
    <!-- Netty 通讯依赖-->
    <dependency>
      <groupId>io.netty</groupId>
      <artifactId>netty-all</artifactId>
    </dependency>
    <!-- zookeeper客户端依赖 -->
    <dependency>
      <groupId>com.github.sgroschupf</groupId>
      <artifactId>zkclient</artifactId>
    </dependency>
    <!--基于google protobuf的工具类 protostuff-->
    <dependency>
      <groupId>io.protostuff</groupId>
      <artifactId>protostuff-core</artifactId>
    </dependency>
    <dependency>
      <groupId>io.protostuff</groupId>
      <artifactId>protostuff-runtime</artifactId>
    </dependency>
    <!-- Apache 集合 扩展依赖 -->
    <dependency>
      <groupId>commons-collections</groupId>
      <artifactId>commons-collections</artifactId>
    </dependency>
    <!-- Apache lang 包扩展依赖 -->
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
    </dependency>
    <!-- Apache BeanUtils 辅助工具依赖 -->
    <dependency>
      <groupId>commons-beanutils</groupId>
      <artifactId>commons-beanutils</artifactId>
    </dependency>
    <!-- Google Guava 核心扩展库-->
    <dependency>
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
    </dependency>
    <!-- 日志组件依赖 -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
    </dependency>
  </dependencies>

</project>

3、Server和Client模块只需要引入Core模块即可(目前是这样)

3、开发Server模块

首先我们要明确各个模块的作用,然后从由简到难的开发。相比之下Server端会比较简单。理由如下:
服务端只需暴露接口,处理接受请求处理响应基本上就可以了。但是作为客户端来说,需要处理的就比较多了,维护服务端提暴露的服务列表(本地缓存)、负载均衡(简单的来说就是服务发现)、生成代理类、失败重试等等一系列,所以我们先开发服务端。
在开发之前我们需要罗列出服务端要做的事情:

  1. 扫描需要暴露的接口
  2. 将暴露的接口保存到注册中心
  3. 处理网络连接,收到请求然后处理响应。

简单的来说RPC 服务端最基本的功能就是这几个,接下来我们逐一实现。

通常情况下我们会自定义一个注解,有该注解的接口我们认为是需要提供给外部使用的。所以我们在Core模块中定义一个最简单的注解,名字就叫YRpcService

package com.cmxy.rpc.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Component;

/**
 * @Author hardy(叶阳华)
 * @Description
 * @Date 2023/5/24 10:40
 */

@Component
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface YRpcService {

    /**
     * 等同于@Component的value
     * @return
     */
    @AliasFor(annotation = Component.class)
    String value() default "";

    /**
     * 服务接口Class
     * @return
     */
    Class<?> interfaceClass() default void.class;

    /**
     * 服务接口名称
     * @return
     */
    String interfaceName() default "";

    /**
     * 服务版本号
     * @return
     */
    String version() default "";

    /**
     * 服务分组
     * @return
     */
    String group() default "";

}

由于当下基本上都是Spring环境,所以我们也利用Spring的特性。将该注解也认定是Spring的一个Component。接下来我们编写一个服务端的初始化类(Server模块下)

在这里插入图片描述

package com.cmxy.rpc.server.registry.zk;

import com.cmxy.rpc.annotation.YRpcService;
import com.cmxy.rpc.server.config.zk.RpcServerConfiguration;
import com.cmxy.rpc.server.registry.Registry;
import com.cmxy.rpc.util.IpUtil;
import com.cmxy.rpc.util.SpringApplicationUtil;
import java.util.Map;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;

/**
 * @Author hardy(叶阳华)
 * @Description
 * @Date 2023/5/24 10:50
 */
@Slf4j
public class ZkRegister implements Registry {

    @Resource
    private ServerZKit serverZKit;
    @Resource
    private RpcServerConfiguration rpcServerConfiguration;
    @Resource
    private SpringApplicationUtil springApplicationUtil;


    /**
     * 基于ZK实现的服务注册: 1、扫描所有需要暴露的接口:即携带了YRpcService的接口 2、将接口信息注册到ZK中
     */
    @Override
    public void register() {

        //1、扫描出携带了YRpcService注解的类
        final Map<String, Object> serviceMap = SpringApplicationUtil.getBeanListByAnnotationClass(
            YRpcService.class);
        if (serviceMap.isEmpty()) {
            log.info("暂无需要暴露的接口,结束注册");
            return;
        }
        //创建根目录
        serverZKit.createRootNode();
        //2、将接口信息写入ZK:path:ServiceBean的名称 data:IP+端口号
        //注意这里创建的是临时节点:以确保当前节点不可用的时候ZK上自动删除当前的节点信息
        serviceMap.forEach((beanName, serviceBean) -> {
            //获取当前Bean上的注解,通过注解
            final YRpcService yRpcService = serviceBean.getClass().getAnnotation(YRpcService.class);
            //获取接口
            final Class<?> serviceClass = yRpcService.interfaceClass();
            //创建服务层节点:例如 com.example.service.impl.testImpl
            String serviceName = serviceClass.getName();
            serverZKit.createPersistentNode(serviceName);
            //获取服务器IP
            String ip = IpUtil.getRealIp();
            //获取端口号:注意这里是RPC端端口号,不是服务端的端口号(因为通信是RPC框架)
            Integer port = rpcServerConfiguration.getRpcPort();
            String path = serviceClass.getName();
            //创建临时节点
            serverZKit.createEphemeralNode(serviceName + "/" + ip + ":" + port);
            log.info("服务:{} 注册成功,ip:{} 端口:{}", serviceName, ip, port);
        });
    }
}

代码解释:上述代码是为了将暴露的接口保存到注册中心,步骤如下

  1. 首先在Spring容器中查询出含有YPrcService注解的的类
  2. 创建根节点(根据配置)
  3. 拿到Service后,根据注解上配置的“接口属性”在ZK中创建节点
  4. 拿到当前的IP和端口号,在点不创建的节点下 创建临时节点(为什么是临时节点,上面注释中有)
  5. 完成注册

4、工具类代码

1、ServerZKit

package com.cmxy.rpc.server.registry.zk;

import com.cmxy.rpc.server.config.zk.RpcServerConfiguration;
import org.I0Itec.zkclient.ZkClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * Zookeeper连接操作接口
 */
@Component
public class ServerZKit {

    @Autowired
    private ZkClient zkClient;

    @Autowired
    private RpcServerConfiguration rpcServerConfiguration;

    /***
     * 根节点创建
     */
    public void createRootNode() {
        boolean exists = zkClient.exists(rpcServerConfiguration.getZkRoot());
        if (!exists) {
            zkClient.createPersistent(rpcServerConfiguration.getZkRoot());
        }
    }

    /***
     * 创建其他节点
     * @param path
     */
    public void createPersistentNode(String path) {
        String pathName = rpcServerConfiguration.getZkRoot() + "/" + path;
        boolean exists = zkClient.exists(pathName);
        if (!exists) {
            zkClient.createPersistent(pathName);
        }
    }

    /***
     * 创建临时节点
     * @param path
     */
    public void createEphemeralNode(String path) {
        String pathName = rpcServerConfiguration.getZkRoot() + "/" + path;
        boolean exists = zkClient.exists(pathName);
        if (!exists) {
            zkClient.createEphemeral(pathName);
        }
    }
}

2、SpringFactory工具

package com.cmxy.rpc.util;

import java.lang.annotation.Annotation;
import java.util.Map;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * @Author hardy(叶阳华)
 * @Description
 * @Date 2023/5/24 10:42
 */
@Component
public class SpringApplicationUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException {
        SpringApplicationUtil.applicationContext = applicationContext;
    }

    public static Object getBean(String className) {
        return applicationContext.getBean(className);
    }

    public static <T> T getBean(Class<T> clazz) {
        return applicationContext.getBean(clazz);
    }

    /***
     * 获取有指定注解的对象
     * @param annotationClass
     * @return
     */
    public static Map<String, Object> getBeanListByAnnotationClass(Class<? extends Annotation> annotationClass) {
        return applicationContext.getBeansWithAnnotation(annotationClass);
    }


}

3、配置类

package com.cmxy.rpc.server.config.zk;

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Data
@Component
public class RpcServerConfiguration {

    /**
     * ZK根节点名称
     */
    @Value("${rpc.server.zk.root}")
    private String zkRoot;

    /**
     * ZK地址信息
     */
    @Value("${rpc.server.zk.addr}")
    private String zkAddr;


    /**
     * RPC通讯端口
     */
    @Value("${rpc.network.port}")
    private int rpcPort;

    /**
     * Spring Boot 服务端口
     */
    @Value("${server.port}")
    private int serverPort;

    /**
     * ZK连接超时时间配置
     */
    @Value("${rpc.server.zk.timeout:10000}")
    private int connectTimeout;
}

五、小结

本文我们讲述了什么是RPC,以及RPC所解决的问题、需要的技术点。最后我们准备做一个简单的RPC框架,开发了服务端的一部分内容。接下里的我们不断完善这个框架。希望对你有所帮助,未完待续。。。。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/23098.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PMP考试应该要如何备考?如何短期通过PMP?

我从新考纲考完下来&#xff0c;3A通过了考试&#xff0c;最开始也被折磨过一段时间&#xff0c;但是后面还是找到了方法&#xff0c;也算有点经验&#xff0c;给大家分享一下吧。 程序猿应该是考PMP里面人最多的&#xff0c;毕竟有一个30大坎&#xff0c;大部分人还是考虑转型…

什么是网络编程

目录 一、什么是网络编程&#xff1f; 二、协议 1.用户数据报协议(User Datagram Protocol) 2.TCP协议 TCP三次握手过程 三、实例 1.UDP通信程序 实现步骤 TCP接收数据 四、TCP协议和UDP协议的区别和联系 一、什么是网络编程&#xff1f; 1.在网络通信协议下&#xf…

一图看懂!RK3568与RK3399怎么选?

▎简介 RK3568和RK3399都是Rockchip公司的处理器&#xff0c;具有不同的特点和适用场景。以下是它们的主要区别和应用场景。 ▎RK3568 RK3568是新一代的高性能处理器&#xff0c;采用了22nm工艺&#xff0c;具有更高的性能和更低的功耗。它支持4K视频解码和编码&#xff0c;支持…

电脑如何查找重复文件?轻松揪出它!

电脑如何查找重复文件&#xff1f;小编每天要接触各种文档、图片等资料&#xff0c;很多时候下载了一些图片后&#xff0c;我根本记不住&#xff0c;下次看到不错的图片&#xff0c;我又会下载下来&#xff0c;结果就是和之前下载的图片是一样的内容。下载的重复文件多了&#…

人员定位及轨迹管理技术原理及应用领域

人员定位及轨迹管理的实现涉及多种技术和设备。例如&#xff0c;在GPS定位方面&#xff0c;使用卫星系统可以提供全球范围内的准确定位信息。然而&#xff0c;GPS在室内环境下的信号覆盖可能存在限制&#xff0c;因此在室内定位应用中&#xff0c;常常采用无线传感器网络&#…

第一行代码 第十一章 基于位置的服务

第11章 基于位置的服务 在本章中&#xff0c;我们将要学习一些全新的Android技术&#xff0c;这些技术有别于传统的PC或Web领域的应用技术&#xff0c;是只有在移动设备上才能实现的。 基于位置的服务&#xff08;Location Based Service&#xff09;。由于移动设备相比于电脑…

案例分享 | 纽扣电池石墨片厚度及缺陷检测

石墨片是一种导热散热材料&#xff0c;质轻柔软&#xff0c;能够轻松贴合在各种热源点&#xff0c;在新能源、航天、3C电子等领域应用广泛。 汽车钥匙中的纽扣电池也需要使用石墨片&#xff0c;石墨片会有统一的厚度标准&#xff0c;装配过程中表面不可避免地会出现裂纹、划痕…

Maven学习笔记(上)22版

1. 概述部分 1. 什么是 Maven&#xff1f; 为什么要学习Maven&#xff1f; 管理规模庞大的 jar 包&#xff0c;需要专门工具。脱离 IDE 环境执行构建操作&#xff0c;需要专门工具。 1、构建 Java 项目开发过程中&#xff0c;构建指的是使用『原材料生产产品』的过程。 原…

双目测距联合YOLOv8 项目总结

代码贴&#xff1a;双目测距--5 双目相机 联合 YOLOv8_爱钓鱼的歪猴的博客-CSDN博客 0、图片筛选 可以用matlab,对双目图像做个一个筛选&#xff0c;也就是做双目标定。 熟悉matlab的小伙伴完全可以用matlab做双目标定&#xff0c;我是没咋接触过不知道怎么导出标定结果&#…

如何使用ArcGIS制作气温空间分布图

本文使用ArcMap10.2&#xff0c;以湖北省为例&#xff0c;通过空间插值&#xff0c;制作湖北省1981-2010年20年平均气温空间分布图 树谷资料库资源大全 1 数据准备 可在中国气象数据网下载湖北省1981-2010共20年的各区站累年平均气温数据和各区站经纬度数据。打开为txt格式 在…

字节原来这么容易进,是面试官放水,还是公司实在是太缺人?

本人211非科班&#xff0c;之前在字节和腾讯实习过&#xff0c;这次其实没抱着什么特别大的希望投递&#xff0c;没想到字节可以再给我一次机会&#xff0c;还是挺开心的。 本来以为有个机会就不错啦&#xff01;没想到能成功上岸&#xff0c;在这里要特别感谢帮我内推的同学&…

用友助力中核集团建设财务共享中心新华发电分中心,实现业财融合

企业在进行决策时需要大量的财务信息作为依据&#xff0c;财务共享中心的建设可以帮助企业将财务和业务分离后重新有序融合&#xff0c;使得决策数据更有价值&#xff0c;也帮助企业的管理和决策更加贴合实际。 新华水力发电有限公司&#xff08;简称“新华发电”&#xff09;…

单片机GD32F303RCT6 (Macos环境)开发 (二十八)—— 蓝牙透传模块HC-08 Android App开发

蓝牙透传模块HC-08 Android App开发 1、App整体开发思路 a、首先要申请权限&#xff0c;采用动态申请的方式&#xff0c;用户点击确认后方可操作蓝牙。 b、搜索蓝牙&#xff0c;之前的版本用startLeScan函数搜索蓝牙&#xff0c;虽然高版本中依然可用&#xff0c;但是google已…

众多行业适用的这款Lighthouse Apex Z便携粒子计数器有什么优势

Lighthouse Apex Z粒子计数器围绕易用性和可靠性进行构建。是建立在Lighthouse洁净室行业 40 多年的基于问题的学习基础上的解决方案。 采样设置 ApexZ易于使用的样品设置&#xff0c;可以匹配当前的sop&#xff0c;减少丢失位置或采样错误参数的风险。 用户管理 为了提高效…

霍尔电流传感器的注意事项及其在直流列头柜中的应用

安科瑞虞佳豪 霍尔电流传感器​注意事项 &#xff08;1&#xff09;电流传感器必须根据被测电流的额定有效值适当选用不同的规格的产品。被测电流长时间超额&#xff0c;会损坏末极功放管&#xff08;指磁补偿式&#xff09;&#xff0c;一般情况下&#xff0c;2倍的过载电流…

word怎么转excel?一键转换并不难

在职场中&#xff0c;常常需要将 Word 文档转换为 Excel 表格&#xff0c;以便更好地管理和数据分析。本文将介绍两种 Word 转 Excel 的方法&#xff0c;以及如何在 Excel 中处理转换后的数据。 方法一&#xff1a;使用文本转换向导 使用Word的文本转换向导&#xff0c;将Word文…

【数据库复习】第六章 关系数据理论 2

若R∈BCNF 所有非主属性对每一个码都是完全函数依赖 所有的主属性对每一个不包含它的码&#xff0c;也是完全函数依赖 没有任何属性完全函数依赖于非码的任何一组属性 多值依赖 Teaching具有唯一候选码(C&#xff0c;T&#xff0c;B)&#xff0c; 即全码&#xff0c; ∈3NF …

AJ-Report是一个完全开源,拖拽编辑的可视化设计工具

简介 AJ-Report是全开源的一个BI平台&#xff0c;酷炫大屏展示&#xff0c;能随时随地掌控业务动态&#xff0c;让每个决策都有数据支撑。     多数据源支持&#xff0c;内置mysql、elasticsearch、kudu驱动&#xff0c;支持自定义数据集省去数据接口开发&#xff0c;目前已支…

《Spring Guides系列学习》guide41 - guide45

要想全面快速学习Spring的内容&#xff0c;最好的方法肯定是先去Spring官网去查阅文档&#xff0c;在Spring官网中找到了适合新手了解的官网Guides&#xff0c;一共68篇&#xff0c;打算全部过一遍&#xff0c;能尽量全面的了解Spring框架的每个特性和功能。 接着上篇看过的gu…

Vue(路由插件)

一、介绍路由 1. 路由就是一组key-value的对关系&#xff0c;多个路由需要经过路由器进行管理 2. 主要应用在SPA&#xff08;单页面应用&#xff09; 在一个页面展示功能之间的跳转 特点&#xff1a; 当跳转时候不进行页面刷新路径随着变化展示区变化但是不开启新的页签 …