什么是RPC

RPC (Remote Procedure Call Protocol), 远程过程调用,通俗的解释就是:客户端在不知道调用细节的情况下,调用存在于远程计算机上的某个对象,就像调用本地应用程序中的对象一样,不需要了解底层网络技术的协议。

简单的整体工作流程

请求端发送一个调用的数据包,该包中包含有调用标识,参数等协议要求的参数。当响应端接收到这个数据包,对应的程序被调起,然后返回结果数据包,返回的数据包含了和请求的数据包中同样的请求标识,结果等。

性能影响因素

  1. 利用的网络协议。可以使用应用层协议,例如HTTP或者HTTP/2协议;也可以利用传输层协议,例如TCP协议,但是主流的RPC还没有采用UDP传输协议。
  2. 消息封装格式。选择或设计一种协议来封装信息进行组装发送。比如,dubbo中消息体数据包含dubbo版本号、接口名称、接口版本、方法名称、参数类型列表、参数、附加信息等。
  3. 序列化。信息在网络传输中要以二进制格式进行传输。序列化和反序列化,是对象到而二进制数据的转换。常见的序列化方法有JSON、Hessian、Protostuff等。
  4. 网络IO模型。可以采用非阻塞式同步IO,也可以在服务器上实现对多路IO模型的支持。
  5. 线程管理方式。在高并发请求下,可以使用单个线程运行服务的具体实现,但是会出现请求阻塞等待现象。也可以为每一个RPC具体服务的实现开启一个独立的线程运行,最大线程数有限制,可以使用线程池来管理多个线程的分配和调度。

第一版RPC

第一个版本简单实现了RPC的最基本功能,即服务信息的发送与接收序列化方式动态代理等。

项目利用Springboot来实现依赖注入与参数配置,使用netty实现NIO方式的数据传输,使用Hessian来实现对象序列化。

动态代理

这里要提到代理模式,它的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系。

根据创建代理类的时间点,又可以分为静态代理和动态代理。

在以往的静态代理中需要手动为每一个目标编写对应的代理类。如果系统已经有了成百上千个类,工作量太大了。

静态代理由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口与被代理类,代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。

代理类在程序运行时创建的代理方式被称为代理模式。在静态代理中,代理类是自己定义好的,在运行之前就已经编译完成了。而在动态代理中,可以很方便地对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。可以通过InvocationHandler接口来实现。

客户端的动态代理

  1. public class ProxyFactory {
  2. public static <T> T create(Class<T> interfaceClass) throws Exception {
  3. return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class<?>[]{interfaceClass}, new LwRpcClientDynamicProxy<T>(interfaceClass));
  4. }
  5. }
  1. @Slf4j
  2. public class LwRpcClientDynamicProxy<T> implements InvocationHandler {
  3. private Class<T> clazz;
  4. public LwRpcClientDynamicProxy(Class<T> clazz) throws Exception {
  5. this.clazz = clazz;
  6. }
  7. @Override
  8. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  9. LwRequest lwRequest = new LwRequest();
  10. String requestId = UUID.randomUUID().toString();
  11. String className = method.getDeclaringClass().getName();
  12. String methodName = method.getName();
  13. Class<?>[] parameterTypes = method.getParameterTypes();
  14. lwRequest.setRequestId(requestId);
  15. lwRequest.setClassName(className);
  16. lwRequest.setMethodName(methodName);
  17. lwRequest.setParameterTypes(parameterTypes);
  18. lwRequest.setParameters(args);
  19. NettyClient nettyClient = new NettyClient("127.0.0.1", 8888);
  20. log.info("开始连接服务器端:{}", new Date());
  21. LwResponse send = nettyClient.send(lwRequest);
  22. log.info("请求后返回的结果:{}", send.getResult());
  23. return send.getResult();
  24. }
  25. }

