怎么样手写RPC

RPC


实现一个RPC框架需要做哪些基本的事:

  • 服务消费者:客户端,通过注册中心找到要调用的方法进行调用,当然调用过程由框架实现,通过代理类来隐藏调用细节,让客户端调用远程方法跟调用本地方法一样。

  • 服务提供者:提供服务功能,当客户端发起调用时实际是服务端处理调用过程,客户端只传过来方法形参,

  • 注册中心:负责服务地址的注册与查找,当客户端需要调用远程方法时,注册中心要有服务发现的能力;服务端提供服务时(服务端一上线)要有服务注册的能力。

  • 容错机制:远程调用本质靠的就是网络,那么就要考虑网络出现故障时应该怎么办

  • 序列化机制:网络传输都是字节的形式,所有客户端与服务器需要约定好序列化形式,以免出错

  • **网络传输:**网络传输是rpc的关键,可以基于socket来传输,但socket属于阻塞的IO,性能低。也可以使用基于非阻塞的NIO,但通过NIO来实现rpc网络传输层会比较繁琐,因此可以使用基于NIO的Netty网络编程框架。

  • **传输协议:**是客户端和服务端之间通信的基础,比如HTTP协议,规定了收发双发需要遵守的要求。同样,rpc也需要规定收发双方需要遵守的协议,通过设计协议,我们可以定义需要传输哪些类型的数据,数据采用哪种序列化机制,以及发送的数据长度大小是多少。

    RPC执行流程
preview image-20200805001037799

如何实现基于netty手写rpc

  • 首先需要完成的是基础部分,有序列化模块,压缩模块,负载均衡模块以及注册中心和服务发现,服务提供,服务注册模块。
  • 其次就是核心部分,网络传输模块。rpc归根结底就是通过网络传输,来实现两台计算机间的交互,那么总的来说实现rpc,就是实现如何感知一个服务中哪些内容要暴露出来供其他服务使用,并且获取这些内容的ip地址和端口号,上传到注册中心,等有其他服务需要使用时,可以通过相同的注册中心将这些内容给拿出来使用。
  • rpc执行流程:首先服务启动,通过自定义bean扫描器,扫描哪些bean上有RpcService注解,说明这个bean要注册到注册中心去,供其他服务使用,这样会扫描出所有需要暴露的服务。随后会扫描RpcReference注解,然后为添加该注解的bean注入实际要调用的bean,这里注入的是实际调用的代理类
  • 接下来看调用和网络传输的细节,此前已经为添加RpcReference注解的属性注入依赖,实际调用时会走到代理类invoke方法里,在这个方法里将调用实际方法的参数包装为RpcRequest,然后通过RpcRequestTransport的具体实现类来将RpcRequest传输给真正的方法执行者,方法执行者执行完后将结果返回到invoke里,然后层层返回给调用者。那么问题就来到rpcrequesttransport拿到rpcrequest后怎么传输呢
  • 这样netty这个框架就登场了,使用netty编写服务端,这个服务端用来将扫描到需要注册服务的bean真正的给添加到注册中心中去,不仅如此,还要建立起两个服务之间传输的信道,在建立连接时顺便设置心跳响应,在服务端中真正进行网络传输的是NettyRpcServerHandler,该执行器继承了ChannelInboundHandlerAdapter,也就是服务端中所有的入站操作都会被该类拦截,入站读操作会执行ChannelRead方法,在这个方法中来判断客户端发送的请求是请求心跳响应还是请求调用方法,然后做响应的操作,如果是请求心跳响应,那么在此方法里就能把响应信息给凑齐,如果是请求调用方法,就把解析出来的rpcrequesthandler来执行真正的调用方法,在RpcRequestHandler中通过服务发现来获取可以处理该请求的服务(此刻先屏蔽掉服务发现的getService方法),结合RpcRequest中的参数来执行方法,将结果层层返回,然后服务端将结果包装为RpcResponse,返回给客户端。
  • rpc-framework中client对应的就是服务调用者,也就是需要远程服务调用的一方,server是服务提供者,就是服务提供的一方,两方都基于zookeeper注册中心,在rpc.properties中声明注册中心的地址
  • 下面来看客户端是怎样的,当客户端运行此方法时,会执行到该方法的代理类中,由于在bean实例化时已经为他注入实例,已经rpcRequestTransport,也就是处理信息的客户端,随后到sendRpcRequest方法中,通过服务发现功能,获取到实际方法执行者的ip地址和端口,然后与服务端建立连接,将信息封装为RpcMessage后传输给服务端。当客户端有入站信息时,和服务端一样的,客户端实际的入站操作是由NettyRpcClientHandler实现的,到站后判断是返回的结果信息还是心跳响应信息。
  • 在上述的过程中,可能发现了,网络编程不都是一个字节字节传输的么,万一出错了,咋办,还要服务端和客户端怎么知道对面传输过来的字节都是啥意思。这些问题就是序列化问题和自定义协议
  • 协议编码 :协议编码要完成的任务是当客户端或服务器发送过来信息后,如何把字节数组给解析为自己可以看懂的内容,双方通过固定的协议编码开始解析数据,然后将编写好的RpcMessageDecoder和RpcMessageEncoder作为handler添加到服务端和客户端的队列中,这样客户端或服务端有数据传输过来时,会先进行编解码,然后做下一步操作。编码协议里主要做的工作是将传输过来的RpcMessage进行封装,加上魔数,版本,消息长度,消息类型,序列化类型,请求Id以及要传输的数据,解码的主要的工作就是处理请求时,检验魔数这些是否在传输时发生错误,取出消息长度做进一步操作防止粘包半包等问题,然后通过消息类型判断当前的RpcMessage是RpcRequest还是RpcResponse,然后由序列化机制将结果封装为具体类型,那么刚才所说的编解码可以发现,这里只是对ByteBuf进行了操作,前面说了,网络传输都是基于字节的,这样还是不能传输,下面就来看看序列化的工作
  • 序列化和压缩:序列化操作分为序列化和反序列化,序列化就是将数据序列化为字节数组,以便在互联网上传输,反序列化就是将字节数组转换为相应的class类型,class一般作为参数传递进来,默认转化为Object。压缩:要处理的就是在传输时将序列化好的数据进行压缩,这样提高传输速度和效率。
  • 接下来就来讨论一下服务发现服务注册服务提供模块:下面来说一下上面所忽略的细节,首先因为这些操作都需要基于zookeeper,所以先编写一个zookeeper的工具类,来完成获取连接,监听节点,创建节点以及获取孩子节点等基本操作,有这个工具类后完成上述这些功能就非常简单了。服务发现就是通过RpcReference注解里的信息从注册中心找到相应的服务地址,以此能让客户端和服务端建立连接,服务发现通过RpcRequest提供的服务名从注册中心查找,会返回一个地址列表,经过负载均衡的处理返回真正执行方法的服务地址。服务注册是指向注册中心创建节点,根据传进来的参数(服务名和地址)在zookeeper上创建节点,并对节点进行监听。发布服务是具体调用服务注册的人,由他向服务注册提供参数,来完成服务发布
  • 最后说说负载均衡,在刚刚服务发现步骤中,提到过通过服务名可以返回一个地址列表,那么负载均衡的作用就是确定到底要返回那个服务地址,如果一直返回同一个,那这个服务迟早会刚不住,所以rpc-framework提供了随机负载均衡,一致性哈希负载均衡和轮询负载均衡。

rpc-framework可优化的点:

  • 类似dubbo-admin的管理界面
  • 在springboot项目中使用rpc-framework
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值