tomcat源码阅读之Tribes.RpcChannel
一、RpcChannel简介:
1、RPC即远程过程调用,它的提出旨在消除通信细节、屏蔽繁杂且易错的底层网络通信操作,像调用本地服务一般地调用远程服务,让业务开发者更多关注业务开发而不必考虑网络、硬件、系统的异构复杂环境。
2、假设一个集群环境中有node1, node2, node3, node4四个集群节点,现在node1调用RPC接口向node2, node3, node4进行服务请求,其过程如下:
a) 先将待传递的数据放到NIO集群通信框架中,由于使用的是NIO模式,线程无需阻塞直接返回, 由于与集群其他节点通信需要花销若干时间,为了提高CPU使用率当前线程应该放弃CPU的使用权进行等待操作;
b) Node2, node3, node4分别收到请求消息后,根据请求消息封装成Response对象返回;
c) 假如NIO集群通信框架首先接收到node2节点的响应消息,将Response对象保存至响应数组;
d) 由于改RPC消息响应模式为RpcResponseType.ALL_REPLY,而此时集群通信框架只收到了node2的响应消息,还有node3和node4的响应消息尚未收到,因此线程持续阻塞中;
e) 此时收到了node4的响应消息,和node2一样,将Response对象保存到响应数组,最后收到node3的响应消息时,将Response对象保存到响应数组;
f) 现在所有节点的响应都已经收集完毕,是时候通知刚刚被阻塞的那条线程了,原来的线程被notify醒后拿到所有节点的响应Response[]进行处理,至此完成了整个集群RPC过程。
3、在多线程环境下,由于多个线程都调用了RPC接口,此时收到的响应消息并不知道是哪个线程发送的,因此在发送之前生成一个UUID标识,此标识要保证同socket中唯一,再把UUID与线程对象关系对应起来,可使用Map数据结构实现,UUID的值作为key,线程对应的锁对象为value。
4、RpcCallback接口:
public interface RpcCallback {
public Serializable replyRequest(Serializable msg, Member sender);
public void leftOver(Serializable msg, Member sender);
}
接口里面的方法是预留提供给上层具体逻辑处理的入口,replyRequest方法用于处理响应逻辑,leftOver方法用于残留请求的逻辑处理,残留响应是指有时我们在接收到第一个响应后就唤起线程。如果node1调用node2的RPC方法,node2收到请求消息后,会调用node2上的replyRequest处理请求消息,处理完后封装成Response对象返回给集群通信框架,集群通信框架根据响应模式决定什么时候返回Response对象给node1,node1收到Response对象后进行处理;如果响应模式是FIRST_REPLY或者MAJORITY_REPLY时(也就是收到第一条响应或者超过半数的响应时),还有部分响应消息不在Response里面,这个时候集群通信框架会调用node1节点上的leftover方法处理;
5、RPCChannel基于Tribes集群通信框架,是整个RPC的抽象,它实现通信框架的ChannelListener接口,实现了该接口就能在messageReceived方法中处理接收到的消息。RPCChannel.send方法也是调用的Channel.send方法实现的;
二、自定义RpcChannel Demo:
自定义一个RPC,它要实现RpcCallback接口,分别对请求处理和残留响应处理,这里请求处理仅仅是简单返回“hello,response for you!”作为响应消息,残留响应处理则是简单输出“receive a leftover message!”。代码如下:
public class MyRPC implements RpcCallback { @Override
public Serializable replyRequest(Serializable msg, Member sender) {
RpcMessage mapmsg = (RpcMessage) msg;
mapmsg.message = "hello,response for you!";
return mapmsg;
} @Override
public void leftOver(Serializable msg, Member sender) {
System.out.println("receive a leftover message!");
} public static void main(String[] args) {
MyRPC myRPC = new MyRPC();
byte[] rpcId = new byte[] {1, 1, 1, 1};
byte[] key = new byte[] {0, 0, 0, 0};
String message = "hello";
int sendOptions = Channel.SEND_OPTIONS_SYNCHRONIZED_ACK | Channel.SEND_OPTIONS_USE_ACK;
RpcMessage msg = new RpcMessage(rpcId, key, (Serializable) message);
RpcChannel rpcChannel = new RpcChannel(rpcId, channel, myRPC);
RpcResponse[] resp =
rpcChannel.send(channel.getMembers(), msg, RpcResponseType.FIRST_REPLY, sendOptions, 3000);
while (true)
Thread.currentThread().sleep(1000);
}
}
三、UML图:
1、RpcMessage定义通信消息协议,实现Externalizable接口自定义序列化和反序列化;message用于存放响应消息,uuid标识用于关联线程,rpcId用于标识RPC实例,reply表示是否回复消息;其writeExternal方法用于将RpcMessage序列化,readExternal用于将二进制的字节流反序列化;
2、NoRpcChannelReply表示响应消息,继承于RpcMessage类,其成员与RpcMessage完全相同,只是在构造函数里面将reply变量指定为True;
3、当调用RpcChannel.send方法后,线程会阻塞住,唤醒线程的条件有四种:接收到第一个响应就唤起线程、接收到集群中大多数节点的响应就唤起线程、接收到集群中所有节点的响应才唤起线程、无需等待响应的无响应模式,代码中的定义如下:
public class RpcResponseType {
public static final int FIRST_REPLY = 1;
public static final int MAJORITY_REPLY = 2;
public static final int ALL_REPLY = 3;
public static final int NO_REPLY = 4;
}
4、Response表示响应对象,用于封装接收到的消息,Member在通信框架是节点的抽象,这里用来表示来源节点。Message表示响应的消息,也就是RpcMessage类型的对象;
5、RpcCollectorKey只是对字节数组类型的id的封装,RpcCollector 表示RPC响应集,用于存放同个UUID的所有响应,其成员变量responses存储了响应数组,key与UUID的意义相同,表示该key下的所有的response响应,options表示线程唤醒类型(FIRST_REPLY, MAJORITY_REPLY, ALL_REPLY, NO_REPLY);destcnt为整型变量,表示总共需要收到的响应数量,如果线程唤醒条件为MAJORITY_REPLY时,response数组中的数量必须大于destcnt的一半以上,如果线程唤醒条件为ALL_REPLY,则response数组中的数量等于destcnt时才会唤醒线程;在isComplete方法里面就是根据线程唤醒条件判断的:
6、RpcChannel是整个RPC的核心抽象,它实现通信框架的ChannelListener接口,实现了该接口就能在messageReceived方法中处理接收到的消息。它还实现了send方法,这两个方法都是基于成员变量Channel来实现的:
Send方法通过调用channel.send将消息发送到其他集群节点上,并阻塞线程等到response响应数组收到的响应数量满足唤醒时才将Response对象数组返回给调用方;
RPC请求发送出去后,其他集群节点收到消息后触发messageReceived方法,在这个方法里面首先调用replyRequest对请求消息进行处理,处理完后将reply设置为true并发送给调用方,而调用方将其封装成Response对象并添加到response数组中:
而调用方接收到响应消息后,将其封装成Response对象并添加到response数组中,此时如果满足线程唤醒条件则唤醒线程:
通过send方法可以看到在发送RPC请求Key添加到responseMap中,发送后阻塞线程,线程唤醒后将该key从responseMap中删除,messageReceived中处理响应消息时,如果在responseMap中找不到该Key,则说明该消息属于残留请求,此时应该调用leftOver来处理该消息;
tomcat源码阅读之Tribes.RpcChannel的更多相关文章
- Tomcat源码阅读(二)初始化
近来,我开始阅读tomcat的源码,感觉还挺清晰易懂:为了方便理解,我参考了网上的一些文章,把tomcat的组成归纳一下:整个tomcat的组成如下图所示: Tomcat在接收到用户请求时,将会通过以 ...
- tomcat源码阅读
1 工具准备 需要SVN.Maven.JDK.Eclipse.Eclipse M2插件 2 下载源码及发布包 源码在这里:http://svn.apache.org/repos/a ...
- tomcat源码阅读之过滤器
一.Servlet过滤器: 1.介绍: Servlet过滤器本身并不生成请求和响应对象,它只提供过滤作用. Servlet过滤器能够在Servlet被调用之前检查Request对象,修改Request ...
- tomcat源码阅读之SingleThreadModel
一.接口简介: 实现了SingleThreadModel接口的servlet类只能保证在同一时刻,只有一个线程执行该servlet实例的service方法,在tomcat实现中会创建多个servlet ...
- tomcat源码阅读之载入器(Loader)
一.Java类的载入器: 双亲委派模型: 1.JVM提供了三种类型的类加载器:引导类载入器(bootstrap class loader).扩展类载入器(extension class loader) ...
- tomcat源码阅读之BackupManager
一. 配置: <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster" channelSendOpti ...
- tomcat源码阅读之集群
一. 配置: 在tomcat目录下的conf/Server.xml配置文件中增加如下配置: <!-- Cluster(集群,族) 节点,如果你要配置tomcat集群,则需要使用此节点. clas ...
- tomcat源码阅读之安全机制
一.领域(Realm): 1.Principal接口代表角色信息,包含了三个成员:用户名.密码.role列表(以逗号分隔),对应了tomcat-users.xml文件中一行user信息: Generi ...
- tomcat源码阅读之默认连接器
默认连接器 一.UML图: 1.所有的连接器都要实现Connector接口,必须创建Request对象和Response对象,httpConnector作为默认连接器,肯定也是要实现Connector ...
随机推荐
- 一、TCP扫描技术
一.TCP扫描技术 常用的端口扫描技术有很多种,如 TCP connect() 扫描 .TCP SYN 扫描.TCP FIN 扫描 等,网络上也有很多文章专门介绍,比如 :http://www.ant ...
- tf多线程读取数据
多线程读取数据的机制 tf中多线程读取数据跟常规的python多线程思路一致,是基于Queue的多线程编程. 主线程读取数据,然后计算,在读数据这部分有两个线程,一个线程读取文件名,生成文件名队列,另 ...
- el-container 实践上的布局问题
当自己利用element-ui上面的例子来实现整体布局的时候, 就是自己分开成单独的vue组件时,发现布局是不对的,效果是这样的: 代码是这样的,代码一模一样,只是拆开了各个组件,如下图: 后来发现是 ...
- shell脚本实例-case实现jumpserver跳板机
1,先通过ssh-keygen 生成公钥,然后将公钥推送到各个主机ssh-copy-id web1|ip 2简单的代码实现 #!/usr/bin/bash trap "" HUP ...
- DOM中offsetLeft与style.left的区别
offsetLeft 获取的是相对于父对象的左边距 left 获取或设置相对于 具有定位属性(position定义为relative)的父对象 的左边距 如果父div的position定义为relat ...
- 性能测试-6.VUG脚本参数化
前言:(原文地址)版面调整 什么是VUGEN action以及作用 参数化 参数化取值(9种组合,在不同场景中如何运用) 一.VUGEN是 LoadRunner 用于开发 Vuser 脚本的主要工具. ...
- 实现在当前的日期上加N天
function getNewDay(dateTemp, days) { var dateTemp = dateTemp.split("-"); var nDate = new D ...
- HihoCoder - 1807:好的数字串 (KMP DP)
Sample Input 6 1212 Sample Output 298 给定一个数字字符串S,如果一个数字字符串(只包含0-9,可以有前导0)中出现且只出现1次S,我们就称这个字符串是好的. 例如 ...
- 压缩文件破解rarcrack-支持格式zip,rar和7z
Kali上没有,需要自己安装 apt-get install rarcrack 安装成功后, 新建一个文本文档,元素: <?xml version="1.0" encodin ...
- 软工实践——结对作业2【wordCount进阶需求】
附录: 队友的博客链接 本次作业的博客链接 同名仓库项目地址 一.具体分工 我负责撰写爬虫爬取信息以及代码整合测试,队友子恒负责写词组词频统计功能的代码. 二.PSP表格 PSP2.1 Persona ...