惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
Vulnerabilities – Threatpost
T
The Blog of Author Tim Ferriss
MyScale Blog
MyScale Blog
月光博客
月光博客
Vercel News
Vercel News
Recorded Future
Recorded Future
I
InfoQ
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
大猫的无限游戏
大猫的无限游戏
博客园 - 叶小钗
人人都是产品经理
人人都是产品经理
M
MIT News - Artificial intelligence
罗磊的独立博客
Martin Fowler
Martin Fowler
D
DataBreaches.Net
Stack Overflow Blog
Stack Overflow Blog
云风的 BLOG
云风的 BLOG
有赞技术团队
有赞技术团队
阮一峰的网络日志
阮一峰的网络日志
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 聂微东
爱范儿
爱范儿
The Cloudflare Blog
F
Fortinet All Blogs
Engineering at Meta
Engineering at Meta
博客园 - 【当耐特】
WordPress大学
WordPress大学
C
Check Point Blog
小众软件
小众软件
L
LangChain Blog
V
V2EX
博客园 - 司徒正美
C
CERT Recently Published Vulnerability Notes
C
Cyber Attacks, Cyber Crime and Cyber Security
N
News | PayPal Newsroom
美团技术团队
C
Cybersecurity and Infrastructure Security Agency CISA
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
W
WeLiveSecurity
Spread Privacy
Spread Privacy
T
Threat Research - Cisco Blogs
L
Lohrmann on Cybersecurity
Security Latest
Security Latest
O
OpenAI News
G
GRAHAM CLULEY
P
Palo Alto Networks Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
T
Tailwind CSS Blog
Cisco Talos Blog
Cisco Talos Blog
Microsoft Azure Blog
Microsoft Azure Blog

王欣的博客

