仿照Dubbo手写RPC框架文章目录仿照Dubbo手写RPC框架前言一、RPC是什么?二、完整RPC框架的结构三、RPC框架的核心功能RPC核心三大步骤四、为什么要引
仿照Dubbo手写RPC框架
文章目录 仿照Dubbo手写RPC框架 前言 一、RPC是什么? 二、完整RPC框架的结构 三、RPC框架的核心功能 四、为什么要引入RPC 五、手写RPC框架的流程和模块说明 1、使用的一些技术点 2、一些模块的说明 3、整体流程图 总结
前言 Dubbo是一款开源的RPC框架,本文通过手写RPC框架的方式来加深对Dubbo的理解
一、RPC是什么? RPC(Remote Procedure Call)远程过程调用,简单的理解是一个节点请求另一个节点提供的服务。它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的思想。
RPC 是一种技术思想而非一种规范或协议,常见 RPC 技术和框架有:
应用级的服务框架:阿里的 Dubbo/Dubbox、Google gRPC、Spring Boot/Spring Cloud。 远程通信协议:RMI、Socket、SOAP(HTTP XML)、REST(HTTP JSON)。 通信框架:MINA 和 Netty。
二、完整RPC框架的结构 在一个典型 RPC 的使用场景中,包含了服务发现、负载、容错、网络传输、序列化等组件,其中“RPC 协议”就指明了程序如何进行网络传输和序列化。
三、RPC框架的核心功能 RPC 的核心功能是指实现一个 RPC 最重要的功能模块,就是上图中的”RPC 协议”部分。 RPC的协议有很多,比如最早的CORBA,Java RMI,Web Service的RPC风格,Hessian,Thrift,甚至Rest API。网络传输方式有 HTTP、TCP、Websocket 等
一个 RPC 的核心功能主要有 5 个部分组成,分别是:客户端、客户端 Stub、网络传输模块、服务端 Stub、服务端等。 下面分别介绍核心 RPC 框架的重要组成:
客户端(Client):服务调用方。 客户端存根(Client Stub):存放服务端地址信息,将客户端的请求参数数据信息打包成网络消息,再通过网络传输发送给服务端。 服务端存根(Server Stub):接收客户端发送过来的请求消息并进行解包,然后再调用本地服务进行处理。 服务端(Server):服务的真正提供者。 Network Service:底层传输,可以是 TCP 或 HTTP。
RPC核心三大步骤 1、 寻址:客户端是如何知晓服务端具体(ip+port)地址的,比如输入域名会通过DNS服务查询到对应的ip 2、通讯方式:选择tcp/udp,以及具体的上层协议;比如http就是一种基于tcp之上的协议 3、数据序列化:客户端和服务端交互时,对数据使用的序列化方式,比如json,xml等
四、为什么要引入RPC 关于这个问题,首先我们来看看下面的场景: 假如我们的电商系统中需要一个发送短信的服务,调用的是A平台的接口,如果我们把短息服务和电商系统的其他模块都放在一个jar包中,那么当我们需要切换到B平台发送短信时,就需要修改代码,然后再重新部署。如果系统中还有其他类似的服务,那么对于代码管理和运维来说是个很繁重的工作,且这样的系统耦合度会很高。 使用RPC技术带来的好处: 1、使用RPC技术可以使系统降低耦合性,系统拆分成多模块后,各模块之间使用RPC通信,各模块可以独立部署,从而达到解耦的目的 2、RPC底层网络是长链接,不必每次通信都要像http一样去3次握手什么的,减少了网络开销 3、RPC框架一般都有注册中心,有丰富的监控管理、发布、下线接口、动态扩展等,对调用方来说是无感知、统一化的操作。 4、RPC协议的底层传输和序列化都可以自定义,在网络传输过程中是比较安全的。
五、手写RPC框架的流程和模块说明 1、使用的一些技术点 1、本文的RPC框架是用redis做注册中心,基于spring提供远程服务,使用spring注解完成远程服务的注入和调用 2、使用了spring中的bean的后置处理器,来做bean的扩展 3、使用了spi机制,加载配置类 4、使用了netty,完成底层的网络传输 5、使用redis的订阅发布机制,完成服务的注册发现,以及服务的上线和下线通知 6、客户端和服务端都采用了代理机制,来完成服务的调用 7、TrpcBootstrap类是服务调用者和服务提供者的统一入口,TrpcProtocol类是实现rpc协议的统一入口
2、一些模块的说明 类:OrderApplication 1.@EnableTRPC 2.context.star() 3.多线程调用OrderService 1.通过@EnableTRPC开启rpc功能 2.开启spring服务 3.子线程获取OrderServiceImpl对象,发起远程调用
注解:EnableTRPC @Import() 1.TRPCPostProcessor.class 2.TRPCConfiguration.class 1.通过@import注解,当服务启动时调用TRPCPostProcessor.class TRPCConfiguration.class,完成一些类的注册 类:TRPCConfiguration 1.BeanDefinitionBuilder 2.environment.getProperty 3.BeanDefinitionRegistry 1.获取配置相关类的构建器 2.将配置文件中的属性,注入到配置类对应的字段中 3.完成配置类对象bean的注册
类:TRPCPostProcessor 1.ServiceConfig 2.ReferenceConfig 3.TrpcBootstrap 1.服务提供者,通过TRpcService注解找到服务的实现类,构建serviceConfig配置,最后由TrpcBootstrap.export()暴露服务 2.服务调用者,通过TRpcReference注解找到执行远程调用的对象变量,通过TrpcBootstrap.getReferenceBean(referenceConfig)获取服务代理对象,并注入到变量中
类:TrpcBootstrap 1.export() 2.getReferenceBean() 1.服务提供者 1.1 ProxyFactory.getInvoker()获取服务实现类的代理对象 1.2 serviceConfig.getProtocolConfigs()获取具体协议,构建协议的URI 1.3 protocol.export(exportUri, invoker)暴露协议 1.4 RegistryService通过spi加载,然后注册协议到redis
2.服务调用者 2.1 new ClusterInvoker()获取服务提供者实例集群 2.2 ProxyFactory.getProxy(clusterInvoker, new Class[]{referenceConfig.getService()})获取实例集群的代理 2.3代理调用invoke方法时,会采用负载均衡策略选择实例聚群中的一个实例进行远程调用
类:ClusterInvoker 1.构造函数 2.invoke() 2.1 loadbalance 1.在构造函数中获取ReferenceConfig中的注册中心uri 2.通过RegistryService和uri,初始化注册中心,并订阅相关服务,最后通过notify回调,更新本地服务列表 3.当notiy回调通知有新的服务uri时,会通过protocol.refer(uri)创建一个新的invoker服务代理,并添加到本地服务列表中
类:ProxyFactory 1 getProxy() 1.1 new InvokerInvocationHandler(invoker) 2.getInvoker() 2.1 new Invoker() 1.服务提供者 1.1 getInvoker(Object proxy, Class type), 返回new Invoker()匿名代理类 2.1 参数proxy是具体提供服务的实现类,匿名代理类中的invoke(RpcInvocation rpcInvocation)方法就会通过反射调用服务实现类的方法
2.服务调用者 2.1 getProxy(Invoker invoker), 方法返回InvokerInvocationHandler代理对象 2.2 该代理对象执行本地方法,有需要远程调用的方法时,就构建RpcInvocation对象 2.3 最后InvokerInvocationHandler(invoker)对象会调用真正服务实现类的invoker来执行服务,既这里多了一层代理
类:TrpcProtocol 1.export() 2.refer() 2.1 transporter.connect() 2.2 TrpcClientInvoker 1.服务提供者 1.1 getInvoker(Object proxy, Class type),获取序列化对象,编解码器对象,服务端Handler对象 1.2 通过spi加载网络协议的实现类transporter,通过transporter.export()导出服务
2.服务调用者 2.1 refer(URI consumerUri),获取序列化对象,编解码器对象,客户端Handler对象 2.2 通过spi加载网络协议的实现类transporter,通过transporter.connect()获取客户端连接 2.3 new TrpcClientInvoker(connect, serialization)返回客户端连接代理,既ClusterInvoker中的服务代理
类:TrpcClientInvoker 1.implements Invoker 2.invoke(RpcInvocation rpcInvocation) 2.1 client.getChannel().send(requestBody) 2.2 future=TrpcClientHandler.waitResult() 2.3 return (Response) future.get() 1.实现了Invoker接口 2.invoke(RpcInvocation rpcInvocation) 2.1方法中通过client中的trpcChannel来完成远程服务调用,既把相关数据发送到netty服务端 2.2 通过TrpcClientHandler异步获取服务端返回的响应,并通过id唯一标识 2.3 将获取到的响应返回
类:RedisRegistry 1.构造函数 2.void register(URI uri) 3.void subscribe(String service, NotifyListener notifyListener) 1.init() 1.1 通过心跳机制,定时刷新redis中服务uri的过期时间 1.2 开启子线程,new JedisPubSub()定义收到订阅消息时的回调处理方式,并通过NotifyListener完成回调通知 1.3 通过__keyspace@0__:trpc-*"和psubscribe进行模式订阅 2.register(URI uri),在redis中set 服务的uri 3. subscribe(),通过 keys()命令,模糊匹配的方式获取到想要调用的远程服务的uri列表
类:Netty4Transporter 1.Client connect() 2.Server start() 1.通过netty服务端程序,开启网络服务 2.通过netty客户端程序,建立长连接
接口:LoadBalance() 1.Invoker select(Map invokerMap) 1.两个实现类RandomLoadBalance和RoundRobin 2.通过select方法,采用对应的策略返回ClusterInvoker中的一个代理对象
类:TrpcClientHandler 1.CompletableFuture waitResult() 2.onReceive() 1.登记,创建返回一个future, 每个发起远程调用的线程有一个单独的future 2.接收远程服务执行结果,删除future 类:TrpcServerHandler 1.onReceive() 2.getInvoker() 1.获取远程服务实现类SmsServiceImpl的代理,并执行远程方法 2.获取远程方法执行结果,并包装成response返回给客户端
类:RpcInvocation 1.将netty发送数据的请求体进行包装 2.long incrementAndGet() 通过自旋方式获取自增id
类:RoundRobin 1.Invoker select() 2.int incrementAndGet()
类:SmsApplication 1.@EnableTRPC 2.context.star()
类:SmsServiceImpl 1.@TRpcService 2.Object send()服务实现类
类:OrderServiceImpl 1.@TRpcReference(loadbalance = “RoundRobin”) 2.smsService.send()远程调用
接口:SmsService 1.Object send(String phone, String content)
接口:OrderService 1.void create(String orderContent);
接口:Invoker 1.invoke(RpcInvocation rpcInvocation)
接口:Handler 1.onReceive() 2.onWrite()
3、整体流程图
总结 这个手写RPC框架,花了我两个星期的时间,真是不容易呀!欢迎各位技术大牛前来指点江山,交流评论。你的支持就是对我最大的鼓励!谢谢!