自己动手写RPC框架

版权声明:本文为博主原创文章,未经允许不得转载。 https://blog.csdn.net/qq_31142553/article/details/86316654

在上一篇博客中,介绍了RPC的主要概念和实现原理,然后基于TCP协议实现了一个非常简单的RPC小案例(点此回顾)。

现在,自我挑战一下,动手写一个RPC框架。

高能预警:本文涉及到的知识点如下

  • Spring Boot2:起步依赖、自动配置,让开发变得简单
  • Spring的Java Bean配置,条件注解:灵活
  • 基于JDK接口的动态代理了解一下?):发起远程调用对调用方来说是透明的
  • ZooKeeper了解一下?):注册中心
  • Netty了解一下?):高性能的网络通信
  • 自定义注解:哪些类提供了远程服务,又是哪些属性需要注入远程服务
  • Java内置的序列化了解一下?):网络中数据以二进制的形式进行传输

先看下效果

1、配置文件:开启服务注册、ZK地址和连接超时时间、暴露服务端口。

2、服务提供者:用RpcService注解说明该bean提供远程调用服务。

3、服务消费者:RpcReference注解为此属性注入远程调用服务的代理对象。

 

4、注册中心:服务提供者启动后,在ZK成功创建了相关节点。

5、调用服务:成功发起了一次远程过程调用。

嘀,开车啦!

嗤,源码下载地址:https://download.csdn.net/download/qq_31142553/10913525

一、原理&流程

敲黑板讲重点!!!

Dubbo一样,分为服务提供者、服务消费者、注册中心三个主要角色。后面将逐一进行讲解。

镇楼图

RPC架构流程图

二、代码结构

我们将项目分为两个模块,一个核心模块,一个测试模块。大概代码结构如下

 

主要maven依赖

        <!-- lombok,简化实体类结构 -->
        <dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<!-- <optional>true</optional> -->
		</dependency>

        <!-- netty,高性能服务通信 -->
		<dependency>
			<groupId>io.netty</groupId>
			<artifactId>netty-all</artifactId>
		</dependency>

		<!-- zk,注册中心 -->
		<dependency>
			<groupId>org.apache.zookeeper</groupId>
			<artifactId>zookeeper</artifactId>
			<version>3.4.12</version>
		</dependency>

二、角色详解

因为基于Java Bean的配置比较灵活,所以我们建立一个全局配置类,在这里配置需要交给Spring IOC容器管理的对象。但是,如果系统不打算使用RPC服务,那就没必要加载这个配置类了。因此,我们自定义一个注解,如果在Spring Boot启动类上使用了此注解,那么此全局配置类才生效。

1、服务提供者

概述

服务提供者系统启动时,如果启用了服务注册功能,就将提供服务的bean信息注册到ZooKeeper,并在内存维护一个interfaceName-bean的映射关系集合,最后在指定端口开启一个监听远程调用请求的服务。

详解

是否启用服务注册功能,可以从配置属性读取。采用条件注解的方式,如果启用了,就注册一个专门用于维护与ZooKeeper的连接会话的实例到IOC容器,在构造函数里面初始化它的ZK实例,并判断是否需要创建ZK的服务根节点。然后,还要注册一个RPC服务到IOC容器,这个服务在Spring容器加载完成之后,获取所有带了自定义的、表示提供远程服务的注解的bean,先将它们存到本地内存维护的一个Map集合里面,以接口全类名为key,bean实例为value,这样,远程过程调用的请求过来之后,就从这个map里面根据接口名找到对应的bean来处理了。然后,还要将这些bean实例信息放到ZK上面,以提供服务消费方去查询获取。至于存什么信息呢?首先是需要这些bean的接口全类名,然后是本机启动的监听请求服务的IP和端口。所以,在ZK服务根节点下,创建一个以接口全类名为名称的永久节点,然后创建一个临时序列子节点,以本机IP和监听端口、当前时间戳(处理一台服务器的一个接口有多个实现类的问题)命名。最后,还要启动一个Netty服务端,绑定本机的IP和端口,端口可以写到配置里面、与前文说的保持一致,添加业务处理的Handler,业务处理主要是根据请求的接口名、方法名、参数列表找到本地内存维护的map里面的服务实例,通过反射调用并返回执行结果。

