什么是RPC调用
在动手之前,首先要明确rpc调用的概念,RPC(Remote Procedure Call)远程过程调用,简单的理解是一个节点请求另一个节点提供的服务,简单来说就是无感知的调用远程服务的方法
举个例子,如果在A服务中实现了接口addCount(),进行计数+1,而B服务想调用A服务的这个接口,本地只需要通过函数指针来调用即可,而在两个服务中的调用就可以成为rpc调用
实现RPC调用的流程
生产者
- 扫描所有的提供服务的Service并对应的注册到注册中心中,暴露服务出去,并且在本地根据服务名称缓存服务执行对象。
- 开启本地服务接受消费者请求。
- 在接收到请求后根据服务名称寻找对应的执行对象,并通过反射的方式调用对应的方法。
- 将执行结果返回给消费者
消费者
- 对含有相应注解的字段注入代理对象。实现类似@Autowired的功能
- 将调用的类名,方法,参数,封装成元数据,并从注册中心获取对应执行地址
- 向执行地址发送相应数据,并接受返回结果
总体来说一个完整的RPC调用流程基本上如上所述,接下来我们就来实现一个简易的RPC调用框架,当然是基于java的,在理论上rpc调用是不限语言的,比如gRPC或者Thrift都是可以跨语言执行的,而当当网二次开发的Dubbox也是可以跨语言的。
工具选型
通过上述流程可以得知,在这个rpc框架中我们需要选择一个注册中心以及网络通讯框架,并且要选择通讯协议,在这个框架中我选择zookeeper做为注册中心,netty为网络通讯框架,通讯协议选择Http(别问,问就是好写)序列化协议选择JSON(别问,好写)。
项目创建
整项目结构如上图,common项目是通用的内容也就是框架的主题内容,consumer是消费者项目,producer是生产者项目,这两个项目都是springboot项目并且依赖common项目。
通用部分
在common项目pom.xml中加入以下依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.14</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.42.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.curator/curator-framework -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.2.0</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.0.5</version>
</dependency>
在common下创建config包,并创建RpcConfig类
@Component
@ConfigurationProperties(prefix = "myrpc")
public class RpcConfig {
private String localAddress;
private Integer nettyPort;
private String basePackage;
public String getBasePackage() {
return basePackage;
}
public void setBasePackage(String basePackage) {
this.basePackage = basePackage;
}
public Integer getNettyPort() {
return nettyPort;
}
public void setNettyPort(Integer nettyPort) {
this.nettyPort = nettyPort;
}
public String getLocalAddress() {
return localAddress;
}
public void setLocalAddress(String localAddress) {
this.localAddress = localAddress;
}
}
该配置类配置本地请求地址以及netty启动对应端口
然后创建ZookeeperConfig类。
@Component
@ConfigurationProperties(prefix = "myrpc.zookeeper")
public class ZookeeperConfig {
private String url;
private Integer sessionTimeOut;
public Integer getSessionTimeOut() {
return sessionTimeOut;
}
public void setSessionTimeOut(Integer sessionTimeOut) {
this.sessionTimeOut = sessionTimeOut;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
该类用于获取zookeeper相关的配置,然后在创建zookeeperRegister类,该类为zookeeper操作相关工具类
@Component
public class ZookeeperRegister {
//根节点
public static final String BASE_NODE = "/myrpc/";
//缓存生产者数据
public static ConcurrentHashMap<String, List<String>> map = new ConcurrentHashMap<>();
@Autowired
private ZookeeperConfig zookeeperConfig;
private ZooKeeper zooKeeper = null;
//获取生产者信息
public List<String> getProducer(Class<?> injectedType) throws Exception {
List<String> producerList = map.get(injectedType.getTypeName());