RPC实现以及相关学习

Posted SpringForAll社区

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RPC实现以及相关学习相关的知识,希望对你有一定的参考价值。

及时获取有趣有料的技术文章

本文来源:http://u6.gg/sSVUY


「我们即希望能够敏捷开发,不做重复的劳动,用别人的势能赋能自己;又要成为一名能够赋能别人的人,拥有自身的势能。」

在一个拥有成千上万大大小小的服务的公司里,每个团队在不同的机器上部署它们自己的服务,所以真实开发一个新服务的场景一定需要考虑两个问题:

  1. 我的团队开发一个新服务,可能需要调用别人的服务。

  2. 我的团队开发一个新服务,别的团队可能会调用。

RPC调用的变与不变

由于服务部署在不同机器,想要进行服务间的调用必须进行网络通信,那服务消费方每调用一个服务都要写一大堆网络通信的东西,不仅复杂而且极易出错。

我们知道此时我们的技术选型时很丰富的,关于各种技术的优缺点网上很多文章,可以去编乎的相关问题去看看,我觉得概括的比较好一句话是「良好的RPC调用是面向服务的封装,针对的是服务的可用性和效率,减轻网络服务开发和调用的复杂性」。

但我们不管选择何种进程间通信手段,http,TCP通信或是消息中间件、RPC通信,调用本身很多东西是不可能变的:

  1. 角色的定义(发起调用的是客户端,接受调用的是服务端)

  2. 通信的机制(网络IO,序列化,传输协议,同步异步)

那真正变的是什么?这些都是你遇到的场景以及你的目标导致的,对于RPC来说,主要来说和其他相比比较大的变化在于下面两条吧。

  1. 调用的目标。服务透明化,目标是让用户像以本地调用方式调用远程服务。

  2. 调用的方式。服务端的服务要被调用,客户端在本地直接调用服务端提供的接口即可,而不需要调用真实的接口实现。于是服务端就是需要利用一些很多反射操作去完成。

RPC需要什么

想要实现一个基本的RPC框架,其实需要什么?

  1. 网络IO,BIONIOAIO,Socket编程,HTTP通信,一个就行。

  2. 序列化,JDK序列化,JSON、Hessian、Kryo、ProtoBuffer、ProtoStuff、Fst知道一个就行。

  3. 反射,JDK或者Cglib的动态代理。

那一个优秀的RPC框架,还需要考虑什么问题?

  1. 多个实例,选哪个调用好?负载均衡

  2. 服务注册中心每次都查?缓存相关

  3. 客户端每次要等服务器返回结果?异步调用

  4. 服务是要升级的?版本控制

  5. 多个服务依赖,某个有问题?熔断器

  6. 某个服务出了问题怎么办?监控 ...

Dubbo

其实要考虑的问题是非常多的,瞻仰一下Dubbo的流程图和Dubbo团队对未来的规划图:RPC实现以及相关学习

RPC实现以及相关学习

自己实现的一个简单RPC框架

整体调用流程

由于采用了etcd做服务注册中心,所以整体调用流程可以被概括为下面这样:

  1. Server端启动进行服务注册到etcd;

  2. Client端启动获取etcd的服务注册信息,定期更新;

  3. Client以本地调用方式调用服务(使用接口,例如helloService.sayHi("world"));

  4. Client通过RpcProxy会使用对应的服务名生成动态代理相关类,而动态代理类会将请求的对象中的方法、参数等组装成能够进行网络传输的消息体RpcRequest;

  5. Client通过一些的负载均衡方式确定向某台Server发送编码(RpcEncoder)过后的请求(netty实现)

  6. Server收到请求进行解码(RpcDecoder),通过反射(cglib的FastMethod实现)会进行本地的服务执行

  7. Server端writeAndFlush()将RpcResponse返回;

  8. Clinet将返回的结果会进行解码,得到最终结果。

Netty学习

Netty是一款异步的事件驱动的网络应用程序框架,支持快速地开发可维护的高性能的面向协议的服务器和客户端。

