简易RPC框架-学习使用
*:first-child {
margin-top: 0 !important;
}
body>*:last-child {
margin-bottom: 0 !important;
}
/* BLOCKS
=============================================================================*/
p, blockquote, ul, ol, dl, table, pre {
margin: 15px 0;
}
/* HEADERS
=============================================================================*/
h1, h2, h3, h4, h5, h6 {
margin: 20px 0 10px;
padding: 0;
font-weight: bold;
-webkit-font-smoothing: antialiased;
}
h1 tt, h1 code, h2 tt, h2 code, h3 tt, h3 code, h4 tt, h4 code, h5 tt, h5 code, h6 tt, h6 code {
font-size: inherit;
}
h1 {
font-size: 28px;
color: #000;
}
h2 {
font-size: 24px;
border-bottom: 1px solid #ccc;
color: #000;
}
h3 {
font-size: 18px;
}
h4 {
font-size: 16px;
}
h5 {
font-size: 14px;
}
h6 {
color: #777;
font-size: 14px;
}
body>h2:first-child, body>h1:first-child, body>h1:first-child+h2, body>h3:first-child, body>h4:first-child, body>h5:first-child, body>h6:first-child {
margin-top: 0;
padding-top: 0;
}
a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 {
margin-top: 0;
padding-top: 0;
}
h1+p, h2+p, h3+p, h4+p, h5+p, h6+p {
margin-top: 10px;
}
/* LINKS
=============================================================================*/
a {
color: #4183C4;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
/* LISTS
=============================================================================*/
ul, ol {
padding-left: 30px;
}
ul li > :first-child,
ol li > :first-child,
ul li ul:first-of-type,
ol li ol:first-of-type,
ul li ol:first-of-type,
ol li ul:first-of-type {
margin-top: 0px;
}
ul ul, ul ol, ol ol, ol ul {
margin-bottom: 0;
}
dl {
padding: 0;
}
dl dt {
font-size: 14px;
font-weight: bold;
font-style: italic;
padding: 0;
margin: 15px 0 5px;
}
dl dt:first-child {
padding: 0;
}
dl dt>:first-child {
margin-top: 0px;
}
dl dt>:last-child {
margin-bottom: 0px;
}
dl dd {
margin: 0 0 15px;
padding: 0 15px;
}
dl dd>:first-child {
margin-top: 0px;
}
dl dd>:last-child {
margin-bottom: 0px;
}
/* CODE
=============================================================================*/
pre, code, tt {
font-size: 12px;
font-family: Consolas, "Liberation Mono", Courier, monospace;
}
code, tt {
margin: 0 0px;
padding: 0px 0px;
white-space: nowrap;
border: 1px solid #eaeaea;
background-color: #f8f8f8;
border-radius: 3px;
}
pre>code {
margin: 0;
padding: 0;
white-space: pre;
border: none;
background: transparent;
}
pre {
background-color: #f8f8f8;
border: 1px solid #ccc;
font-size: 13px;
line-height: 19px;
overflow: auto;
padding: 6px 10px;
border-radius: 3px;
}
pre code, pre tt {
background-color: transparent;
border: none;
}
kbd {
-moz-border-bottom-colors: none;
-moz-border-left-colors: none;
-moz-border-right-colors: none;
-moz-border-top-colors: none;
background-color: #DDDDDD;
background-image: linear-gradient(#F1F1F1, #DDDDDD);
background-repeat: repeat-x;
border-color: #DDDDDD #CCCCCC #CCCCCC #DDDDDD;
border-image: none;
border-radius: 2px 2px 2px 2px;
border-style: solid;
border-width: 1px;
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
line-height: 10px;
padding: 1px 4px;
}
/* QUOTES
=============================================================================*/
blockquote {
border-left: 4px solid #DDD;
padding: 0 15px;
color: #777;
}
blockquote>:first-child {
margin-top: 0px;
}
blockquote>:last-child {
margin-bottom: 0px;
}
/* HORIZONTAL RULES
=============================================================================*/
hr {
clear: both;
margin: 15px 0;
height: 0px;
overflow: hidden;
border: none;
background: transparent;
border-bottom: 4px solid #ddd;
padding: 0;
}
/* IMAGES
=============================================================================*/
img {
max-width: 100%
}
-->
基于netty4,protostuff的出于学习目的的RPC框架,后续会完善功能。
背景
做微服务有不短时间了,单纯RPC框架呢生产环境上之前主要使用dubbo,出于学习了解过Spring Cloud以及其它的比如Finagle,grpc,thrift。看过dubbo部分源码,了解过RPC的基本原理,但不系统。
写一个类似dubbo的有多难
猛的一看dubbo源码的工程构建的话,代码量不少,工程大大小小估计有十几二十个,一时不知从何处下手,如果不反复debug源码的话一时半会想了解清楚还是有一定难度的。如何面对一个庞大的工程呢?我的办法就是化繁为简,先看最为核心的。
如果给你看这个图你不一定短时间知道它的内部结构。

如果给你看这张图呢?会不会轻松很多?

那么根据RPC的基本原理,首先就只观注如下图的几个部分就可以了:

- 远程通信,dubbo支持多种远程通信协议,先只看netty
- 编码,远程通信时需要将信息进行编码以及解码,这里采用protostuff
- 客户端代理,RPC的主要特点就是将远程调用本地化,实现这一目标的手段就是动态代理
以上这些,就可以实现一个基本的RPC调用了,但dubbo为什么有那么多功能呢,因为:
- 支持了强大的SPI插件机制,让用户方便的扩展原有功能
- 提供了多个已经实现的扩展功能,比如远程通信除了netty还有mina,注册中心除了推荐的ZK还是redis等
- 在服务治理上下了很大的功夫,比如服务注册与发现,限流,多线程等
综上所看实现一个简易的RPC并不难,难在对于功能的抽象,扩展点的支持,性能优化等方面上。为了系统的了解这些优秀RPC框架,我想按自己的思路实现以此验证实现一个RPC到底有多难。我并没有完全从零开始设计然后编码,因为我喜欢参考一些开源的项目,因为我相信有我这类想法的人一定不在少数,造轮子玩的事很多人都喜欢。
项目参考
主要思路来源于如下两个项目,其中第一个项目是原始版本,第二个版本是另外的维护版本。我按我自己的需求进一步修改成自己想要的结果。
另外主要参考dubbo源码
变更
出于我个人的理解做了不同程序的调整以及增强。
增加客户端引用服务的注解
这里只是为了简单,只实现通过注解方式获取远程接口的实例。先定义引用注解,isSync属性是为了支持同步接口以及异步接口,默认为同步接口。
public @interface RpcReference {
boolean isSync() default true;
}
客户端引用实例使用:
@RpcReference
private ProductService productService;
原理是通过BeanPostProcessor接口来实现注解字段的注入:
public class BeanPostPrcessorReference implements BeanPostProcessor {
//...
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
//...
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
try {
if (! field.isAccessible()) {
field.setAccessible(true);
}
RpcReference reference = field.getAnnotation(RpcReference.class);
if (reference != null) {
Object value=this.rpcClient.createProxy(field.getType(),reference.isSync());
if (value != null) {
field.set(bean, value);
}
}
} catch (Exception e) {
throw new BeanInitializationException("Failed to init remote service reference at filed " + field.getName() + " in class " + bean.getClass().getName(), e);
}
}
return bean;
}
}
增加服务注解的功能
版本1项目的RpcService需要指定唯一的远程接口,感觉有限制,修改为支持多接口的远程服务。
@RpcService
public class ProductServiceImpl implements ProductService,CommentService
重构代码
- 针对版本1同步调用存在的问题,参考了dubbo的思路,版本1在客户端获取结果时有这么段代码,它的问题在于channelRead0方法的执行有可能出现在obj.wait()方法之前,这样有可能造成客户端永远获取不到预期的结果。
@Override
public void channelRead0(ChannelHandlerContext ctx, RpcResponse response) throws Exception {
this.response = response; synchronized (obj) {
obj.notifyAll();
}
} public RpcResponse send(RpcRequest request) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
//.... ChannelFuture future = bootstrap.connect(host, port).sync();
future.channel().writeAndFlush(request).sync(); synchronized (obj) {
obj.wait();
} if (response != null) {
future.channel().closeFuture().sync();
}
return response;
} finally {
group.shutdownGracefully();
}
}
修改后的版本,核心就是参考dubbo的做法返回一个ResponseFuture,在远程方法回调时更新真实的返回值,最后通过get()阻塞方法获取结果。由于我对原方案变更比较多就不贴代码了,有兴趣的可以看这:https://github.com/jiangmin168168/jim-framework
- 针对版本2在同步获取服务端返回结果采用AbstractQueuedSynchronizer,感觉有些复杂,采用Lock替代,ReponseFuture获取结果的逻辑:
private ReentrantLock lock = new ReentrantLock();
private Condition doneCondition=lock.newCondition();
public Object get(long timeout, TimeUnit unit) {
long start = System.currentTimeMillis();
if (!this.isDone()) {
this.lock.lock();
try{
while (!this.isDone()) {
this.doneCondition.await(2000,TimeUnit.MICROSECONDS);
if(System.currentTimeMillis()-start>timeout){
break;
}
}
}
catch (InterruptedException ex){
throw new RpcException(ex);
}
finally {
this.lock.unlock();
}
}
return this.getResultFromResponse();
}
- 针对程序关闭时资源的回收,参考了dubbo的思路,采用addShutdownHook注册回收函数
- 增加了filter机制,这个功能对RPC是非常重要的,比如日志,异常,权限等通用功能的动态植入。
定义filter接口
public interface RpcFilter<T> {
<T> T invoke(RpcInvoker invoker, RpcInvocation invocation);
}
定义filter注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface ActiveFilter {
String[] group() default {};
String[] value() default {};
}
server invoker
public class RpcServerInvoker extends AbstractInvoker<RpcRequest> {
private final Map<String, Object> handlerMap;
public RpcServerInvoker(Map<String, Object> handlerMap, Map<String,RpcFilter> filterMap) {
super(handlerMap,filterMap);
this.handlerMap=handlerMap;
}
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, RpcRequest rpcRequest) {
//...
}
@Override
public RpcResponse invoke(RpcInvocation invocation) {
//...
}
}
AbstractInvoker构造函数中的filterMap是通过下面方式注入。
public class RpcServerInitializer extends ChannelInitializer<SocketChannel> implements ApplicationContextAware {
//...
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, Object> rpcFilterMap = applicationContext.getBeansWithAnnotation(ActiveFilter.class);
if (null!=rpcFilterMap) {
for (Object filterBean : rpcFilterMap.values()) {
Class<?>[] interfaces = filterBean.getClass().getInterfaces();
ActiveFilter activeFilter=filterBean.getClass().getAnnotation(ActiveFilter.class);
if(null!=activeFilter.group()&& Arrays.stream(activeFilter.group()).filter(p->p.contains(ConstantConfig.PROVIDER)).count()==0){
continue;
}
for(Class<?> clazz:interfaces) {
if(clazz.isAssignableFrom(RpcFilter.class)){
this.filterMap.put(filterBean.getClass().getName(),(RpcFilter) filterBean);
}
}
}
}
}
}
AbstractInvoker,主要有ServerInvoker以及ClientInvoker两个子类,两个子类分别获取不同作用域的Filter然后构建Filter执行链。
public abstract class AbstractInvoker<T> extends SimpleChannelInboundHandler<T> implements RpcInvoker {
private final Map<String, Object> handlerMap;
private final Map<String,RpcFilter> filterMap;
protected AbstractInvoker(Map<String, Object> handlerMap, Map<String,RpcFilter> filterMap){
this.handlerMap = handlerMap;
this.filterMap=filterMap;
}
public RpcInvocation buildRpcInvocation(RpcRequest request){
//...
}
public RpcInvoker buildInvokerChain(final RpcInvoker invoker) {
RpcInvoker last = invoker;
List<RpcFilter> filters = Lists.newArrayList(this.filterMap.values());
if (filters.size() > 0) {
for (int i = filters.size() - 1; i >= 0; i --) {
final RpcFilter filter = filters.get(i);
final RpcInvoker next = last;
last = new RpcInvoker() {
@Override
public Object invoke(RpcInvocation invocation) {
return filter.invoke(next, invocation);
}
};
}
}
return last;
}
protected abstract void channelRead0(ChannelHandlerContext channelHandlerContext, T t);
public abstract Object invoke(RpcInvocation invocation);
}
构建filter链,入口点是在构建代理的逻辑中。
public class RpcProxy <T> implements InvocationHandler {
//...
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//...
RpcInvoker rpcInvoker=invoker.buildInvokerChain(invoker);
ResponseFuture response=(ResponseFuture) rpcInvoker.invoke(invoker.buildRpcInvocation(request));
if(isSync){
return response.get();
}
else {
RpcContext.getContext().setResponseFuture(response);
return null;
}
}
}
- 版本2的异步实现有点奇怪,感觉调用方式不RPC(特别是call接口,需要以字符串形式描述调用的方法)
IAsyncObjectProxy client = rpcClient.createAsync(HelloService.class);
RPCFuture helloFuture = client.call("hello", Integer.toString(i));
String result = (String) helloFuture.get(3000, TimeUnit.MILLISECONDS);
通过dubbo的方式的版本:创建代理的逻辑中根据是否同步来返回不同的值,如果是同步那么调用阻塞方法获取实时返回的值,如果是异步直接返回null,同时将ResponseFuture放入RpcContext这个上下文变量中。
RpcInvoker rpcInvoker=invoker.buildInvokerChain(invoker);
ResponseFuture response=(ResponseFuture) rpcInvoker.invoke(invoker.buildRpcInvocation(request)); if(isSync){
return response.get();
}
else {
RpcContext.getContext().setResponseFuture(response);
return null;
}
获取异步接口:
@RpcReference(isSync = false)
private ProductService productServiceAsync;
异步方法调用,获取结果时需要从RpcContext中获取,感觉调用有点复杂,特别是需要通过RpcContext来获取,后续有更好的方案再更新。
Product responseFuture= this.productServiceAsync.getById(productId);
if(null==responseFuture){
System.out.println("async call result:product is null");
Product responseFutureResult= (Product) RpcContext.getContext().getResponseFuture().get();
if(null!=responseFutureResult){
System.out.println("async call result:"+responseFutureResult.getId());
}
}
- 调整了目录结构以及类名
根据自己的理解,重命令了一些类名,也调整了一些目录结构。
至此,RPC拥有了远程通信,序列化,同步异步调用,客户端代理,Filter等常用功能。所依赖的包也有限,要想完善RPC无非是做加法以及优化。尽管不能写也一个超过dubbo的项目,但至少可以用自己的思路去模仿,并不是那么的不可想象。
未来添加的功能
- 服务注册发现
- 限流/熔断
- 服务版本
- 客户端多线程
- ......
简易RPC框架-学习使用的更多相关文章
- 简易RPC框架-心跳与重连机制
*:first-child { margin-top: 0 !important; } body > *:last-child { margin-bottom: 0 !important; } ...
- 简易RPC框架-客户端限流配置
*:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* ...
- 简易RPC框架-SPI
*:first-child { margin-top: 0 !important; } body > *:last-child { margin-bottom: 0 !important; } ...
- 自行实现一个简易RPC框架
10分钟写一个RPC框架 1.RpcFramework package com.alibaba.study.rpc.framework; import java.io.ObjectInputStrea ...
- RPC框架学习+小Demo实例
一.什么是RPC协议? 全称:远程过程调度协议 效果:使消费者向调用本地方法一样调用远程服务方法,对使用者透明 目前常用:Dubbo.Thirft.Sofa.... 功能: 建立远程通信(socket ...
- 简易RPC框架-过滤器机制
*:first-child { margin-top: 0 !important; } body > *:last-child { margin-bottom: 0 !important; } ...
- 简易RPC框架-上下文
*:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* ...
- 简易RPC框架-代理
*:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* ...
- RPC框架学习总结
1.RPC是一种技术框架的称呼,不是某种具体协议,不局限于某种协议,RPC顾名思义就是远程过程调用,其核心思想是,RPC客户端调用远程服务器上的接口完成过程调用,远程服务器把结果返回. 2.RPC的最 ...
随机推荐
- Androidstudio2.0.0汉化教程及汉化包。
()Eric为大家带来Androidstudio2.0.0的简单汉化教程,许多小伙伴喜欢使用中文版的AS那么没有中文的AS只能靠自己汉化取得更好的体验. 第一步下载AS2.0.0汉化包,我有链接给大家 ...
- Linux常用命令整理
1.常用命令:cd 进入 ls(list)查看当前目录下的文件 pwd 查看目录的路径 who an i 查看当前用户 clear 清除屏幕 2.绝对路径:从根目录开始\ 相对路径:上一层.下一层 ...
- Java ---理解MVC架构
之间的文章,我们主要是介绍了jsp的相关语法操作,我们可以通过请求某个jsp页面,然后由相对应的servlet实例给我们返回html页面.但是在实际的项目中,我们很少会直接的请求某个页面,一般都是请求 ...
- C++学习笔记之模板篇
title: C++学习笔记之模板篇 tags: c++,c,模板,vector,friend,static,运算符重载,标准模板 --- 一.模板 不管是函数模板还是类模板,在未初始化前都是不占用内 ...
- Masonry适配的简单使用
一.Masonry是什么: 答:是一个很好的三方,用来做适配的 二.怎么使用Masonry 1.先导入头文件 #define MAS_SHORTHAND #define MAS_SHORTHAND_G ...
- macos系统下共语言gopath变量的设置
一.问题 在macos下安装golang开发环境,想更改gopath路径,通过export GOPATH=/Volume/E/go 在vscode中通过go env命令查看GOPATH还是原始默认的, ...
- Entity Framework细节追踪
小分享:我有几张阿里云优惠券,用券购买或者升级阿里云相应产品最多可以优惠五折!领券地址:https://promotion.aliyun.com/ntms/act/ambassador/shareto ...
- 在jsp中用一数组存储了数据库表中某一字段的值,然后在页面中输出其中的值。
List<String> list = new ArrayList<String>(); String sql = "select userName from us ...
- 使用纯CSS方案,解决垂直居中
CSS是HTML元素的剪刀手,它极度的丰富了web页面的修饰.在众多CSS常见的样式需求中,有一奇葩式的存在[垂直居中],因为不管是从逻辑实现方面还是从正常需求量来讲,这都没理由让这个需求在实践过程中 ...
- php流程管理
流程控制即某个人发起一个流程,通过一层一层审核,通过后,完成整个流程,若有一层审核未通过,中断整个流程.即结束! 比如请假流程: 某一员工发起一个请假流程,那么这个流程的节点人员即他的上级,上上级,上 ...
