从传统阻塞模型刚过度到Spring WebFlux非阻塞模型编程会有诸多不适应的地方。比如调用一个方法都是返回Mono或者Flux,然后需要不停的map,flatMap去处理返回的结果。在Spring MVC中可以很方便快捷处理的场景,在Spring WebFlux可能会麻烦一点。这个时候有些同学可能会想偷懒,用Reactor的boock()方法来快速简单处理了。然后就掉进陷阱了:
error:java.lang.IllegalStateException: block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-tcp-nio-5
刚接触Spring WebFlux的同学可能就懵逼了,不是提供了阻塞的方法吗?为什么使用报错了?
这是在Reactor v3.2.0.M2中更新的内容:
Blocking APIs (like
blockLast()
,block()
,iterator()
) called inside a parallel or singleScheduler
trigger an exception (#1102)
- This kind of blocking call are harmful as they impact limited resources, with a high risk of freezing the application
在并行或单个的Scheduler中调用阻塞的方法就会抛出异常了。原因在更新日志中也写明了:阻塞会影响线程池中有限的资源,而且还有很大概率冻结程序的风险。
这里的Scheduler是Reactor的reactor.core.scheduler。如果使用Reactor的线程池应该都不会陌生,就是下面这些:
不要以为不使用block方法就不会出现上面的错误了,block的陷阱可不止这些,还有同类型的错误:
IllegalStateException: Iterating over a toIterable() / toStream() is blocking
这个错误也是在返回响应式类型方法中调用阻塞方法引起的,不过这个比较隐晦,不像block等方法叫的这么明显。触发这个错误的原因是调用Flux的stream方法,这是一个阻塞的方法。如果是想对Flux中的元素进行遍历或者其他操作,可以使用使用Flux.collectList操作符,它返回一个Mono<List>类型。
问题找到了,我们在使用Spring webFlux的时候一定需要小心谨慎。不要为了一时的方便而导致整个系统性能受到影响。同时,我们也可以看到Spring WebFlux和Spring MVC的一些区别,在Spring MVC中处处都是阻塞,偶尔用一个异步线程池还可能提升一下性能(在阻塞模型中疯狂的建线程,线程的上下文切换也会导致性能下降)。在Sring WebFlux中处处都是异步,偶尔阻塞可能就导致系统冻结,瘫痪了。最后总结一下:
- 不应该在返回反应式类型(Mono,Flux)的方法中调用block方法。这样会阻塞应用中本就稀少的线程,会对程序有很大的影响。
- 在这种场景中使用subscribe订阅也是一个不好的方式,因为这样处理也或多或少是在一个单独的线程中开始一个新的任务。
- 使用block()方法的场景只有在本身方法就是一个阻塞的方法时才可以使用。也就是在阻塞方法中调用响应式方法,这个时候需要使用block()。比如进行单元测试。
感谢阅读,希望对你有所帮助。
参考资料
https://github.com/reactor/reactor-core/releases/tag/v3.2.0.M2