一个入门rpc框架的学习
参考
- huangyong-rpc
- 轻量级分布式RPC框架
- 该程序是一个短连接的rpc实现
简介
RPC,即 Remote Procedure Call(远程过程调用),说得通俗一点就是:调用远程计算机上的服务,就像调用本地服务一样。
RPC 可基于 HTTP 或 TCP 协议,Web Service 就是基于 HTTP 协议的 RPC,
它具有良好的跨平台性,但其性能却不如基于 TCP 协议的 RPC。会两方面会直接影响 RPC 的性能,一是传输方式,二是序列化。
众所周知,TCP 是传输层协议,HTTP 是应用层协议,而传输层较应用层更加底层,
在数据传输方面,越底层越快,因此,在一般情况下,TCP 一定比 HTTP 快。
就序列化而言,Java 提供了默认的序列化方式,但在高并发的情况下,
这种方式将会带来一些性能上的瓶颈,于是市面上出现了一系列优秀的序列化框架,比如:Protobuf、Kryo、Hessian、Jackson 等,
它们可以取代 Java 默认的序列化,
从而提供更高效的性能。
为了支持高并发,传统的阻塞式 IO 显然不太合适,因此我们需要异步的 IO,即 NIO。
Java 提供了 NIO 的解决方案,Java 7 也提供了更优秀的 NIO.2 支持,用 Java 实现 NIO 并不是遥不可及的事情,只是需要我们熟悉 NIO 的技术细节。
我们需要将服务部署在分布式环境下的不同节点上,通过服务注册的方式,
让客户端来自动发现当前可用的服务,并调用这些服务。
这需要一种服务注册表(Service Registry)的组件,让它来注册分布式环境下所有的服务地址(包括:主机名与端口号)。
应用、服务、服务注册表之间的关系见下图:

