分布式系统:dubbo的连接机制
研究这个问题的起因
起因是一次面试,一次面试某电商网站,前面问到缓存,分布式,业务这些,还相谈甚欢。然后面试官突然甩出一句:“了解dubbo吗?dubbo是长连接还是短连接?”。当时我主要接触了解学习的还是spring cloud,dubbo作为知名的分布式rpc框架,只是有一定了解,并且连接这一块并没有很深入去了解,但是基于对分布式系统的了解,我不假思索的回答了:“长连接啊!”。其实分布式系统接触多了就知道了,分布式系统为了应对高并发,减少在高并发时的线程上下文切换损失以及重新建立连接的损失,往往都是采用长连接的。所以我当时我是这么想的:“dubbo作为处理小数据高并发分布式RPC框架,如果采用短连接,应该不可能达到那么高的吞吐吧。”。所以果断回答了长连接。可是没想到面试官微微一笑,带着几分不屑的说道:“短连接”。当时就给我整懵逼了,无法想象短连接如何处理高并发下重复建立连接以及线程上下文切换的问题。导致我回家的地铁上一直都处在怀疑人生的状态,回家后立马各种百度Google(甚至还怀疑查到的结果)。
dubbo的连接机制
这里直接上结论了,dubbo默认是使用单一长连接,即消费者与每个服务提供者建立一个单一长连接,即如果有消费者soa-user1,soa-user2,提供者soa-account三台,则每台消费者user都会与3台account建立一个连接,结果是每台消费者user有3个长连接,每台提供者account维持6个长连接。
为什么这么做
dubbo这么设计的原因是,一般情况下因为消费者是在请求链路的上游,消费者承担的连接数以及并发量都是最高的,他需要承担更多其他的连接请求,而对提供者来说,承担的连接只来于消费者,所以每台提供者只需要承接消费者数量的连接就可以了,dubbo面向的就是消费者数量远大于服务提供者的情况。所以说,现在很多项目使用的都是消费者和提供者不分的情况,这种情况并没有很好的利用这个机制。
dubbo同步转异步
dubbo的底层是使用netty,netty之前介绍过是非阻塞的,但是dubbo调用我们大多数时候都是使用的同步调用,那么这里是怎么异步转同步的呢?这里其实延伸下,不只是dubbo,大多数在web场景下,还是同步请求为主,那么netty中要如何将异步转同步?我这边描述一下关键步骤。
dubbo的实现
//DubboInvoker
protected Result doInvoke(final Invocation invocation) throws Throwable {
//...
if (isOneway) {//2.异步没返回值
boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
currentClient.send(inv, isSent);
RpcContext.getContext().setFuture(null);
return new RpcResult();
} else if (isAsync) {
//1.异步有返回值,异步的直接返回带future的result就完事了
ResponseFuture future = currentClient.request(inv, timeout);
FutureAdapter<Object> futureAdapter = new FutureAdapter<>(future);
RpcContext.getContext().setFuture(futureAdapter);
Result result;
if (isAsyncFuture) {
result = new AsyncRpcResult(futureAdapter, futureAdapter.getResultFuture(), false);
} else {
result = new SimpleAsyncRpcResult(futureAdapter, futureAdapter.getResultFuture(), false);
}
return result;
} else {//3.异步变同步,这里是同步的返回,主要阻塞的原因在于.get(),实际上就是HeaderExchangeChannel里返回的DefaultFuture的.get()方法
RpcContext.getContext().setFuture(null);
return (Result) currentClient.request(inv, timeout)//返回下面的future
.get();//进入get()方法,是当前线程阻塞。那么当有结果返回时,唤醒这个线程
}
}
//--HeaderExchangeChannel
public ResponseFuture request(Object request, int timeout) throws RemotingException {
Request req = new Request();
req.setVersion(Version.getProtocolVersion());
req.setTwoWay(true);
req.setData(request);
//这里在发送前,构建自定义的future,用来让调用线程等待,注意这里的future和netty的channelFuture不同。
DefaultFuture future = DefaultFuture.newFuture(channel, req, timeout);
try {
channel.send(req);//使用实际的channel,里面封装了netty的channel,最后是调用到了nettyChannel的send的。
} catch (RemotingException e) {
future.cancel();
throw e;
}
return future;
}
//--DefaultFuture--实现阻塞调用线程的逻辑,接收到结果
//阻塞的逻辑
public Object get(int timeout) throws RemotingException {
if (timeout <= 0) {
timeout = 1000;
}
//done是自身对象的一个可重入锁成员变量的一个condition,这里的逻辑就是:
//如果获取到了锁,并且条件不满足,则await线程等到下面receive方法唤醒。
//其实我想吐槽下,这个condition命名为done,又有一个方法叫isDone,但是isDone又是判断response!=null的和done没有任何关系,这个命名不是很科学。
if (!this.isDone()) {
long start = System.currentTimeMillis();
this.lock.lock();
try {
while(!this.isDone()) {
this.done.await((long)timeout, TimeUnit.MILLISECONDS);
if (this.isDone() || System.currentTimeMillis() - start > (long)timeout) {
break;
}
}
} catch (InterruptedException var8) {
throw new RuntimeException(var8);
} finally {
this.lock.unlock();
}
if (!this.isDone()) {
throw new TimeoutException(this.sent > 0L, this.channel, this.getTimeoutMessage(false));
}
}
return this.returnFromResponse();
}
//收到结果时唤醒的逻辑
public static void received(Channel channel, Response response) {
try {
DefaultFuture future = FUTURES.remove(response.getId());
if (future != null) {
future.doReceived(response);
} else {
}
} finally {
CHANNELS.remove(response.getId());
}
}
private void doReceived(Response res) {
lock.lock();
try {
response = res;//拿到了响应
if (done != null) {
done.signal();//唤醒线程
}
} finally {
lock.unlock();
}
if (callback != null) {
invokeCallback(callback);
}
}
//那么我们知道DefaultFuture被调用received方法时会被唤醒,那么是什么时候被调用的呢?
//--HeaderExchangeHandler-- netty中处理的流就是handler流,之前有篇文章讲到过,这里也是在handler中给处理的,其实上面还有ExchangeHandlerDispatcher这类dispatcher预处理,将返回
//分给具体的channelHandler处理,但是结果到了这里
public class HeaderExchangeHandler implements ChannelHandlerDelegate {
public void received(Channel channel, Object message) throws RemotingException {
channel.setAttribute(KEY_READ_TIMESTAMP, System.currentTimeMillis());
HeaderExchangeChannel exchangeChannel = HeaderExchangeChannel.getOrAddChannel(channel);
try {
if (message instanceof Request) {
Request request = (Request)message;
if (request.isEvent()) {
this.handlerEvent(channel, request);
} else if (request.isTwoWay()) {
Response response = this.handleRequest(exchangeChannel, request);
channel.send(response);
} else {
this.handler.received(exchangeChannel, request.getData());
}
} else if (message instanceof Response) {
//主要是这里
handleResponse(channel, (Response)message);
} else if (message instanceof String) {
if (isClientSide(channel)) {
Exception e = new Exception("Dubbo client can not supported string message: " + message + " in channel: " + channel + ", url: " + channel.getUrl());
logger.error(e.getMessage(), e);
} else {
String echo = this.handler.telnet(channel, (String)message);
if (echo != null && echo.length() > 0) {
channel.send(echo);
}
}
} else {
this.handler.received(exchangeChannel, message);
}
} finally {
HeaderExchangeChannel.removeChannelIfDisconnected(channel);
}
}
//handleResponse,到这里直接调用静态方法,回到了上面接受结果那步。
static void handleResponse(Channel channel, Response response) throws RemotingException {
if (response != null && !response.isHeartbeat()) {
DefaultFuture.received(channel, response);
}
}
}
纯netty的简单实现
纯netty的简单实现,其实也很简单,在创建handler时,构造时将外部的FutureTask对象构造到hanlder中,外面使用FutureTask对象get方法阻塞,handler中在最后有结果时,将FutureTask的结果set一下,外部就取消了阻塞。
public SettableTask<String> sendAndSync(FullHttpRequest httpContent){
//创建一个futureStask
final SettableTask<String> responseFuture = new SettableTask<>();
ChannelFutureListener connectionListener = future -> {
if (future.isSuccess()) {
Channel channel = future.channel();
//创建一个listener,在连接后将新的futureTask构造到一个handler中
channel.pipeline().addLast(new SpidersResultHandler(responseFuture));
} else {
responseFuture.setExceptionResult(future.cause());
}
};
try {
Channel channel = channelPool.acquire().syncUninterruptibly().getNow();
log.info("channel status:{}",channel.isActive());
channel.writeAndFlush(httpContent).addListener(connectionListener);
} catch (Exception e) {
log.error("netty写入异常!", e);
}
return responseFuture;
}
//重写一个可以手动set的futureTask
public class SettableTask<T> extends FutureTask<T> {
public SettableTask() {
super(() -> {
throw new IllegalStateException("Should never be called");
});
}
public void setResultValue(T value) {
this.set(value);
}
public void setExceptionResult(Throwable exception) {
this.setException(exception);
}
@Override
protected void done() {
super.done();
}
}
//resultHandler
public class SpidersResultHandler extends SimpleChannelInboundHandler<String> {
private SettableTask<String> future;
public SpidersResultHandler(SettableTask<String> future){
this.future=future;
}
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String httpContent) throws Exception {
log.info("result={}",httpContent);
future.setResultValue(httpContent);
}
@Override
public void exceptionCaught(ChannelHandlerContext channelHandlerContext, Throwable throwable) throws Exception {
log.error("{}异常", this.getClass().getName(), throwable);
}
}
总结
dubbo的高性能,也源于他对每个点不断的优化,最早的时候我记得看到一篇文章写到:dubbo的异步转同步机制,是使用的CountDownLatch实现的。现在想来,可能是在乱说。一些框架的原理,还是要自己多思考多翻看,才能掌握。
分布式系统:dubbo的连接机制的更多相关文章
- Dubbo入门到精通学习笔记(七):基于Dubbo的分布式系统架构介绍(以第三方支付系统架构为例)、消息中间件的作用介绍
文章目录 架构简单介绍 消息中间件在分布式系统中的作用介绍 消息中间件的定义 消息中间件的作用 应用场景 JMS(Java Message Service) JMS消息模型 实现了JMS规范的消息中间 ...
- 基于SLF4J的MDC机制和Dubbo的Filter机制,实现分布式系统的日志全链路追踪
原文链接:基于SLF4J的MDC机制和Dubbo的Filter机制,实现分布式系统的日志全链路追踪 一.日志系统 1.日志框架 在每个系统应用中,我们都会使用日志系统,主要是为了记录必要的信息和方便排 ...
- 分布式系统中的幂等性-zookeeper与dubbo
现如今我们的系统大多拆分为分布式SOA,或者微服务,一套系统中包含了多个子系统服务,而一个子系统服务往往会去调用另一个服务,而服务调用服务无非就是使用RPC通信或者restful,既然是通信,那么就有 ...
- 基于dubbo的分布式系统(一)安装docker
1.安装过程参考: #uname -r 查看内核是否高于3.10 #sudo yum update root权限登陆确保yum包最新 #sudo yum remove docker docker-c ...
- 整理下.net分布式系统架构的思路
最近看到有部分招聘信息,要求应聘者说一下分布式系统架构的思路.今天早晨正好有些时间,我也把我们实际在.net方面网站架构的演化路线整理一下,只是我自己的一些想法,欢迎大家批评指正. 首先说明的是.ne ...
- 基于dubbo的分布式项目实例应用
本文主要学习dubbo服务的启动检查.集群容错.服务均衡.线程模型.直连提供者.只定阅.只注册等知识点,希望通过实例演示进一步理解和掌握这些知识点. 启动检查 Dubbo缺省会在启动消费者时检查依赖的 ...
- [资料分享]dubbo视频教程流行版
一.基础篇 第001节–课程介绍 第01节–使用Dubbo对传统工程进行服务化改造的思路介绍 第02节–使用Dubbo对传统工程进行服务化改造 第03节–ZooKeeper注册中心安装 第04节–使用 ...
- Dubbo初探
Dubbo是什么? 1.阿里巴巴开源项目.2.Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案. ps: SOA(面相服务的体系结构) RPC( ...
- net分布式系统架构
net分布式系统架构的思路 最近看到有部分招聘信息,要求应聘者说一下分布式系统架构的思路.今天早晨正好有些时间,我也把我们实际在.net方面网站架构的演化路线整理一下,只是我自己的一些想法,欢迎大家批 ...
随机推荐
- pyhon 自动化 logger
#!/Users/windows8.1/PycharmProjects/pythonapi# @Software: PyCharm Community Edition# -*- coding: utf ...
- Docker(一):Docker安装
简介 Docker是dotcloud公司开源的一款产品,主要基于PAAS平台为开发者提供服务.是解决运行环境和配置问题软件容器,方便做持续集成并有助于整体发布的容器虚拟化技术. Docker组件 ...
- 【进阶之路】线程池拓展与CompletionService操作异步任务
大家好,我是练习java两年半时间的南橘,小伙伴可以一起互相交流经验哦. 一.扩展ThreadPoolExecutor 1.扩展方法介绍 ThreadPoolExecutor是可以扩展的,它内部提供了 ...
- 卷积涨点论文 | Asymmetric Convolution ACNet | ICCV | 2019
文章原创来自作者的微信公众号:[机器学习炼丹术].交流群氛围超好,我希望可以建议一个:当一个人遇到问题的时候,有这样一个平台可以快速讨论并解答,目前已经1群已经满员啦,2群欢迎你的到来哦.加入群唯一的 ...
- Java 面试知识点【背诵版 240题 约7w字】
-- 转载自牛客网 是瑶瑶公主吖 Java 基础 40 语言特性 12 Q1:Java 语言的优点? ① 平台无关性,摆脱硬件束缚,"一次编写,到处运行". ② 相对安全的内存管理 ...
- 5分钟看懂系列:Python 线程池原理及实现
概述 传统多线程方案会使用"即时创建, 即时销毁"的策略.尽管与创建进程相比,创建线程的时间已经大大的缩短,但是如果提交给线程的任务是执行时间较短,而且执行次数极其频繁,那么服务器 ...
- 中小学生的噩梦:怎样用Python检测抄袭行为?广大中小学生们的美梦就此结束
本教程将介绍如何使用机器学习技术(如word2vec和余弦相似度等),在Python中用几行代码制作抄袭检测器.搭建完成后,抄袭检测器将会从文件中载入学生们的作业,然后通过计算相似度来判断学生有无相互 ...
- Python 带你高效创作短视频,视频创作秀到飞起!!!
近两年,抖音.快手将短视频推到风口浪尖上,要生产出高质量的视频,离不开视频剪辑这一环节:在全民剪片浪潮中,大众使用最多的剪辑软件如:Pr.FCPX.剪印.Vue 等. 视频剪辑过程中,Python 一 ...
- 精尽Spring MVC源码分析 - ViewResolver 组件
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 微软自动化测试工具palywright
前言 我们介绍许多e2e的自动化测试工具 一类是基于 Selenium 的测试框架: robot framework gauge SeleniumBase seldom(我自己维护的) 另一类是基于J ...