2、服务消费者

概述

服务启动时,从注册中心订阅(先查,后续变化自动触发更新)自己需要的服务到本地列表。方法调用时,利用代理先去本地列表查询到服务提供所在IP和端口,然后发起Netty请求,将响应作为远程调用的执行结果返回。

详解

首先,服务消费者系统启动时,先注册一个与ZooKeeper的连接会话的实例到IOC容器。然后,查询所有bean中带了自定义的、表示需要远程服务的注解的属性,根据它们的类型名称到ZK上面查询同名节点,存在的话获取它的所有孩子节点名称,然后保存到一个自己定义的、类似Map<String, Set<String>>结构的集合里,同时还要为类名的节点注册子节点变化监听器,触发时重新读取其子节点更新到集合里。接着,利用反射为属性赋值一个代理对象,这个代理对象在方法被调用时,将方法调用信息封装成请求参数,根据自己的类型名称到集合里面找到符合的服务提供者所在的IP和端口后发起请求,并将响应结果返回。

三、代码说明

由于代码量有点大,我也不知道怎么贴。就将这些类是做什么用的简单说明一下,比较关键的部分附上截图。

推荐感兴趣的小伙伴直接去下载源码:https://download.csdn.net/download/qq_31142553/10913525

(1)annotation包

EnableRpcConfiguration:自定义注解,用于Spring Boot启动类上面。有此注解时,配置类RpcConfiguration才生效,系统才使用RPC功能。

RpcReference:自定义注解,用于Spring Bean的属性上,表明此字段需要注入远程调用的代理对象。

RpcService:自定义注解,用于Spring Bean的类上面,表明该实例提供远程调用服务。需要设置interfaces属性,指明提供哪些接口的服务。

(2)client包

NodeChildrenChangedWatcher:子节点变化观察者,用于服务消费者监听ZooKeeper指定节点的变化从而更新服务信息列表。

RpcClientHandler:用于服务消费者向服务提供者发送请求并接收响应。

RpcInjectHandler:客户端核心类,从ZooKeeper获取需要的服务信息,为带RpcReference注解的属性设置远程调用的代理对象。

(3) common包

RpcDecoder:Netty解码器,将字节数组转为Java对象。

RpcEncoder:Netty编码器,将Java对象转为字节数组。

RpcRequest:RPC请求封装类,有接口名称、方法名称、参数列表(类型和值)。

RpcResponse:RPC结果封装类,有响应结果、异常。

(4)conf包

Consts:常量,有zk服务根节点 、本机IP。

 RpcConfiguration:RPC配置类,配置所有注入IOC容器的bean。

(4)server包

RpcServer:将提供RPC服务的bean存到本地服务列表,并注册到ZooKeeper

RpcServerHandler:处理服务消费者的请求,反射调用本地方法,并将执行结果返回。

ServerListener:开启Netty服务端监听。

(5)util包

HashMultimap:类似Google Guava的同名类,提供Map<K, Set<V>>结构的集合。

IpUtils:获取本机IP地址。

RpcException:RPC异常类。

SerializationUtils:Java内置的序列化和反序列化功能。

(6)zk包

ZkClient:维护服务消费者与ZooKeeper的连接会话。

ZkServer:维护服务提供者与ZooKeeper的连接会话,并创建服务根节点(需要的话)。

后续可以考虑加入负载均衡、服务容错等功能! 

源码下载地址:https://download.csdn.net/download/qq_31142553/10913525

猜你喜欢

转载自blog.csdn.net/qq_31142553/article/details/86316654