dubbo协议下的单一长连接与多线程并发如何协同工作
上班的路上突然就冒出了这么个问题:既然在dubbo中描述消费者和提供者之间采用的是单一长连接,那么如果消费者端是高并发多线程模型的web应用,单一长连接如何解决多线程并发请求问题呢?
其实如果不太了解socket或者多线程编程的相关知识,不太容易理解这个问题。传统的最简单的RPC方式,应该是为每次远程调用请求创建一个对应的线程,我们先不说这种方式的缺点。至少优点很明显,就是简单。简单体现在哪儿?
通信双方一对一(相比NIO来说)。
通俗点来说,socket通信的双方发送和接受数据不会被其它(线程)干扰,这种干扰不同于数数据包的“粘包问题”。其实说白了就相当于电话线路的场景:
试想一下如果多个人同时对着同一个话筒大喊,对方接受到的声音就会是重叠且杂乱的。
对于单一的socket通道来说,如果发送方多线程的话,不加控制就会导致通道中的数据乱七八糟,接收端无法区分数据的单位,也就无法正确的处理请求。
乍一看,似乎dubbo协议所说的单一长连接与客户端多线程并发请求之间,是水火不容的。但其实稍加设计,就可以让它们和谐相处。
socket中的粘包问题是怎么解决的?用的最多的其实是定义一个定长的数据包头,其中包含了完整数据包的长度,以此来完成服务器端拆包工作。
那么解决多线程使用单一长连接并发请求时包干扰的方法也有点雷同,就是给包头中添加一个标识id,服务器端响应请求时也要携带这个id,供客户端多线程领取对应的响应数据提供线索。
其实如果不考虑性能的话,dubbo完全也可以为每个客户端线程创建一个对应的服务器端线程,但这是海量高并发场景所不能接受的~~
那么脑补一张图:

下面咱们试图从代码中找到痕迹。
一路追踪,我们来到这个类:com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeChannel.java,先来看看其中的request方法,大概在第101行左右:
public ResponseFuture request(Object request, int timeout) throws RemotingException {
if (closed) {
throw new RemotingException(this.getLocalAddress(), null, "Failed to send request " + request + ", cause: The channel " + this + " is closed!");
}
// create request.
Request req = new Request();
req.setVersion("2.0.0");
req.setTwoWay(true);
req.setData(request);
//这个future就是前面我们提到的:客户端并发请求线程阻塞的对象
DefaultFuture future = new DefaultFuture(channel, req, timeout);
try{
channel.send(req); //非阻塞调用
}catch (RemotingException e) {
future.cancel();
throw e;
}
return future;
}
注意这个方法返回的ResponseFuture对象,当前处理客户端请求的线程在经过一系列调用后,会拿到ResponseFuture对象,最终该线程会阻塞在这个对象的下面这个方法调用上,如下:
public Object get(int timeout) throws RemotingException {
if (timeout <= 0) {
timeout = Constants.DEFAULT_TIMEOUT;
}
if (! isDone()) {
long start = System.currentTimeMillis();
lock.lock();
try {
while (! isDone()) { //无限连
done.await(timeout, TimeUnit.MILLISECONDS);
if (isDone() || System.currentTimeMillis() - start > timeout) {
break;
}
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
if (! isDone()) {
throw new TimeoutException(sent > 0, channel, getTimeoutMessage(false));
}
}
return returnFromResponse();
}
上面我已经看到请求线程已经阻塞,那么又是如何被唤醒的呢?再看一下com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.java,其实所有实现了ChannelHandler接口的类都被设计为装饰器模式,所以你可以看到类似这样的代码:
protected ChannelHandler wrapInternal(ChannelHandler handler, URL url) {
return new MultiMessageHandler(
new HeartbeatHandler(
ExtensionLoader.getExtensionLoader(Dispather.class).getAdaptiveExtension().dispath(handler, url)
));
}
现在来仔细看一下HeaderExchangeHandler类的定义,先看一下它定义的received方法,下面是代码片段:
public void received(Channel channel, Object message) throws RemotingException {
channel.setAttribute(KEY_READ_TIMESTAMP, System.currentTimeMillis());
ExchangeChannel exchangeChannel = HeaderExchangeChannel.getOrAddChannel(channel);
try {
if (message instanceof Request) {
.....
} else if (message instanceof Response) {
//这里就是作为消费者的dubbo客户端在接收到响应后,触发通知对应等待线程的起点
handleResponse(channel, (Response) message);
} else if (message instanceof String) {
.....
} else {
handler.received(exchangeChannel, message);
}
} finally {
HeaderExchangeChannel.removeChannelIfDisconnected(channel);
}
}
我们主要看中间的那个条件分支,它是用来处理响应消息的,也就是说当dubbo客户端接收到来自服务端的响应后会执行到这个分支,它简单的调用了handleResponse方法,我们追过去看看:
static void handleResponse(Channel channel, Response response) throws RemotingException {
if (response != null && !response.isHeartbeat()) { //排除心跳类型的响应
DefaultFuture.received(channel, response);
}
}
熟悉的身影:DefaultFuture,它是实现了我们上面说的ResponseFuture接口类型,实际上细心的童鞋应该可以看到,上面request方法中其实实例化的就是这个DefaultFutrue对象:
DefaultFuture future = new DefaultFuture(channel, req, timeout);
那么我们可以继续来看一下DefaultFuture.received方法的实现细节:
public static void received(Channel channel, Response response) {
try {
DefaultFuture future = FUTURES.remove(response.getId());
if (future != null) {
future.doReceived(response);
} else {
logger.warn("The timeout response finally returned at "
+ (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()))
+ ", response " + response
+ (channel == null ? "" : ", channel: " + channel.getLocalAddress()
+ " -> " + channel.getRemoteAddress()));
}
} finally {
CHANNELS.remove(response.getId());
}
}
留一下我们之前提到的id的作用,这里可以看到它已经开始发挥作用了。通过id,DefaultFuture.FUTURES可以拿到具体的那个DefaultFuture对象,它就是上面我们提到的,阻塞请求线程的那个对象。好,找到目标后,调用它的doReceived方法,这就是标准的java多线程编程知识了:
private void doReceived(Response res) {
lock.lock();
try {
response = res;
if (done != null) {
done.signal();
}
} finally {
lock.unlock();
}
if (callback != null) {
invokeCallback(callback);
}
}
这样我们就可以证实上图中左边的绿色箭头所标注的两点。
接下来我们再来看看右边绿色箭头提到的两点是如何实现的?其实dubbo在NIO的实现上默认依赖的是netty,也就是说真正在长连接两端发包和接包的苦力是netty。由于哥们我对netty不是很熟悉,所以暂时我们就直接把netty当做黑箱,只需要知道它可以很好的完成NIO通信即可。
参考链接:https://blog.csdn.net/joeyon1985/article/details/51046548
dubbo协议下的单一长连接与多线程并发如何协同工作的更多相关文章
- NIO单一长连接——dubbo通信模型实现
转: NIO单一长连接——dubbo通信模型实现 峡客 1.2 2018.07.15 19:04* 字数 2552 阅读 6001评论 30喜欢 17 前言 前一段时间看了下dubbo,原想将dubb ...
- iOS应用中通过设置VOIP模式实现休眠状态下socket的长连接
如果你的应用程序需要在设备休眠的时候还能够收到服务器端发送的消息,那我们就可以借助VOIP的模式来实现这一需求.但是如果的应用程序并不是正真的VOIP应用,那当你把你的应用提交到AppStore的时候 ...
- [转] iOS应用中通过设置VOIP模式实现休眠状态下socket的长连接
转自:http://blog.csdn.net/missautumn/article/details/17102067 如果你的应用程序需要在设备休眠的时候还能够收到服务器端发送的消息,那我们就可 ...
- 网络协议-dubbo协议
Dubbo支持dubbo.rmi.hessian.http.webservice.thrift.redis等多种协议,但是Dubbo官网是推荐我们使用Dubbo协议的. 下面我们就针对Dubbo的每种 ...
- 基于dubbo框架下的RPC通讯协议性能测试
一.前言 Dubbo RPC服务框架支持丰富的传输协议.序列化方式等通讯相关的配置和扩展.dubbo执行一次RPC请求的过程大致如下:消费者(Consumer)向注册中心(Registry)执行RPC ...
- Dubbo协议与连接控制
协议参考手册 (+) (#) 推荐使用Dubbo协议 性能测试报告各协议的性能情况,请参见:性能测试报告 (+) dubbo:// (+) (#) Dubbo缺省协议采用单一长连接和NIO异步通讯,适 ...
- dubbo协议参考手册(转)
原文链接:http://wely.iteye.com/blog/2331332 协议参考手册 (+) (#) 推荐使用Dubbo协议 性能测试报告各协议的性能情况,请参见:性能测试报告 (+) dub ...
- Dubbo协议
参考dubbo官方文档http://dubbo.apache.org/zh-cn/docs/user/references/protocol/dubbo.html dubbo共支持如下几种通信协议: ...
- dubbo hessian+dubbo协议
Dubbo缺省协议采用单一长连接和NIO异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况 Hessian协议用于集成Hessian的服务,Hessian底层采 ...
随机推荐
- top-adx-apps
中国地区top adx和流量平台 12719 unity ios 12001 mopub ios 6599 unity android 5277 mobfox ios 3855 mopub andro ...
- rsync入门使用
rsync是用来同步文件的,但是是基于增量同步的,也就是说每次同步时不需要把全部文件数据都传输过去,只需要将不相同的部分(也就是说增量差异内容)传输过去. 其基本命令格式为rsync [option] ...
- linux配置防火墙
centos6.5 防火墙开放80端口 iptables -I INPUT -p tcp –dport 80 -j ACCEPT //注意,dport前面是两个-,其中-I是指在防火墙INPUT表最前 ...
- react-static 基于react 渐进式静态站点生成框架
react-static 是一个不错的基于react 开发的静态站点生成框架,可以用来替代create-react-app 包含的特性 100% react 很快的构建以及性能 自动代码以及数据分离 ...
- Kafka 文件存储机制那些事 - 美团技术团队
出处:https://tech.meituan.com/2015/01/13/kafka-fs-design-theory.html 自己总结: Kafka 文件存储机制_结构图:https://ww ...
- 利用js获取客户端ip的方法
1. 通过script标签引入url 比如如下代码: <script type="text/javascript" src="http://pv.sohu.com/ ...
- nginx下js文件修改后访问不更新问题解决
今天遇到一个问题,nginx下js修改后不更新,加版本号,刷新浏览器缓存都不行,重启服务器才行,修改后又不更新了而且加载的js文件会有乱码或者文件加载不全的问题. 解决办法:修改nginx.conf, ...
- Linux 下的三种时间介绍
Linux 下的三种时间介绍: Access Time:简写为atime,表示文件访问的时间,当文件内容被访问时,更新atime时间 Modify Time:简写为mtime,表示文件内容修改的时间, ...
- 运行Docker镜像
1. 检查本机Docker镜像 docker images 2. 下载hello-world镜像 docker pull hello-world 3. 运行docker镜像hello-world do ...
- WIN10 网速问题,边下载 边逛论坛 电脑 有点卡
引用:https://www.chiphell.com/forum.php?mod=viewthread&tid=1961836&extra=page%3D1&mobile=2 ...