课程咨询: 400-996-5531 / 投诉建议: 400-111-8989
认真做教育 专心促就业
我们在学习java编程开发的时候应该都了解过,尤其是在进行框架搭建的过程中,关于如何利用java语言的方法也有很多介绍。今天,我们就一起来了解一下关于反应式的一些知识内容。
改变的动机
现如今,“反应式”俨然已经成为一个热门词汇。不过,它真的是大势所趋,异步Web开发的发展经历了从早期简单的Servlet容器到现如今的异步无处不在。我们为此做好准备了吗?
传统的Java使用线程池来并行执行阻塞式的IO操作(比如进行远程调用)。表面上看,这样做很简单,但实际上可能不是这么回事。先,在进行同步或共享数据结构时,应用程序就会变得很复杂。其次,每个阻塞操作都会占用一个线程,以致于难以伸缩,而且这种等待所带来的延迟我们是无能为力的(这样会让客户端和服务器端都慢下来)。
因此,出现了各种解决方案(比如协程、actor等),把并发的复杂性归到框架或编程语言当中。现在出现了一个有关Java轻量级线程模型的提议,叫作Loom,但要看到它被应用到生产环境可能还要等上好几年。而在当前,我们能够做点什么来更好地处理异步并发呢?
在Java开发者当中普遍存在这样的误解,他们认为伸缩需要更多的线程。或许,在使用命令式编程范式和阻塞式模型时,这样想是对的,但通常来说并非如此。如果一个应用程序是完全非阻塞的,那么它完全可以使用少量的线程来实现伸缩,Node.js就是好的例子。而在Java里,我们不一定要局限于只使用单个线程,我们可以启动足够的线程,把CPU的核数都用上。不过原则依旧:我们并不依赖更多的线程来实现高并发。
我们是如何让应用程序变成非阻塞式的?先,我们必须放弃与命令式编程有关的串行逻辑,我们使用异步API,对事件作出反应。当然,使用太多的回调函数很快就会让事情变得复杂起来。我们可以使用更好的模型,比如Java 8引入的CompletableFuture,它提供了链式API,处理事件的逻辑是按步骤串联在一起的,而不是按照嵌套回调的方式组织在一起。
技术栈选择
Spring并不是一个提供异步非阻塞特性的开发框架,不过它在企业级应用层面提供了各个层次的选择。能够做出自由的选择是非常关键的一点,因为并非所有的应用程序都能随意更改,况且有些应用根本就不需要做出更改。随着微服务架构的发展,单个应用程序可以独立发生变更,那么选择性、一致性和持续性也就变得越发重要。
接下来让我们来看看我们都有哪些选择。
应用服务器
长久以来,Servlet API是事实上的应用服务器标准。不过,随着时间推移,出现了一些替代方案,对于想尝试事件循环并发和非阻塞式IO的项目来说,它们早就把目光移到了Servlet API和Servlet容器之外。
确实,在过去几年,Tomcat和Jetty发展得很不错。但这20年来一直没怎么发生改变的是,我们一直在使用阻塞式的Servlet API。Servlet API在3.1版本中引入了非阻塞式API,不过还没有被实际采用,因为它要求应用服务器做出深度的修改,把原先围绕阻塞式IO而设计的核心框架和应用程序接口全部都改掉。所以实际情况是,开发者需要在Servlet的阻塞式API和不依赖Servlet API的三方异步运行时(如Netty)之间做出选择。
在Spring 5中,我们可以选择是使用阻塞式API还是反应式运行时。Spring WebFlux应用程序可以运行在Servlet容器上。从Spring Boot 2开始,WebFlux默认使用Netty,不过也可以选用Tomat或Jetty,只需要修改几行配置代码。
在控制器上使用注解
Spring MVC的注解方式可以用在Servlet栈(Spring MVC)和反应式栈(Spring WebFlux)上。也就是说,我们可以在阻塞式和非阻塞式之间做出选择,同时保持编程模型不变。
反应式客户端
使用反应式客户端可以在不处理与线程相关代码的情况下,更有效地调度对远程服务的调用。这对于服务器端的并发性能来说无疑有巨大的好处。
反应式类库
注解编程模型的好处之一是可以灵活地选择方法签名。应用程序可以灵活地选择方法参数和返回值,这样就可以更方便地支持多个反应式类库。
不管是Servlet栈还是反应式栈,都可以在控制器的方法签名中使用Reactor或RxJava类型。而且这是可配置的,我们也可以使用其他反应式类库。
函数式Web端点
除了可以在控制器上使用注解,Spring WebFlux还支持轻量级的函数式编程模型,也就是基于lambda表达式来路由和处理请求。
函数式端点与注解控制器非常不一样。在使用注解时,我们告诉框架应该要做什么,然后让框架尽可能为我们做更多的工作——还记得好莱坞的“不要打给我,我会打给你”法则吗?相反,函数式编程模型提供了一系列辅助类,用于从头到尾处理请求消息。
反应式、非阻塞和回压
Servlet栈和反应式栈都支持在控制器上使用注解,不过它们的并发模型是不一样的。
在Servlet栈里,允许应用程序发生阻塞,这也就是为什么Servlet容器需要使用一个很大的线程池来应对可能发生的阻塞。这个可以从Filter和Servlet接口看出来,它们是命令式的,而且返回的是void。阻塞式的InputStream和OutputStream也是一样。
而在反应式栈里,应用程序不能发生阻塞。事件轮询只提供了少量的线程,如果应用程序发生阻塞,很快就会殃及整个服务器。这个可以从WebFilter和WebHandler看出来,它们返回的是Mono。那些用在请求消息体和响应消息体中的反应式类型也是一样。
请求消息体可以通过Flux来访问,也就是说,我们必须一次性处理完整个数据块。这个看起来有点棘手,不过框架提供了内置的编码和解码器,用于将字节流转换成对象流。
作者:Rossen Stoyanchev
译者:无明
来源:infoq
【免责声明】:本内容转载于网络,转载目的在于传递信息。文章内容为作者个人意见,本平台对文中陈述、观点保持中立,不对所包含内容的准确性、可靠性与完整性提供形式地保证。请读者仅作参考。