WebFlux 默认有几个线程?在控制器里 Thread.sleep 或查 JDBC 会怎样?block() 为什么会报错?subscribeOn(boundedElastic) 能解决什么?为什么 WebFlux 用很少的线程就能扛很多并发?这篇在 Spring Boot 4.0.8 的 WebFlux 上把 event loop 线程数调到 2,用真实请求逐个验证。
100 个请求(每个新连接)分布在 10 个线程上:{'reactor-http-nio-1': 10, 'reactor-http-nio-10': 10, 'reactor-http-nio-2': 10, 'reactor-http-nio-3': 10, 'reactor-http-nio-4': 10, 'reactor-http-nio-5': 10, 'reactor-http-nio-6': 10, 'reactor-http-nio-7': 10, 'reactor-http-nio-8': 10, 'reactor-http-nio-9': 10}event loop 线程数等于核心数,连接轮流分配。
reactor.netty.ioWorkerCount=2:40 个请求分布在 2 个线程上:{'reactor-http-nio-1': 20, 'reactor-http-nio-2': 20}设成 2 让阻塞的后果可见。
-- 在 Mono.fromCallable 里 Thread.sleep(500)(没有切线程) 4 个并发请求的耗时(ms):[611, 611, 1120, 1125];响应里的线程:['blocked-500@reactor-http-nio-1', 'blocked-500@reactor-http-nio-2'] 期间每 40 ms 发一个 /fast(新连接)共 12 个:最慢 1022 ms,超过 100 ms 的有 1 个;耗时序列 [1022, 2, 2, 2, 5, 8, 4, 10, 6, 6, 7, 6] -- 控制器方法直接返回 String,里面 Thread.sleep(500) 4 个并发请求的耗时(ms):[585, 585, 1096, 1099];响应里的线程:['direct-500@reactor-http-nio-1', 'direct-500@reactor-http-nio-2'] 期间每 40 ms 发一个 /fast(新连接)共 13 个:最慢 993 ms,超过 100 ms 的有 1 个;耗时序列 [993, 7, 8, 5, 2, 1, 2, 2, 2, 2, 2, 2, 7] -- Thread.sleep(500) 放到 subscribeOn(boundedElastic) 4 个并发请求的耗时(ms):[595, 595, 595, 595];响应里的线程:['offload-500@boundedElastic-1', 'offload-500@boundedElastic-2', 'offload-500@boundedElastic-3', 'offload-500@boundedElastic-4'] 期间每 40 ms 发一个 /fast(新连接)共 32 个:最慢 9 ms,超过 100 ms 的有 0 个;耗时序列 [8, 2, 2, 2, 6, 9, 6, 7, 5, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 3, 6, 6] -- Mono.delayElement(500 ms)(非阻塞等待) 4 个并发请求的耗时(ms):[579, 579, 579, 579];响应里的线程:['delayed'] 期间每 40 ms 发一个 /fast(新连接)共 32 个:最慢 8 ms,超过 100 ms 的有 0 个;耗时序列 [8, 2, 2, 2, 2, 8, 7, 4, 2, 2, 2, 8, 7, 6, 7, 7, 5, 5, 8, 8, 3, 1, 1, 2, 2]
event loop 上阻塞会拖慢所有请求;offload 到 boundedElastic 则不影响。
GET /call-block → 500 [IllegalStateException] block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-2 GET /call-block-offload → 200 x
Reactor 在非阻塞线程上直接拒绝 block()。
50 个并发的 /delay?ms=500:全部完成用了 622 ms;结果统计 {200: 50};成功的里最慢 617 ms
100 个并发的 /delay?ms=500:全部完成用了 552 ms;结果统计 {200: 100};成功的里最慢 526 ms
200 个并发的 /delay?ms=500:全部完成用了 557 ms;结果统计 {200: 200};成功的里最慢 533 ms等待不占线程,少量线程撑住大量并发。