Netty重要的几个概念

  1. Channel:这并不是Netty专有的概念,Java NIO里也有。可以看作是入站或者出战数据的载体,有各种基本的read、write、connect、bind等方法,相当于传统IO的Socket,需要关注一下ServerChannel,ServerChannel负责创建子Channel,子Channel具体去执行一些具体accept之后的读写操作。项目中用的NiosocketChannel和NioServerSocketChannel。

  2. EventLoop和EventLoopGroup:Netty的核心抽象,channel的整个生命周期都是通过EventLoop去处理。EventLoop相当于对Thread的封装,一个EventLoop里面拥有一个永远都不会改变的Thread,同时任务的提交只需要通过EventLoop就可执行;而EventLoopGroup负责为每个Channel分配一个EventLoop/

  3. ChannelFuture:Netty所有的IO操作都是异步的原因。

  4. ChannelHandler和ChannelPipeline:开发人员主要关注的也可能是唯一需要关注的两个组件,用来管理数据流以及执行应用程序处理逻辑。

  5. ChannelInboundHandler和ChannelOutboundHandler:两个常见的ChannelHandler适配器,前者管理入站的数据和操作,后者管理出站的数据和操作,谨记:入站顺序执行,出站逆序执行。

  6. ChannelPipeline:一个拦截流经某个channel的入站和出站时间的ChannelHandle实例链,每一个Channel刚被创建就会被分配一个ChannelPipeline,永久不可更改。

  7. ChannelHandlerContext:ChannelHandle和ChannelPipeline中间管理的纽带,每一个ChannelHandler分配一个ChannelHandlerContext用来跟其他Handler作交互。

  8. ByteBuf:网络数据的基本单位是字节,Java NIO使用的ByteBuffer作为字节容器,而Netty使用ByteBuf替代ByteBuffer作为数据容器进行读写。

  9. BootStrap:将各种组件拼图进行组装,ServerBootstrap用来引导服务端,Bootstrap用来引导客户端。ServerBootstrap的Group一般会放入两个EventLoopGroup,需要结合Channel去理解,ServerChannel会有子Channel,那为了处理这个Channel,你需要为每一个子Channel分配一个EventLoop,第二个EventLoopGroup是为了让子Channel去共享一个EventLoop,避免额外的线程创建以及上下文切换。

  10. ByteToMessageDecoder和MessageToByteEncoder:编解码器的解码器和编码器,MessageToByteEncoder继承了ChannelOutboundHandlerAdapter接口,ByteToMessageDecoder继承了ChannelInboundHandlerAdapter接口。解码器是将字节解码为消息;编码器是将消息编码成字节。

Netty学习的其他问题

1.「序列化和编码都是把 Java 对象封装成二进制数据的过程,这两者有什么区别和联系?」序列化是把内容变成计算机可传输的资源,而编码则是让程序认识这份资源。

2.「与服务端启动相比,客户端启动的引导类少了哪些方法,为什么不需要这些方法?」服务端:需要两个线程组,NioServerSocketChannel线程模型,可以设置childHandle 客户端:一个线程组,NioSocketChannel线程模型,只可以设置handler

3.「ChannelPipeline执行顺序?」(1)InboundHandler顺序执行,OutboundHandler逆序执行 (2)InboundHandler之间传递数据,通过ctx.fireChannelRead(msg) (3)InboundHandler通过ctx.write(msg),则会传递到outboundHandler (4)  使用ctx.write(msg)传递消息,Inbound需要放在结尾,在Outbound之后,不然outboundhandler会不执行;但是使用channel.write(msg)、pipline.write(msg)情况会不一致,都会执行,那是因为channel和pipline会贯穿整个流。(5)  outBound和Inbound谁先执行,针对客户端和服务端而言,客户端是发起请求再接受数据,先outbound(写)再inbound(读),服务端则相反。

(6)outBound可以理解为数据“出航”,inBound可以理解为“归航”,所以请求从客户端到服务端就意味着,请求数据从客户端出航,在服务端归航,服务端响应请求是从服务端到客户端的,所以就是响应数据从服务端出航,在客户端归航。

4.「三种最常见的ChannelHandle的子类型?」a. 基于 ByteToMessageDecoder,我们可以实现自定义解码,而不用关心 ByteBuf 的强转和 解码结果的传递。b. 基于 SimpleChannelInboundHandler,这主要针对的最常见的一种情况,你去接收一种(泛型)解码信息,然后对数据应用业务逻辑然后继续传下去。我们可以实现每一种指令的处理,通过泛型不再需要强转,不再有冗长乏味的 if else 逻辑,不需要手动传递对象。c. 基于 MessageToByteEncoder,我们可以实现自定义编码,而不用关心 ByteBuf 的创建,不用每次向对端写 Java 对象都进行一次编码。

