概念RPC是什么?RPC全称为远程过程调用(RemoteProcedureCall),用于解决分布式系统中服务之间的调用问题。通俗地说,开发者可以像调用本地方法一样调用远程服务。所以RPC的作用主要体现在这两个方面:屏蔽了远程调用和本地调用的区别,让我们感觉像在项目中调用方法;隐藏底层网络通信的复杂性,让我们更专注于业务逻辑。RPC框架的基本架构下面用一张图来说明RPC框架的基本架构。RPC框架包括三个最重要的组件,即客户端、服务器和注册中心。在一个RPC调用过程中,这三个组件的交互方式如下:服务器启动后,将其提供的服务列表发布到注册中心,客户端从注册中心订阅服务地址;客户端会使用本地的代理模块Proxy调用服务器,Proxy模块接收数据负责将方法、参数等转换成网络字节流;客户端从服务列表中选择一个服务地址,通过网络向服务器发送数据;服务器接收到数据,进行解码,得到请求信息;服务器根据解码后的请求信息调用相应的服务,然后将调用结果返回给客户端。RPC框架通信过程及涉及的角色从上图可以看出RPC框架一般有这些组件:服务治理(注册发现)、负载均衡、容错、序列化/反序列化、编解码、网络传输、线程池、动态代理等角色,当然有些RPC框架还有连接池、日志、安全等角色。具体调用流程服务消费者(client)在本地调用服务。客户端存根负责将方法、参数等封装成一个消息体,在收到调用后可以通过网络进行传输。客户端存根对消息进行编码并将其发送到服务器。服务器存根接收到消息后,服务器存根根据解码结果调用本地服务。本地服务执行并将结果返回给服务器存根。服务器存根对返回的导入结果进行编码,并将其发送给消费者。客户端存根接收消息并对其进行解码。服务消费者(客户端)获取结果RPC消息协??议RPC调用需要将参数编组为消息发送,接收方需要将消息作为参数解组,进程处理结果也需要编组和解组。消息由哪些部分组成以及消息的表示构成了消息协议。RPC调用过程中使用的消息协议称为RPC消息协议。从上面的概念,我们知道了一个RPC框架大概有哪些组件,所以在设计RPC框架的时候,我们也需要考虑这些组件。从RPC的定义我们可以知道,RPC框架需要屏蔽底层细节,让用户感觉调用远程服务就像调用本地方法一样简单,所以需要考虑这些问题:如何配置尽可能少可能当用户使用我们的RPC框架时如何注册服务去ZK(这里注册中心选择ZK)并且让用户无意识如何调用透明(尽可能用户察觉不到)调用服务提供者启用多个服务提供者如何实现动态负载均衡框架如何让用户自定义扩展组件(如扩展自定义负载均衡策略)如何定义消息协议,以及编解码器……等等这些问题都将在这个RPC框架的设计中得到解决。技术选型注册中心目前比较成熟的注册中心有Zookeeper、Nacos、Consul、Eureka。这里使用ZK作为注册中心,没有切换和自定义注册中心的功能。IO通信框架的实现使用Netty作为底层通信框架,因为Netty是一个高性能的事件驱动的非阻塞IO(NIO)框架,不提供其他实现,不支持用户自定义通信框架消息协议。自定义消息协议,项目整体结构后面会详细说明。从这个结构可以知道rpc命名的模块是rpc框架的模块,也是本项目RPC框架的内容,consumer就是服务消费者,provider就是服务提供者.,provider-api是暴露的服务API。整体依赖项目实现介绍为了保证用户使用我们的RPC框架尽可能少的配置,所以rpc框架设计成一个starter,用户只需要依赖这个starter,基本就可以了。为什么要设计成两个启动器(client-starter/server-starter)?这是为了更好的体现client和server的概念。消费者依赖客户端,服务提供者依赖服务器,尽量减少依赖。为什么要把它设计成启动器?基于springboot自动装配机制,会加载starter中的spring.factories文件,在文件中配置如下代码。到这里,我们的starter配置类就会生效,在配置类中配置一些需要的bean。org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.rrtv.rpc.client.config.RpcClientAutoConfiguration发布服务和消费服务对于发布服务提供者需要在暴露的服务上添加注解@RpcService,这个自定义注解是基于@service是复合注解,具有@service注解的作用。在@RpcService注解中,指定服务接口和服务版本,将服务发布到ZK,根据这两个元数据注册和发布服务原理:服务提供者启动后,根据springboot自动组装机制,服务端-starter配置类生效,在bean后处理器(RpcServerProvider)中获取@RpcService注解的bean,将注解的元数据注册到ZK。对于消费服务,消费服务需要使用自定义的@RpcAutowired注解标识,它是基于@Autowired的复合注解。消费者服务原理要让客户端在不知不觉中调用服务提供者,就需要使用动态代理。如上图,HelloWordService没有实现类,所以需要为其分配一个代理类,并在代理类中发起请求调用。基于springboot自动组装,服务消费者启动,bean后处理器RpcClientProcessor开始工作。主要是遍历所有的bean,判断每个bean中的属性是否被@RpcAutowired注解修改,如果是,则动态将该属性赋值给代理类,再次调用时会调用代理类的invoke方法。代理类的invoke方法通过服务发现获取服务端的元数据,封装请求,通过netty发起调用。注册中心本项目的注册中心使用的是ZK,因为服务消费者和服务提供者都使用注册中心。所以把ZK放在rpc-core模块中。rpc-core模块如上图所示,核心功能都在这个模块中。服务在注册包下注册。服务注册接口使用ZK实现。负载均衡策略负载均衡定义在rpc-core中,目前支持轮询(FullRoundBalance)和随机(RandomBalance),默认使用随机策略。由rpc-client-spring-boot-starter指定。通过ZK服务发现时,会发现多个实例,然后通过负载均衡策略获取其中一个实例。可以在consumer中配置rpc.client.balance=fullRoundBalance来代替,也可以通过实现接口LoadBalance来自定义负载均衡策略,并设置创建的类可以添加到IOC容器中。由于我们配置了@ConditionalOnMissingBean,所以用户定义的bean将首先被加载。自定义消息协议,codec所谓协议,就是通信的双方事先协商好规则,服务器知道如何解析发送的数据。自定义消息协议Magicnumber:magicnumber是通信双方协商的密码,通常用固定的字节数表示。幻数的作用是防止任何人随意向服务器端口发送数据。例如,幻数0xCAFEBABE存放在javaClass文件的开头。当加载Class文件时,首先会验证magicnumber的正确性。协议版本号:随着业务需求的变化,协议可能需要修改结构或字段。相应的分析方法也不同。Serializationalgorithm:serializationalgorithm字段表示数据发送方应该使用哪种方法将请求的对象转换成二进制,如何将二进制转换成对象,如JSON、Hessian、Java自带的序列化等。包类型:在不同的业务场景中,可能会有不同类型的数据包。RPC帧中有请求、响应、心跳等类型的消息。状态:状态字段用于标识请求是否正常(SUCCESS、FAIL)。MessageID:请求的唯一ID,通过它关联response,也可以通过requestID进行链接跟踪。数据长度:表示数据的长度,用于判断是否是一个完整的数据包。数据内容:请求体内容的编解码在rpc-core模块中实现,在com.rrtv.rpc.core.codec包下。自定义编码器通过继承netty的MessageToByteEncoder