第6章:工具篇——使用 RunningHub(ComfyUI)生成短剧 - 王欣说AI|信息安全|AIGC|AI 第5章:工具篇——使用小云雀Agent生成短剧 - 王欣说AI|信息安全|AIGC|AI 第4篇:导演思维 + AI素材生成全流程 - 怎么用AI从0做出短剧/漫剧? - 王欣说AI|信息安全|AIGC|AI 马斯克出手!SpaceX 豪掷 600 亿美元锁定 Cursor,AI 编程江湖巨变 - 王欣说AI|信息安全|AIGC|AI 第3篇:编剧篇——一切从一个好故事开始 - 怎么用AI从0做出短剧/漫剧? - 王欣说AI|信息安全|AIGC|AI 第2篇:四项基本功——提示词、镜头语言、剪映、投流 - 怎么用AI从0做出短剧/漫剧? - 王欣说AI|信息安全|AIGC|AI 第1篇:为什么2026年是一个人拍短剧的最佳时机?- 怎么用AI从0做出短剧/漫剧? - 王欣说AI|信息安全|AIGC|AI 2026 AI 开发新范式:Harness Engineering(驾驭工程)为何是智能体的决胜点? - 王欣说AI|信息安全|AIGC|AI 通过Claude Code的源码分析其技术架构 - 王欣说AI|信息安全|AIGC|AI 没团队、没演员、没经验——我一个写代码的,用AI独自做完了短剧《顶级温差》 - 王欣说AI|信息安全|AIGC|AI 谁能替你操作电脑?OpenClaw vs Claude Code vs Codex 终极拆解 - 王欣说AI|信息安全|AIGC|AI OpenClaw macOS安装:飞书机器人一键生成小说+分镜 - 王欣说AI|信息安全|AIGC|AI AI公司亲自下场研究 Macos上跑Docker Desktop遇到registry-1.docker.io网络不通问题填坑笔记 - 王欣说AI|信息安全|AIGC|AI Macos上跑Docker Desktop遇到registry-1.docker.io网络不通问题填坑笔记 - 王欣的博客|微服务|RPC|信息安全|AIGC RocketMQ的死信队列你了解多少?怎么实现的? - 王欣说AI|信息安全|AIGC|AI RocketMQ的死信队列你了解多少?怎么实现的? - 王欣的博客|微服务|RPC|信息安全|AIGC Dubbo服务间的连接是怎么控制处理的? - 王欣的博客|微服务|RPC|信息安全|AIGC Seata 基本原理和源码分析 - 王欣说AI|微服务|RPC|信息安全|AIGC 分布式事务一致性算法Paxos、raft、zab等的比较 - 王欣说AI|微服务|RPC|信息安全|AIGC 谈谈各种流程引擎、规则引擎 - 王欣说AI|微服务|RPC|信息安全|AIGC 从netty组件的传输讲讲io - 王欣说AI|微服务|RPC|信息安全|AIGC JVM内存结构的历史 (从Jdk1.6、1.7、8) - 王欣说AI|信息安全|AIGC|AI 怎么定位线上的内存溢出? - 王欣说AI|微服务|RPC|信息安全|AIGC MQ组件盘点,哪些你用在了生产中? - 王欣说AI|信息安全|AIGC|AI JVM内存结构的历史 (从Jdk1.6、1.7、8) - 王欣的博客|微服务|RPC|信息安全|AIGC 怎么定位线上的内存溢出? - 王欣的博客|微服务|RPC|信息安全|AIGC Seata 基本原理和源码分析 - 王欣的博客|微服务|RPC|信息安全|AIGC 分布式事务一致性算法Paxos、raft、zab等的比较 - 王欣的博客|微服务|RPC|信息安全|AIGC 谈谈各种流程引擎、规则引擎 - 王欣的博客|微服务|RPC|信息安全|AIGC 从netty组件的传输讲讲io - 王欣的博客|微服务|RPC|信息安全|AIGC MQ组件盘点,哪些你用在了生产中? - 王欣的博客|微服务|RPC|信息安全|AIGC 一些有用的github配置 - 王欣说AI|信息安全|AIGC|AI 一些有用的github配置 - 王欣的博客|微服务|RPC|信息安全|AIGC 我看《subscribed》 - 王欣说AI|信息安全|AIGC|AI 我看《subscribed》 - 王欣的博客|微服务|RPC|信息安全|AIGC 为什么Redis读写快? - 王欣说AI|微服务|RPC|信息安全|AIGC 为什么Redis读写快? - 王欣的博客|微服务|RPC|信息安全|AIGC Dubbo代码分析—超时和重试设计 - 王欣说AI|微服务|RPC|信息安全|AIGC Dubbo代码分析—超时和重试设计 - 王欣的博客|微服务|RPC|信息安全|AIGC 【译】apache 基金会角色列表 - 王欣说AI|微服务|RPC|信息安全|AIGC 【译】apache 基金会角色列表 - 王欣的博客|微服务|RPC|信息安全|AIGC Dubbo代码分析---服务端线程池 - 王欣说AI|微服务|RPC|信息安全|AIGC rpc跟踪日志常用shell统计脚本 - 王欣说AI|微服务|RPC|信息安全|AIGC Dubbo代码分析---服务端线程池 - 王欣的博客|微服务|RPC|信息安全|AIGC rpc跟踪日志常用shell统计脚本 - 王欣的博客|微服务|RPC|信息安全|AIGC 并发 - 谈谈HashMap和ConcurrentHashMap - 王欣说AI|微服务|RPC|信息安全|AIGC 并发 - 谈谈HashMap和ConcurrentHashMap - 王欣的博客|微服务|RPC|信息安全|AIGC JVM基本结构 - 王欣说AI|微服务|RPC|信息安全|AIGC JVM基本结构 - 王欣的博客|微服务|RPC|信息安全|AIGC 血的教训:线程池定义一定要全局化,共享使用 - 王欣说AI|微服务|RPC|信息安全|AIGC 谈谈并发包的工具都是用在什么地方,底层是怎么实现的? - 王欣说AI|信息安全|AIGC|AI 血的教训:线程池定义一定要全局化,共享使用 - 王欣的博客|微服务|RPC|信息安全|AIGC
Dubbo服务间的连接是怎么控制处理的? - 王欣说AI|信息安全|AIGC|AI
Xin Wang(王欣) · 2019-12-03 · via 王欣的博客

​ 最近被问到一个问题: 有依赖关系的两个dubbo服务。通过TCP进行连接时候,他们之间的连接是怎么控制的?怎么达到一个合理的数量?