每台 Server 上可发布多个 Service,这些 Service 共用一个 host 与 port,
在分布式环境下会提供 Server 共同对外提供 Service。此外,为防止 Service Registry 出现单点故障,因此需要将其搭建为集群环境。
本文将为您揭晓开发轻量级分布式 RPC 框架的具体过程,
该框架基于 TCP 协议,提供了 NIO 特性,提供高效的序列化方式,同时也具备服务注册与发现的能力。
根据以上技术需求,我们可使用如下技术选型:
Spring:它是最强大的依赖注入框架,也是业界的权威标准。
Netty:它使 NIO 编程更加容易,屏蔽了 Java 底层的 NIO 细节。
Protostuff:它基于 Protobuf 序列化框架,面向 POJO,无需编写 .proto 文件。
ZooKeeper:提供服务注册与发现功能,开发分布式系统的必备选择,同时它也具备天生的集群能力。
源代码目录结构
- rpc-client
- 实现了rpc的服务动态代理(RpcProxy)以及基于Netty封装的一个客户端网络层(RpcClient)
- rpc-common
- 封装了RpcRequest和RpcResponse,即rpc请求和响应的数据结构
- 基于Netty提供了编解码器
- 提供了序列化反序列化等工具
- rpc-registry
- 提供了服务发现和注册接口
- rpc-registry-zookeeper
- 基于zookeeper的服务发现和注册接口
- rpc-server
- rpc服务器(RpcServer)的实现,用来监听rpc请求以及向Zookeeper注册服务地址
- rpc服务本地调用
- rpc-sample-api
- rpc测试公共api服务接口
- rpc-sample-client
- rpc测试客户端
- rpc-sample-server
- rpc测试服务启动程序和服务实现
启动顺序
- 配置Zookeeper
- 解压zookeeper-3.4.9
- 进入conf目录,重命名zoo_sample.cfg为zoo.cfg(或者复制一份重命名)并修改一些配置选项如dataDir.另外可以看到默认的clientPort是2181
- 将bin目录加入环境变量PATH,这样则可直接使用zkServer命令直接启动
- 启动rpc-sample-server工程的下RpcBootstrap
- 启动rpc-sample-client工程下的测试程序HelloClient等
关键实现和核心模块分析
- RpcBootstrap
- 加载spring.xml实例化RpcServer
- 两个参数分别是rpc服务地址(127.0.0.1:8000)和基于ZooKeeper的服务注册接口实现(使用ZkClient连接Zookeeper的2181端口)
- 加载过程中,会首先调用setApplicationContext方法
- 扫描com.xxx.rpc.sample.server下带有@RpcService注解的类,本例是HelloServiceImpl和HelloServiceImpl2,即有两个rpc服务类,其中HelloServiceImpl2加了一个版本号用来区分第一个服务类,扫描后放入handlerMap,即服务名和服务对象之间的映射map
- 加载后,调用afterPropertiesSet方法
- 启动Netty服务端,监听8000端口;channelpipeline增加编解码器(RpcDecoder、RpcEncoder)和逻辑处理类(RpcServerHandler)
- RpcEncoder,编码器,消息格式为4个字节的消息长度和消息体;直接使用Protostuff进行序列化,编码对象为RpcResponse
- RpcDecoder,解码器;已解决粘包问题;使用Objenesis和Protostuff继续反序列化
- RpcServerHandler,收到RpcRequest后直接从handlerMap找到对应的服务类反射进行方法调用(使用了CGLib);最后向客户端写入rpc响应,完毕则主动关闭连接(所以从这里看是短连接)
- ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE)
- 这行代码相当于在rpc响应发送的这个操作完成之后关闭连接
- 注意Netty强烈建议直接通过添加监听器的方式获取I/O操作结果;当I/O操作完成之后,I/O线程会回调ChannelFuture中GenericFutureListener#operationComplete方法
- ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE)
- 绑定端口成功后,向Zookeeper注册上面的两个rpc服务
- 启动Netty服务端,监听8000端口;channelpipeline增加编解码器(RpcDecoder、RpcEncoder)和逻辑处理类(RpcServerHandler)
- 加载spring.xml实例化RpcServer
ChannelFutureListener CLOSE = new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) {
future.channel().close();
}
};
- RpcProxy
- 初始化亦通过加载spring.xml,指定了基于zookeeper的服务发现类ZooKeeperServiceDiscovery
- create方法,主要使用了jdk的动态代理;当代理方法执行的时候,首先根据请求的服务名利用Zookeeper的服务发现接口获取服务的address;然后封装rpc请求调用Netty客户端连接服务地址并发送
- 关于RpcClient,同Netty服务端,需要设置channelpipeline的编解码器和逻辑处理handler
- Channel channel = future.channel();
channel.writeAndFlush(request).sync();
channel.closeFuture().sync();
return response; - 注意上部分代码,发送rpc请求后等待发送完毕;发送完毕后等待连接关闭,此时线程阻塞直到服务端发送完回复消息并主动关闭连接,线程继续;所以这个例子并没有会有request对不上reponse的问题,因为每次rpc调用都是短连接且当前执行线程挂起;另外服务端收到request的时候,也会用requestId作为response的requestId
可改进地方
- 本人觉得spring相对较厚重,所以将spring去掉,对象实例化和依赖注入用比较简单的方式去处理;不过比较麻烦的是对于扫描@RpcService注解这部分需要手动处理
- 目前该示例提供的两个服务均是在同一个端口8000下的服务;如何测试不同的两个服务在不同的端口?按照该例子的设计,一个RpcServer即一个rpc发布服务器,该监听的端口下可以注册不同很多服务(当然一个Netty server本身可以bind多个端口,这个暂时不考虑实现);如果需要增加不同的服务,则需要单独启动RpcServer并向Zookeeper注册
其他待调研rpc框架
一个入门rpc框架的学习的更多相关文章
- 一个浏览器Fuzzing框架的学习
一个浏览器Fuzzing框架的学习 关于框架 之前是LCatro师傅在小密圈分享的他写的这个Fuzzing框架(不过我以前翻github时好像就看到过),但是之前一直没啥时间搞这方面,这两天研究学习了 ...
- 自行实现一个简易RPC框架
10分钟写一个RPC框架 1.RpcFramework package com.alibaba.study.rpc.framework; import java.io.ObjectInputStrea ...
- 基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇
基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇 前提 最近对网络编程方面比较有兴趣,在微服务实践上也用到了相对主流的RPC框架如Spring Cloud Gateway底层也切换 ...
- 基于Netty和SpringBoot实现一个轻量级RPC框架-Server篇
前提 前置文章: Github Page:<基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇> Coding Page:<基于Netty和SpringBoot实现 ...
- 基于Netty和SpringBoot实现一个轻量级RPC框架-Client篇
前提 前置文章: <基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇> <基于Netty和SpringBoot实现一个轻量级RPC框架-Server篇> 前 ...
- 基于Netty和SpringBoot实现一个轻量级RPC框架-Client端请求响应同步化处理
前提 前置文章: <基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇> <基于Netty和SpringBoot实现一个轻量级RPC框架-Server篇> & ...
- rpc框架dubbo学习入门及环境搭建(spring boot+Kotlin)
学习之前,确保有以下知识基础: Java网络编程 Socket传输数据 IO流 rpc简介及实现 rpc是remote procedure call的简写,意思为远程过程调用. rpc应用较多的情景是 ...
- 设计一个分布式RPC框架
0 前言 提前先祝大家春节快乐!好了,先简单聊聊. 我从事的是大数据开发相关的工作,主要负责的是大数据计算这块的内容.最近Hive集群跑任务总是会出现Thrift连接HS2相关问题,研究了解了下内部原 ...
- 一个简单RPC框架是怎样炼成的(I)——开局篇
开场白,这是一个关于RPC的相关概念的普及篇系列,主要是通过一步步的调整,提炼出一个相对完整的RPC框架. RPC(Remote Procedure Call Protocol)--远程过程调用协议, ...
随机推荐
- Gradle 依赖管理
依赖管理(在 build.gradle 中): 1.定义依赖仓库(repositories): Gradle 要求至少定义一个依赖仓库,依赖仓库可以是文件系统,也可以是中心服务器.最常用的是 jcen ...
- 忽略 iconv 转码错误导致输出中断
在遇到一些特殊字符时候,使用iconv转码会提示报错,导致整段文字输出为空,解决的方法是增加TRANSLIT//IGNORE $str = iconv("UTF-8", " ...
- Git Learning2 GitHub upload
1.在自己的github上创建一个仓库 2.git remote add [name] [link] 使用git来增加一个link的别名 3.git push [linkname] [分支名] 4.g ...
- Ubuntu16.04 使用sudo cat EOF 编辑文件,提示Permission denied错误的解决办法
一.执行命令报错 在Ubuntu16.04下,使用如下命令,修改hosts主机文件,居然提示权限错误: catty@node186:~$ sudo cat <<EOF > /etc/ ...
- 融云(API)
先记录下融云API地址:真是难找 IMKit: https://www.rongcloud.cn/docs/api/android/imkit/index.html IMLib: https://ww ...
- ionic 在windows环境下更换logo和加载图片的问题
做用自己的电脑做ionic项目时,更换logo和加载图片时,无论使用哪种命令,发现都上传不了,并且报错 最后发现,需要将 icon和splash两个文件改为.ai格式才能上传成功. 这是最终生成后的文 ...
- vue2 inheritAttrs、attrs和attrs和listeners使用
inheritAttrs.attrs和attrs和listeners使用场景: 组件传值,尤其是祖孙组件有跨度的传值. (1)inheritAttrs 属性说明:https://cn.vuejs.or ...
- C#流程控制语句--分支语句(if,switch,三位运算符)
分支语句:判断结构要求程序员指定一个或多个要评估或测试的条件,以及条件为真时要执行的语句(必需的)和条件为假时要执行的语句(可选的). 分支语句 if if(判断条件表达式) {//表达式结果为tu ...
- RTK与差分测量的区别
差分GPS定位原理 它使用一台 GPS基准接收机(基准站)和一台用户接收机(移动站),利用实时或事后处理技术,就可以使用户测量时消去公共的误差源 —卫星轨道误差.卫星钟差.大气延时.多路径效应.特别提 ...
- vbs中对excel的常用操作
使用QTP自动化测试中,用到对excel的读写操作,这里把一些常用对excel操作的方法进行了归纳,总结.(对excel格式设置的常用操作这里没有进行总结.) Function DataToExcel ...