在服务端会利用在客户端获取到的类名。参数等信息利用反射机制进行调用。

  1. Class<?>[] parameterTypes = request.getParameterTypes();
  2. Object[] paramethers = request.getParameters();
  3. // 使用CGLIB 反射
  4. FastClass fastClass = FastClass.create(serviceClass);
  5. FastMethod fastMethod = fastClass.getMethod(methodName, parameterTypes);
  6. return fastMethod.invoke(serviceBean, paramethers);

Netty客户端

  1. @Slf4j
  2. public class NettyClient {
  3. private String host;
  4. private Integer port;
  5. private LwResponse response;
  6. private EventLoopGroup group;
  7. private ChannelFuture future = null;
  8. private Object obj = new Object();
  9. private NettyClientHandler nettyClientHandler;
  10. public NettyClient(String host, Integer port) {
  11. this.host = host;
  12. this.port = port;
  13. }
  14. public LwResponse send(LwRequest request) throws Exception{
  15. nettyClientHandler = new NettyClientHandler(request);
  16. group = new NioEventLoopGroup();
  17. Bootstrap bootstrap = new Bootstrap();
  18. bootstrap.group(group)
  19. .channel(NioSocketChannel.class)
  20. .option(ChannelOption.SO_KEEPALIVE, true)
  21. .option(ChannelOption.TCP_NODELAY, true)
  22. .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
  23. .handler(new ChannelInitializer<SocketChannel>() {
  24. @Override
  25. protected void initChannel(SocketChannel socketChannel) throws Exception {
  26. ChannelPipeline pipeline = socketChannel.pipeline();
  27. pipeline.addLast(new LengthFieldBasedFrameDecoder(65535, 0, 4));
  28. pipeline.addLast(new LwRpcEncoder(LwRequest.class, new HessianSerializer()));
  29. pipeline.addLast(new LwRpcDecoder(LwResponse.class, new HessianSerializer()));
  30. pipeline.addLast(nettyClientHandler);
  31. }
  32. });
  33. future = bootstrap.connect(host, port).sync();
  34. nettyClientHandler.getCountDownLatch().await();
  35. this.response = nettyClientHandler.getLwResponse();
  36. return this.response;
  37. }
  38. @PreDestroy
  39. public void close() {
  40. group.shutdownGracefully();
  41. future.channel().closeFuture().syncUninterruptibly();
  42. }
  43. }
  1. @Slf4j
  2. public class NettyClientHandler extends ChannelInboundHandlerAdapter {
  3. private final CountDownLatch countDownLatch = new CountDownLatch(1);
  4. private LwResponse response = null;
  5. private LwRequest request;
  6. public NettyClientHandler(LwRequest request) {
  7. this.request = request;
  8. }
  9. public CountDownLatch getCountDownLatch() {
  10. return countDownLatch;
  11. }
  12. public LwResponse getLwResponse() {
  13. return this.response;
  14. }
  15. @Override
  16. public void channelActive(ChannelHandlerContext ctx) {
  17. log.info("客户端向客户端发送消息");
  18. ctx.writeAndFlush(request);
  19. log.info("客户端请求成功");
  20. }
  21. @Override
  22. public void channelRead(ChannelHandlerContext ctx, Object msg)
  23. throws Exception {
  24. LwResponse lwResponse = (LwResponse) msg;
  25. log.info("收到服务端的信息:{}", lwResponse.getResult());
  26. this.response = lwResponse;
  27. this.countDownLatch.countDown();
  28. }
  29. @Override
  30. public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
  31. ctx.close();
  32. }
  33. @Override
  34. public void exceptionCaught(ChannelHandlerContext ctx,
  35. Throwable cause) {
  36. cause.printStackTrace();
  37. ctx.close();
  38. }
  39. }

在客户端发送服务信息时,用LwQuest类进行封装,返回的结果用LwResponse进行封装,当客户端读取到服务器端返回的响应时,在NettyClientHandler中进行处理,并利用CountDownLatch进行线程的阻塞和运行。

