轻量级分布式RPC框架
一个轻量级分布式RPC框架--NettyRpc
1、背景
最近在搜索Netty和Zookeeper方面的文章时,看到了这篇文章《轻量级分布式 RPC 框架》,作者用Zookeeper、Netty和Spring写了一个轻量级的分布式RPC框架。花了一些时间看了下他的代码,写的干净简单,写的RPC框架可以算是一个简易版的dubbo。这个RPC框架虽小,但是麻雀虽小,五脏俱全,有兴趣的可以学习一下。
项目地址:https://github.com/luxiaoxun/NettyRpc
自己花了点时间整理了下代码,并修改一些问题,以下是自己学习的一点小结。
2、简介
RPC,即 Remote Procedure Call(远程过程调用),调用远程计算机上的服务,就像调用本地服务一样。RPC可以很好的解耦系统,如WebService就是一种基于Http协议的RPC。
这个RPC整体框架如下:

这个RPC框架使用的一些技术所解决的问题:
服务发布与订阅:服务端使用Zookeeper注册服务地址,客户端从Zookeeper获取可用的服务地址。
通信:使用Netty作为通信框架。
Spring:使用Spring配置服务,加载Bean,扫描注解。
动态代理:客户端使用代理模式透明化服务调用。
消息编解码:使用Protostuff序列化和反序列化消息。
3、服务端发布服务
使用注解标注要发布的服务
服务注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface RpcService {
Class<?> value();
}
一个服务接口:
public interface HelloService {
String hello(String name);
String hello(Person person);
}
一个服务实现:使用注解标注

@RpcService(HelloService.class)
public class HelloServiceImpl implements HelloService { @Override
public String hello(String name) {
return "Hello! " + name;
} @Override
public String hello(Person person) {
return "Hello! " + person.getFirstName() + " " + person.getLastName();
}
}

服务在启动的时候扫描得到所有的服务接口及其实现:

@Override
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
Map<String, Object> serviceBeanMap = ctx.getBeansWithAnnotation(RpcService.class);
if (MapUtils.isNotEmpty(serviceBeanMap)) {
for (Object serviceBean : serviceBeanMap.values()) {
String interfaceName = serviceBean.getClass().getAnnotation(RpcService.class).value().getName();
handlerMap.put(interfaceName, serviceBean);
}
}
}

在Zookeeper集群上注册服务地址:
这里在原文的基础上加了AddRootNode()判断服务父节点是否存在,如果不存在则添加一个PERSISTENT的服务父节点,这样虽然启动服务时多了点判断,但是不需要手动命令添加服务父节点了。
关于Zookeeper的使用原理,可以看这里《ZooKeeper基本原理》。
4、客户端调用服务
使用代理模式调用服务:

public class RpcProxy {
private String serverAddress;
private ServiceDiscovery serviceDiscovery;
public RpcProxy(String serverAddress) {
this.serverAddress = serverAddress;
}
public RpcProxy(ServiceDiscovery serviceDiscovery) {
this.serviceDiscovery = serviceDiscovery;
}
@SuppressWarnings("unchecked")
public <T> T create(Class<?> interfaceClass) {
return (T) Proxy.newProxyInstance(
interfaceClass.getClassLoader(),
new Class<?>[]{interfaceClass},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
RpcRequest request = new RpcRequest();
request.setRequestId(UUID.randomUUID().toString());
request.setClassName(method.getDeclaringClass().getName());
request.setMethodName(method.getName());
request.setParameterTypes(method.getParameterTypes());
request.setParameters(args);
if (serviceDiscovery != null) {
serverAddress = serviceDiscovery.discover();
}
if(serverAddress != null){
String[] array = serverAddress.split(":");
String host = array[0];
int port = Integer.parseInt(array[1]);
RpcClient client = new RpcClient(host, port);
RpcResponse response = client.send(request);
if (response.isError()) {
throw new RuntimeException("Response error.",new Throwable(response.getError()));
} else {
return response.getResult();
}
}
else{
throw new RuntimeException("No server address found!");
}
}
}
);
}
}

