某天下午,运营同学突然反馈,app点击各个按钮都无反应,当时的第一反应是可能是个别用户,app死机之类的问题,但是运营同学说不是,好多人在反馈同样的问题,我就赶紧拿自己手机里的app试了一下,确实某些页面点了以后无反应。然后赶紧登录k8s管理台,看一下微服务是不是down掉了,看了一下所有的微服务都是正常的没有容器down掉。再去看服务的日志,看看有没有内存溢出的情况,看了所有的微服务日志,没有内存溢出的异常打印。当初在想奇怪了,容器还在怎么不能提供服务了呢。 此时在想是不是线程堵塞了,用arthas排查了一遍确实是线程阻塞了。这时候想起来原来压测的时候,也遇到过这个问题。当时的问题是springboot里面的web服务器线程数配的太少,因为微服务循环依赖,导致了微服务死锁了。用一张图来展示:
图中,base是一个基础服务,给各个服务提供基础的一些功能,大部分的其他微服务都需要依赖base服务,由于历史原因,base服务中的某些特殊的接口需要依赖edu服务,这样服务和服务直接就形成了一个环,当此时流量突然增加的时候,就有可能造成base和edu服务之间的死锁,原因分两种,1、服务相互依赖,2、微服务的线程数不够。 我们当时用的是undertow作为web容器,undertow如果没有配置的话,默认的线程是计算规则如下:
I/O线程数
- 默认值:
Math.max(Runtime.getRuntime().availableProcessors(), 2) - 计算逻辑:CPU核心数和2之间取较大值
- 作用:处理网络I/O操作,负责接收请求和发送响应
工作线程数(Worker线程)
- 默认值:
I/O线程数 * 8 - 计算逻辑:I/O线程数乘以8
- 作用:处理具体的业务逻辑和请求处理
举例说明
假设你的服务器有4个CPU核心:
- I/O线程数 = max(4, 2) = 4
- 工作线程数 = 4 * 8 = 32
一般情况我们的docker都是配置的2ge或者4gcpu,工作线程数也就是在16或者32,当瞬时流量上来的时候,有可能会导致微服务之间的死锁,这个时候最简单的解决办法就是加大undertow的线程数:
server:
port: 0
shutdown: graceful
undertow:
threads:
worker: 256
再一个就是在微服务设计的时候,尽量避免微服务相互调用。
避免微服务相互调用的核心策略:
- 正确的领域建模 - DDD方法论
- 事件驱动架构 - 异步解耦
- 数据冗余策略 - 减少跨服务查询
- 单向依赖原则 - 清晰的依赖方向
- 聚合器模式 - 专门的组合服务
- 消息队列 - 异步通信中间件