Netty服务端

  1. @Component
  2. @Slf4j
  3. public class NettyServer {
  4. private EventLoopGroup boss = null;
  5. private EventLoopGroup worker = null;
  6. @Autowired
  7. private ServerHandler serverHandler;
  8. @Value("${server.address}")
  9. private String address;
  10. public void start() throws Exception {
  11. log.info("成功");
  12. boss = new NioEventLoopGroup();
  13. worker = new NioEventLoopGroup();
  14. try {
  15. ServerBootstrap serverBootstrap = new ServerBootstrap();
  16. serverBootstrap.group(boss, worker)
  17. .channel(NioServerSocketChannel.class)
  18. .option(ChannelOption.SO_BACKLOG, 1024)
  19. .childOption(ChannelOption.SO_KEEPALIVE, true)
  20. .childHandler(new ChannelInitializer<SocketChannel>() {
  21. @Override
  22. protected void initChannel(SocketChannel socketChannel) throws Exception {
  23. ChannelPipeline pipeline = socketChannel.pipeline();
  24. pipeline.addLast(new LengthFieldBasedFrameDecoder(65535, 0, 4));
  25. pipeline.addLast(new LwRpcEncoder(LwResponse.class, new HessianSerializer()));
  26. pipeline.addLast(new LwRpcDecoder(LwRequest.class, new HessianSerializer()));
  27. pipeline.addLast(serverHandler);
  28. }
  29. });
  30. String[] strs = address.split(":");
  31. String addr = strs[0];
  32. int port = Integer.valueOf(strs[1]);
  33. ChannelFuture future = serverBootstrap.bind(addr, port).sync();
  34. future.channel().closeFuture().sync();
  35. } finally {
  36. worker.shutdownGracefully();
  37. boss.shutdownGracefully();
  38. }
  39. }
  40. @PreDestroy
  41. public void destory() throws InterruptedException {
  42. boss.shutdownGracefully().sync();
  43. worker.shutdownGracefully().sync();
  44. log.info("关闭netty");
  45. }
  46. }
  1. @Component
  2. @Slf4j
  3. @ChannelHandler.Sharable
  4. public class ServerHandler extends SimpleChannelInboundHandler<LwRequest> implements ApplicationContextAware {
  5. private ApplicationContext applicationContext;
  6. @Override
  7. public void setApplicationContext(ApplicationContext applicationContext) {
  8. this.applicationContext = applicationContext;
  9. }
  10. @Override
  11. protected void channelRead0(ChannelHandlerContext channelHandlerContext, LwRequest msg) throws Exception {
  12. LwResponse lwResponse = new LwResponse();
  13. lwResponse.setRequestId(msg.getRequestId());
  14. log.info("从客户端接收到请求信息:{}", msg);
  15. try {
  16. Object result = handler(msg);
  17. lwResponse.setResult(result);
  18. } catch (Throwable throwable) {
  19. lwResponse.setCause(throwable);
  20. throwable.printStackTrace();
  21. }
  22. channelHandlerContext.writeAndFlush(lwResponse);
  23. }
  24. private Object handler(LwRequest request) throws ClassNotFoundException, InvocationTargetException {
  25. Class<?> clazz = Class.forName(request.getClassName());
  26. Object serviceBean = applicationContext.getBean(clazz);
  27. Class<?> serviceClass = serviceBean.getClass();
  28. String methodName = request.getMethodName();
  29. log.info("获取到的服务类:{}", serviceBean);
  30. Class<?>[] parameterTypes = request.getParameterTypes();
  31. Object[] paramethers = request.getParameters();
  32. // 使用CGLIB 反射
  33. FastClass fastClass = FastClass.create(serviceClass);
  34. FastMethod fastMethod = fastClass.getMethod(methodName, parameterTypes);
  35. return fastMethod.invoke(serviceBean, paramethers);
  36. }
  37. }

