我们从一个后端服务的抽象开始。一、线程池为什么成为瓶颈转发型网关服务先是一个中间服务它接收上游调用、然后调用下游服务工作将结果返回给上游。通常会有一个线程池处理上述这个任务任务的核心是网络调用、也就是网络io获得下游结果然后返回。这样有两个瓶颈调用下游服务是客户端需要一个连接池而接收处理上游请求任务需要一个线程池。如果这是一个SpringBoot那么上述请求客户端对应HttpClient线程池对应Tomcat线程池如果下游返回慢首先会长时间占用HttpClient连接池的连接这是第一个可能的瓶颈资源进而负责处理这个调用任务的线程也会阻塞那么Tomcat线程池就成为了第二个可能的资源瓶颈。二、非阻塞IO如何减少线程占用这比较好解决我们可以直接用WebClient来替换上述的HttpClient这是一个非阻塞io调用调用线程可以返回去干别的事情。可以验证一下就算我们把Tomcat线程池的线程数设置为1然后做两个接口分别去调用slow和fast两个下游服务那么在slow接口返回之前fast接口的请求可以先返回。类似如下先调用后返回 上游 - 本服务slow接口 - 下游slow服务 后调用先返回 上游 - 本服务fast接口 - 下游 fast服务那么既然走到这里了为了彻底消除 Tomcat 线程模型的心智负担我们索性直接用WebFlux替换spring-web也就是底层用Netty替换了Tomcat。这样彻底不用纠结所谓Tomcat线程池调优问题了不用拍脑袋去设置线程池的各种参数WebFlux底层的Netty会自动根据cpu核数*2来固定的reactor线程池大小———这也是这种线程模型下的最优解然后这些个reactor线程会非阻塞的接收请求以及处理它们的任务、即用WebClient调用下游服务。这样整个应用服务就都是非阻塞的了。这也是很多高性能网关的原理因为网关就是这样的转发型中间服务原理一样但未必用Java比如基于OpenResty的kong, Apache APISIX等。自己做任务的服务那么如果我们讨论的这个服务它不是转发型的而是自己要做一些工作呢我们抽象的归类大部分其实是操作某种数据库比如MySQL, 或者Redis。我们仔细想想其实这也是网络io的一种。我们接着我们的上面的优化后的服务模型现在reactor线程不是用WebClient调用下游http服务了而是需要用jdbc来调用MySQL。这时候问题来了jdbc是个同步阻塞Java API底层通过 MySQL Wire Protocol 与数据库通信意味着线程要等结果返回之后才能去干别的假设2核CPU那么有4个reactor线程这仅有的4个线程很快会被可能的慢sql任务占满整个服务就变得不可用了。于是有了r2dbc重新设计的异步响应式api 协议还是原来的没变线程在调用r2dbc 进行数据库操作的时候可以非阻塞向连接池申请连接、向数据库发送sql执行的网络请求、然后不用等sql执行完而可以立即返回去干其他事情。三、连接池为什么成为新的瓶颈线程模型讨论完了那连接呢到此是不是完美了呢我们上面讨论一直是围绕线程池但是连接池部分比如HttpClient , WebClient (以 HTTP/1.1 为例)、HikariCP这些。线程池我们通过reactor非阻塞线程模型实现了少量线程就可以完美工作但是在每个网络请求的时候要去连接池申请一个到对端的网络连接用完放回连接池而这个连接在被线程使用的时候是不能被别的线程使用的。这叫做独占式的连接复用连接池仍然会成为瓶颈。四、连接多路复用的原理而与之相对的是共享式的连接复用实现连接共享模式的连接池一般需要客户端与服务端在协议上做出约定使用类似RequestID这种、服务端在返回Response时带上这个ID用来识别这个是客户端的哪个请求的响应后面就可以根据这个ID去做对应的处理了比如回调对应的handle等等蚂蚁的Sofa Bolt以及阿里的Dubbo rpc框架就是基于的这样的原理 。或者还有一种情况例如基于Netty的Redis Lettuce默认以单连接多路复用方式工作它可以采用单连接共享模式的原因在于它对端的服务端是单线程处理的Redis 加之TCP协议本身的顺序性保证这样一来在同一条TCP连接上响应到达的顺序必然与请求发出的顺序一致所以就不需要上面所说的客户端与服务端之间约定的ID了。注Redis 6.0 之后引入了多线程 I/O网络读写由多线程处理但命令的执行仍然是单线程串行的。ok, 到此连接池的瓶颈的问题似乎也解决了。我们尽量用连接共享式的连接池。但是到底是设置多少连接呢多连接一定比单连接快 还是说这是跟“线程多一定比线程少要好”一样的愚蠢的问题。五、单连接与多连接之争先说结论在不存在链路争用、且在低延迟的数据中心内网环境下的情况下如果连接可以在多线程间共享且非阻塞单连接是和多连接一样快的。如果客户端与服务端之间出现了某个瓶颈节点比如路由器交换机之类的网络中的公共节点为多个链路公用。由于TCP的拥塞控制机制经过该公共设备的连接的实际速率会趋于平均化公平分配所以会出现某个客户端没有充分利用带宽的情况这样如果在客户端上再开几个连接由于平均分配机制那么就会提高该客户端在公共节点的流量占比从客户端角度看过来就是传输速率变快了。其实可以认为是从其他客户端那儿抢过来的一些公共节点的带宽使用。如图客户端1和客户端2如果都是单连接的话在路由器会被均分带宽每个是R/2假设如果客户端带宽也是R的话那么从客户端的角度来看就没有充分利用带宽这时候客户端1开两个连接那么其在路由器相当于会分到2R/3带宽从其角度来看两连接就比原来的单连接要快了。而在高延迟网络中即使没有任何链路争用单条 TCP 连接也可能因为拥塞窗口cwnd或接收窗口rwnd的大小限制无法打满可用带宽。带宽时延积BDP 带宽 × RTT决定了管道里能飞多少数据单连接的窗口如果没有调大在跨地域高延迟链路上即使独占带宽也跑不满。这时候开多连接确实更快且不是抢别人的带宽而是自己没充分利用。比如跨机房或公网场景这时候为了充分利用带宽确实是可以适当开多连接。最后跟风贩卖一下焦虑AI时代来临 2027程序员灭绝倒数的时间点清理旧电脑时翻出以前的笔记整理一下发出来祭奠一下我的开发时代和年轻的日子。