一个依赖变慢,怎么拖垮整个服务?线程池隔离和信号量隔离各怎么做?Java 线程池满了该选哪种拒绝策略?网关上又怎么防止慢上游把连接占满?这篇用本机的小服务、Java 和 nginx 量出来。
做法 | A 类请求数 | A 类 1 s 内成功 | A 类成功的中位数 | A 类 p99 | B 类请求数 | B 类 1 s 内成功 | B 类被立刻拒绝 | B 类超时 ----------------+--------+-------------+-----------+---------+--------+-------------+----------+------ 共享线程池 | 350 | 0.0% | - | - | 150 | 0.0% | 0 | 150 信号量舱壁(D2 ≤ 5 个) | 350 | 100.0% | 15 ms | 17 ms | 150 | 0.0% | 140 | 10 独立线程池(14 + 6) | 350 | 100.0% | 14 ms | 17 ms | 150 | 0.0% | 138 | 12 (连接类错误 0 次;不是超时,应为 0)
一个依赖变慢会占满共享的线程,没有问题的请求也饿死;给每个依赖划定上限后,慢的只影响自己。
策略 | 执行了的任务 | 抛出 RejectedExecutionException | 悄悄丢掉的 | 其中在提交者(main)线程里执行 | 提交完 20 个任务用了 | 执行了的任务编号 --------------------+--------+-------------------------------+-------+-------------------+--------------+----------------------------------------------------------------------- AbortPolicy | 8 | 12 | 0 | 0 | 0 ms | [0, 1, 2, 3, 4, 5, 6, 7] CallerRunsPolicy | 20 | 0 | 0 | 3 | 622 ms | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] DiscardPolicy | 8 | 0 | 12 | 0 | 0 ms | [0, 1, 2, 3, 4, 5, 6, 7] DiscardOldestPolicy | 8 | 0 | 12 | 0 | 1 ms | [0, 1, 6, 7, 16, 17, 18, 19] AbortPolicy 把拒绝变成异常交给调用方处理;CallerRunsPolicy 让提交者自己执行,所以提交会被拖慢(这是一种背压),但提交者线程也被占住;DiscardPolicy 悄悄丢最新的;DiscardOldestPolicy 悄悄丢队列里最老的(编号靠前的排队任务没有执行)
Abort 抛异常,CallerRuns 让提交者执行(背压),Discard 悄悄丢最新的,DiscardOldest 悄悄丢最老的。
配置 | /fast 探测次数 | /fast 成功 | 成功的中位数 | p99 | /fast 的失败类型(error = 连接被拒或重置) | /slow 被立刻 503 的次数 | error.log 里 worker_connections are not enough 的行数
---------------------+------------+----------+--------+--------+------------------------------+-------------------+--------------------------------------------------
不做隔离 | 47 | 81% | 1.5 ms | 2.5 ms | {'error': 9} | 0 | 632
/slow 加 limit_conn 6 | 47 | 100% | 1.1 ms | 4.9 ms | {} | 3281 | 0
limit_conn 的计数键用的是常量字符串 "slow":最初我用了 $server_name,这个 server 块没有写 server_name,变量值是空串,而 nginx 不统计键为空的请求,结果 limit_conn 悄悄没有任何效果(20 个并发请求全部 200)慢上游占满 worker_connections 时无关的路由也受影响;限制慢路由的并发可以隔离。limit_conn 的键不能是空串。