在Netty服务端中,会利用``serverHandler`来处理从客户端中接收的信息,并利用反射的思想调用本地的方法,并将处理的结构封装在LwResponse中。

LwRequestLwRespnse要想在网络中进行传输,需要转化为二进制转换。具体方法如下:

  1. public class HessianSerializer implements Serializer {
  2. @Override
  3. public byte[] serialize(Object object) throws IOException {
  4. ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
  5. Hessian2Output output = new Hessian2Output(byteArrayOutputStream);
  6. output.writeObject(object);
  7. output.flush();
  8. return byteArrayOutputStream.toByteArray();
  9. }
  10. public <T> T deserialize(Class<T> clazz, byte[] bytes) throws IOException {
  11. Hessian2Input input = new Hessian2Input(new ByteArrayInputStream(bytes));
  12. return (T) input.readObject(clazz);
  13. }
  14. }
  1. public class LwRpcDecoder extends ByteToMessageDecoder {
  2. private Class<?> clazz;
  3. private Serializer serializer;
  4. public LwRpcDecoder(Class<?> clazz, Serializer serializer) {
  5. this.clazz = clazz;
  6. this.serializer = serializer;
  7. }
  8. @Override
  9. protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
  10. if (byteBuf.readableBytes() < 4)
  11. return;
  12. byteBuf.markReaderIndex();
  13. int dataLength = byteBuf.readInt();
  14. if (dataLength < 0) {
  15. channelHandlerContext.close();
  16. }
  17. if (byteBuf.readableBytes() < dataLength) {
  18. byteBuf.resetReaderIndex();
  19. }
  20. byte[] data = new byte[dataLength];
  21. byteBuf.readBytes(data);
  22. Object obj = serializer.deserialize(clazz, data);
  23. list.add(obj);
  24. }
  25. }
  1. public class LwRpcEncoder extends MessageToByteEncoder<Object> {
  2. private Class<?> clazz;
  3. private Serializer serializer;
  4. public LwRpcEncoder(Class<?> clazz, Serializer serializer) {
  5. this.clazz = clazz;
  6. this.serializer = serializer;
  7. }
  8. @Override
  9. protected void encode(ChannelHandlerContext ctx, Object in, ByteBuf out) throws Exception {
  10. if (clazz.isInstance(in)) {
  11. byte[] data = serializer.serialize(in);
  12. out.writeInt(data.length);
  13. out.writeBytes(data);
  14. }
  15. }
  16. }

轻量级RPC设计与实现第一版的更多相关文章

  1. 轻量级RPC设计与实现第五版(最终版)

    在最近一段时间里,通过搜集有关资料加上自己的理解,设计了一款轻量级RPC,起了一个名字lightWeightRPC.它拥有一个RPC常见的基本功能.主要功能和特点如下: 利用Spring实现依赖注入与 ...

  2. 轻量级RPC设计与实现第三版

    在前两个版本中,每次发起请求一次就新建一个netty的channel连接,如果在高并发情况下就会造成资源的浪费,这时实现异步请求就十分重要,当有多个请求线程时,需要设计一个线程池来进行管理.除此之外, ...

  3. 轻量级RPC设计与实现第四版

    在本版本中引入了SPI机制,关于Java的SPI机制与Dubbo的SPI机制在以前的文章中介绍过. 传送门:Dubbo的SPI机制与JDK机制的不同及原理分析 因为设计的RPC框架是基于Spring的 ...

  4. 轻量级RPC设计与实现第二版

    在上一个版本中利用netty实现了简单的一对一的RPC,需要手动设置服务地址,限制性较大. 在本文中,利用zookeeper作为服务注册中心,在服务端启动时将本地的服务信息注册到zookeeper中, ...

  5. 微博轻量级RPC框架Motan

    Motan 是微博技术团队研发的基于 Java 的轻量级 RPC 框架,已在微博内部大规模应用多年,每天稳定支撑微博上亿次的内部调用.Motan 基于微博的高并发和高负载场景优化,成为一套简单.易用. ...

  6. C# 的轻量级 RPC 框架

    Redola.Rpc 的一个小目标 Redola.Rpc 的一个小目标 Redola.Rpc 的一个小目标:20000 tps. Concurrency level: 8 threads Comple ...

  7. 微博轻量级RPC框架Motan正式开源:支撑千亿调用

    支撑微博千亿调用的轻量级 RPC 框架 Motan 正式开源了,项目地址为https://github.com/weibocom/motan. 微博轻量级RPC框架Motan正式开源 Motan 是微 ...

  8. 轻量级RPC框架开发

    nio和传统io之间工作机制的差别 自定义rpc框架的设计思路 rpc框架的代码运行流程 第2天 轻量级RPC框架开发 今天内容安排: 1.掌握RPC原理 2.掌握nio操作 3.掌握netty简单的 ...

  9. 拥抱.NET Core,跨平台的轻量级RPC:Rabbit.Rpc

    不久前发布了一篇博文".NET轻量级RPC框架:Rabbit.Rpc",当初只实现了非常简单的功能,也罗列了之后的计划,经过几天的不断努力又为Rabbit.Rpc增加了一大波新特性 ...

随机推荐

  1. Expect & Shell: 网络设备配置备份

    1. 环境介绍及效果展示 A. centos 6.6 x64 B. tftp-server 0.49 C. 脚本目录 D. 备份目录 E. 备份邮件 2. tftp服务配置 A. [root@step ...

  2. 康拓展开 & 逆康拓展开 知识总结(树状数组优化)

    康拓展开 : 康拓展开,难道他是要飞翔吗?哈哈,当然不是了,康拓具体是哪位大叔,我也不清楚,重要的是 我们需要用到它后面的展开,提到展开,与数学相关的,肯定是一个式子或者一个数进行分解,即 展开. 到 ...

  3. 深入理解JVM(学习过程)

    这,仅是我学习过程中记录的笔记.确定了一个待研究的主题,对这个主题进行全方面的剖析.笔记是用来方便我回顾与学习的,欢迎大家与我进行交流沟通,共同成长.不止是技术. 2020年02月06日22:43:0 ...

  4. ELK logstash 各种报错

    1.logstash 启动后数据传输了,但是 ElasticSearch 中没有生成索引,查看logstash日志,报错如下 [2018-06-08T14:46:25,387][WARN ] [log ...

  5. 使用matplotlib画图

    一.介绍 官方文档:https://www.matplotlib.org.cn/home.html 安装:pip install matplotlib Matplotlib是一个Python 2D绘图 ...

  6. 5.7.20 多实例——MGR部署实战

    数据库 | MySQL:5.7.20 多实例——MGR部署实战 MGR介绍 基于传统异步复制和半同步复制的缺陷——数据的一致性问题无法保证,MySQL官方在5.7.17版本正式推出组复制(MySQL ...

  7. dict的使用

    Python字典是可变类型数据,可以存储任意对象,如字符串,数字,元组,列表等. 字典的创键 字典有键key和值value组成,使用键值对链接:,字典也称为关联数组或哈希表. dict_person ...

  8. UI自动化技术在高德的实践

    一.背景汽车导航作为ToB业务,需要满足不同汽车厂商在功能和风格上体现各自特色的需求.针对这种情况,传统的UI开发方式,基本上是一对一的特别定制.但是这种方式动辄就要500~600人日的工作量投入,成 ...

  9. Android布局管理器-使用LinearLayout实现简单的登录窗口布局

    场景 Android布局管理器-从实例入手学习相对布局管理器的使用: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/1038389 ...

  10. 「Flink」Flink的状态管理与容错

    在Flink中的每个函数和运算符都是有状态的.在处理过程中可以用状态来存储数据,这样可以利用状态来构建复杂操作.为了让状态容错,Flink需要设置checkpoint状态.Flink程序是通过chec ...