Spring WebFlux(Reactor3) block()/blockFirst()/blockLast()使用陷阱

从传统阻塞模型刚过度到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 single Scheduler 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的线程池应该都不会陌生,就是下面这些:

Spring WebFlux(Reactor3) block()/blockFirst()/blockLast()使用陷阱_第1张图片

 

不要以为不使用block方法就不会出现上面的错误了,block的陷阱可不止这些,还有同类型的错误:

IllegalStateException: Iterating over a toIterable() / toStream() is blocking

这个错误也是在返回响应式类型方法中调用阻塞方法引起的,不过这个比较隐晦,不像block等方法叫的这么明显。触发这个错误的原因是调用Flux的stream方法,这是一个阻塞的方法。如果是想对Flux中的元素进行遍历或者其他操作,可以使用使用Flux.collectList操作符,它返回一个Mono类型。

问题找到了,我们在使用Spring webFlux的时候一定需要小心谨慎。不要为了一时的方便而导致整个系统性能受到影响。同时,我们也可以看到Spring WebFlux和Spring MVC的一些区别,在Spring MVC中处处都是阻塞,偶尔用一个异步线程池还可能提升一下性能(在阻塞模型中疯狂的建线程,线程的上下文切换也会导致性能下降)。在Sring WebFlux中处处都是异步,偶尔阻塞可能就导致系统冻结,瘫痪了。最后总结一下:

  • 不应该在返回反应式类型(Mono,Flux)的方法中调用block方法。这样会阻塞应用中本就稀少的线程,会对程序有很大的影响。
  • 在这种场景中使用subscribe订阅也是一个不好的方式,因为这样处理也或多或少是在一个单独的线程中开始一个新的任务。
  • 使用block()方法的场景只有在本身方法就是一个阻塞的方法时才可以使用。也就是在阻塞方法中调用响应式方法,这个时候需要使用block()。比如进行单元测试。

感谢阅读,希望对你有所帮助。

参考资料

https://stackoverflow.com/questions/51449889/block-blockfirst-blocklast-are-blocking-error-when-calling-bodytomono-afte

https://github.com/reactor/reactor-core/releases/tag/v3.2.0.M2

 

你可能感兴趣的:(spring,java,多线程,spring)