这里每次使用代理远程调用服务,从Zookeeper上获取可用的服务地址,通过RpcClient send一个Request,等待该Request的Response返回。这里原文有个比较严重的bug,在原文给出的简单的Test中是很难测出来的,原文使用了obj的wait和notifyAll来等待Response返回,会出现“假死等待”的情况:一个Request发送出去后,在obj.wait()调用之前可能Response就返回了,这时候在channelRead0里已经拿到了Response并且obj.notifyAll()已经在obj.wait()之前调用了,这时候send后再obj.wait()就出现了假死等待,客户端就一直等待在这里。使用CountDownLatch可以解决这个问题。
注意:这里每次调用的send时候才去和服务端建立连接,使用的是短连接,这种短连接在高并发时会有连接数问题,也会影响性能。
从Zookeeper上获取服务地址:
每次服务地址节点发生变化,都需要再次watchNode,获取新的服务地址列表。
5、消息编码
请求消息:
响应消息:
消息序列化和反序列化工具:(基于 Protostuff 实现)
由于处理的是TCP消息,本人加了TCP的粘包处理Handler
channel.pipeline().addLast(new LengthFieldBasedFrameDecoder(65536,0,4,0,0))
消息编解码时开始4个字节表示消息的长度,也就是消息编码的时候,先写消息的长度,再写消息。
6、性能改进
Netty本身就是一个高性能的网络框架,从网络IO方面来说并没有太大的问题。
从这个RPC框架本身来说,在原文的基础上把Server端处理请求的过程改成了多线程异步:

public void channelRead0(final ChannelHandlerContext ctx,final RpcRequest request) throws Exception {
RpcServer.submit(new Runnable() {
@Override
public void run() {
LOGGER.debug("Receive request " + request.getRequestId());
RpcResponse response = new RpcResponse();
response.setRequestId(request.getRequestId());
try {
Object result = handle(request);
response.setResult(result);
} catch (Throwable t) {
response.setError(t.toString());
LOGGER.error("RPC Server handle request error",t);
}
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
LOGGER.debug("Send response for request " + request.getRequestId());
}
});
}
});
}