5.**Netty关于拆包粘包理论与解决方案?**本次使用的是LengthFieldBasedFrameDecoder。

a.固定长度的拆包器 FixedLengthFrameDecoder 如果你的应用层协议非常简单,每个数据包的长度都是固定的,比如 100,那么只需要把这个拆包器加到 pipeline 中,Netty 会把一个个长度为 100 的数据包 (ByteBuf) 传递到下一个 channelHandler。b.行拆包器 LineBasedFrameDecoder 从字面意思来看,发送端发送数据包的时候,每个数据包之间以换行符作为分隔,接收端通过 LineBasedFrameDecoder 将粘过的 ByteBuf 拆分成一个个完整的应用层数据包。c.分隔符拆包器 DelimiterBasedFrameDecoder DelimiterBasedFrameDecoder 是行拆包器的通用版本,只不过我们可以自定义分隔符。d.基于长度域拆包器 LengthFieldBasedFrameDecoder 最后一种拆包器是最通用的一种拆包器,只要你的自定义协议中包含长度域字段,均可以使用这个拆包器来实现应用层拆包。由于上面三种拆包器比较简单,读者可以自行写出 demo,接下来,我们就结合我们小册的自定义协议,来学习一下如何使用基于长度域的拆包器来拆解我们的数据包。

CGLib学习

反射和动态代理

反射机制是Java语言提供的一种基础功能,赋予程序在运行时 自省 (introspect,官方用语)的能力。通过反射我们可以直接操作类或者对象,比如获取某个对象的类定义,获取类声明的属性和方法,调用方法或者构造对象,甚至可以运行时修改类定义。

动态代理是一种方便运行时动态构建代理、动态处理代理方法调用的机制,很多场景都是利用类似机制做到的,比如用来包装 RPC 调用、面向切面的编程(AOP)。实现动态代理的方式很多,比如 JDK 自身提供的动态代理,就是主要利用了上面提到的反射机制。还有其他的实现方式,比如利用传说中更高性能的字节码操作机制,类似 ASM、cglib(基于 ASM)等。

总结:反射是java的一种能力,而动态代理是一种解决问题的方案。动态代理是一种代理模式。代理可以看作是对调用目标的一个包装,这样我们对目标代码的调用不是直接发生的,而是通过代理完成。通过代理可以让调用者与实现者之间解耦 。

CGLib实现反射

    FastClass fastClass = FastClass.create(serviceClass);
FastMethod fastMethod = fastClass.getMethod(methodName, parameterTypes);
return fastMethod.invoke(serviceBean, parameters);

CGLib实现动态代理

实现MethodInterceptor接口,然后使用Enhancer构建

    public static <T> T createByCglib(Class<T> clazz) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(new RpcMethodInterceptor(clazz));
return (T) enhancer.create();
}

JDK实现动态代理

实现InvocationHandler接口,然后使用Proxy创建

    public static <T> T create(Class<T> interfaceClass) {
return (T) Proxy.newProxyInstance(
interfaceClass.getClassLoader(),
new Class<?>[]{interfaceClass},
new RpcInvocationHandler<>(interfaceClass)
);
}

序列化实现

序列化有多种实现方式,不同序列化优缺点不同,网上有很多比较天梯图。我实现了五种,JSON,FST,HESSIAN2,PROTO_STUFF,KRYO。

http://u6.gg/sSVT2

http://u6.gg/sSVTj





如果资源对你有帮助的话

       
         
         
       
❤️ 给个 「在看」 ,是最大的支持❤️

以上是关于RPC实现以及相关学习的主要内容,如果未能解决你的问题,请参考以下文章

深入RPC 实现以及学习总结

IOS开发-OC学习-常用功能代码片段整理

区块链 | 智能合约Ethereum源代码- 以太坊RPC通信实例和原理代码分析(上)

实战RPC是什么?如何实现?

RPC服务 RPC相关概念详解,以及如何设计一个RPC框架

hadoop相关重要源码学习