​ 我们从一个例子开始吧:一个订单服务 OrderService,IP为192.168.0.110 连接了商品服务 ProductService, ip 为192.168.0.111 ,其中订单服务中的方法比较多,并且很多请求也刚好路由到192.168.0.111ProductService 服务。那问题就来了:110机器作为客户端是怎么控制连接数的?会不会无限量地和111机器进行TCP连接?

​ 我们先看一下Dubbo的官方文档对“连接控制”的说明文档 : http://dubbo.apache.org/zh-cn/docs/user/demos/config-connections.html

​ 在xml配置方式中xml accepts="10"connections="10" 分别在服务端和客户端进行了相应的连接控制。下面我们看一下源码,追一下连接控制的原理。

​ 我们看一下DubboProtocol.java的创建客户端tcp连接的方法,int connectNum正是每个客户端对服务端的tcp连接数,默认是1,当然可以修改成更大。默认是1,这样一个客户端的调用service数最多也不会超过1000吧。这样就不会出现单机创建TCP连接数过多的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15







private List<ReferenceCountExchangeClient> buildReferenceCountExchangeClientList(URL url, int connectNum) {
List<ReferenceCountExchangeClient> clients = new ArrayList<>();

for (int i = 0; i < connectNum; i++) {
clients.add(buildReferenceCountExchangeClient(url));
}
return clients;
}

客户端和服务端是一对一的,建立长连接,那么如果客户端并发访问,他们是怎么和服务端交互的呢?
经过看代码:

下面咱们试图从代码中找到痕迹。一路追踪,我们来到这个类:com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeChannel.java,先来看看其中的request方法,大概在第101行左右:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  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!");
}

Request req = new Request();
req.setVersion("2.0.0");
req.setTwoWay(true);
req.setData(request);


DefaultFuture future = new DefaultFuture(channel, req, timeout);
try{
channel.send(req);
}catch (RemotingException e) {
future.cancel();
throw e;
}
return future;
}

注意这个方法返回的ResponseFuture对象,当前处理客户端请求的线程在经过一系列调用后,会拿到ResponseFuture对象,最终该线程会阻塞在这个对象的下面这个方法调用上,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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接口的类都被设计为装饰器模式,所以你可以看到类似这样的代码:

1
2
3
4
5
6
  protected ChannelHandler wrapInternal(ChannelHandler handler, URL url) {
return new MultiMessageHandler(
new HeartbeatHandler(
ExtensionLoader.getExtensionLoader(Dispather.class).getAdaptiveExtension().dispath(handler, url)
));
}

现在来仔细看一下HeaderExchangeHandler类的定义,先看一下它定义的received方法,下面是代码片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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) {

handleResponse(channel, (Response) message);
} else if (message instanceof String) {
.....
} else {
handler.received(exchangeChannel, message);
}
} finally {
HeaderExchangeChannel.removeChannelIfDisconnected(channel);
}
}

我们主要看中间的那个条件分支,它是用来处理响应消息的,也就是说当dubbo客户端接收到来自服务端的响应后会执行到这个分支,它简单的调用了handleResponse方法,我们追过去看看:

1
2
3
4
5
static void handleResponse(Channel channel, Response response) throws RemotingException {
if (response != null && !response.isHeartbeat()) {
DefaultFuture.received(channel, response);
}
}

熟悉的身影:DefaultFuture,它是实现了我们上面说的ResponseFuture接口类型,实际上细心的童鞋应该可以看到,上面request方法中其实实例化的就是这个DefaultFutrue对象:

1
DefaultFuture future = new DefaultFuture(channel, req, timeout);

那么我们可以继续来看一下DefaultFuture.received方法的实现细节:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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的作用,这里可以看到它已经开始发挥作用了。通过idDefaultFuture.FUTURES可以拿到具体的那个DefaultFuture对象,它就是上面我们提到的,阻塞请求线程的那个对象。好,找到目标后,调用它的doReceived方法,这就是标准的java多线程编程知识了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void doReceived(Response res) {
lock.lock();
try {
response = res;
if (done != null) {
done.signal();
}
} finally {
lock.unlock();
}
if (callback != null) {
invokeCallback(callback);
}
}

这样我们就可以证实上图中左边的绿色箭头所标注的两点。

参考链接:https://blog.csdn.net/joeyon1985/article/details/51046548