Netty 4中的Handler处理在IO线程中,如果Handler处理中有耗时的操作(如数据库相关),会让IO线程等待,影响性能。
个人觉得该RPC的待改进项:
1)客户端保持和服务进行长连接,不需要每次调用服务的时候进行连接,长连接的管理(通过Zookeeper获取有效的地址)。
2)客户端请求异步处理的支持,不需要同步等待:发送一个异步请求,返回Feature,通过Feature的callback机制获取结果。
3)编码序列化的多协议支持。
有时间再改改吧。。
项目地址:https://github.com/luxiaoxun/NettyRpc
参考:
轻量级分布式 RPC 框架:http://my.oschina.net/huangyong/blog/361751
你应该知道的RPC原理:http://www.cnblogs.com/LBSer/p/4853234.html
轻量级分布式RPC框架的更多相关文章
- 一个轻量级分布式RPC框架--NettyRpc
1.背景 最近在搜索Netty和Zookeeper方面的文章时,看到了这篇文章<轻量级分布式 RPC 框架>,作者用Zookeeper.Netty和Spring写了一个轻量级的分布式RPC ...
- 轻量级分布式 RPC 框架
@import url(/css/cuteeditor.css); 源码地址:http://git.oschina.net/huangyong/rpc RPC,即 Remote Procedure C ...
- 【转】轻量级分布式 RPC 框架
第一步:编写服务接口 第二步:编写服务接口的实现类 第三步:配置服务端 第四步:启动服务器并发布服务 第五步:实现服务注册 第六步:实现 RPC 服务器 第七步:配置客户端 第八步:实现服务发现 第九 ...
- 一个轻量级分布式 RPC 框架 — NettyRpc
原文出处: 阿凡卢 1.背景 最近在搜索Netty和Zookeeper方面的文章时,看到了这篇文章<轻量级分布式 RPC 框架>,作者用Zookeeper.Netty和Spring写了一个 ...
- 轻量级分布式 RPC 框架(转)
RPC,即 Remote Procedure Call(远程过程调用),说得通俗一点就是:调用远程计算机上的服务,就像调用本地服务一样. RPC 可基于 HTTP 或 TCP 协议,Web Servi ...
- [源码解析] PyTorch 分布式(15) --- 使用分布式 RPC 框架实现参数服务器
[源码解析] PyTorch 分布式(15) --- 使用分布式 RPC 框架实现参数服务器 目录 [源码解析] PyTorch 分布式(15) --- 使用分布式 RPC 框架实现参数服务器 0x0 ...
- [源码解析] PyTorch 分布式(17) --- 结合DDP和分布式 RPC 框架
[源码解析] PyTorch 分布式(17) --- 结合DDP和分布式 RPC 框架 目录 [源码解析] PyTorch 分布式(17) --- 结合DDP和分布式 RPC 框架 0x00 摘要 0 ...
- dubbo分布式rpc框架用法
dubbo是阿里巴巴开源的分布式服务框架,致力于提供高性能和透明化的rpc远程服务调用方案,以及soa服务治理方案,如果没有分布式需求,是不需要dubbo的,分布式环境dubbo的使用架构官方给出了一 ...
- 分布式RPC框架性能大比拼 dubbo、motan、rpcx、gRPC、thrift的性能比较
Dubbo 是阿里巴巴公司开源的一个Java高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring框架无缝集成.不过,略有遗憾的是,据说在淘宝内部,dub ...
随机推荐
- go iota
package main import ( "fmt" ) const ( a = 'A' b c = iota d ) func main() { fmt.Println(a) ...
- 推动Common Lisp的实际应用
推动Common Lisp的实际应用 推动Common Lisp的实际应用
- COCOS2D中对精灵的操作、对图片的各种操作
内容简要: 1.初始化 2.创建无图的精灵 3.设置精灵贴图大小 4.添加入层中 5.对精灵进行缩放 6.对精灵宽或高进行缩放 7.旋转精灵 8.设置精灵透明度 9.精灵的镜像反转 10.设 ...
- AVOS_百度百科
AVOS_百度百科 AVOS 目录 公司产品 AVOS 是 YouTube 创始人 Chad Hurley 和 Steve Chen(陈士骏)创立的互联网公司. 编辑本段公司产品 产品包括 ...
- java中super()和this()浅析
<span style="font-size:18px;">本质:这两个都是调用构造方法的方法.</span> 在java中,super()是在当前类的构造 ...
- SQL Server Database 维护计划创建一个完整的备份策略
SQL Server维护计划Maintenance Plan这是一个非常有用的维护工具,能够完成大部分的数据库维护任务,通过这些功能包.您可以省略大量的编码时间. 介绍的不是非常多,特此补上一篇 ...
- Delphi 基本数据类型列表 高级数据类型列表 字符类型查询列表清单
原文:Delphi 基本数据类型列表 高级数据类型列表 字符类型查询列表清单 长长的列表文字类型文件 分类 范围 字节 备注 简单类型 序数 整数 Integer -2147483648 .. 214 ...
- Oracle Client: TNS: Connect timeout ocurred.
1. 检查Oracle Server 的防火墙是否关闭. 2. Client, Server 重启.
- CC 3-Palindromes(manacher)
传送门:3-Palindromes 题意:求为回文串且能整除3且不前导0的子串个数. 分析:由 manacher算法O(N)可算出以i为坐标的最长为p[i]回文子串,且Si-k,Si-k+1..... ...
- loj1236(数学)
传送门:Pairs Forming LCM 题意:题意:问符合 lcm(i,j)=n (1<=i<=j<=n,1<=n<=10^14) 的 (i,j) 有多少对. 分析: ...