一、响应式编程解决什么问题?
Spring framework 5 最大的变化就是引入了响应式编程(Reactive Programming )。
Spring 为啥要引入这个特性?
八个字:“逆水行舟,不进则废”!
在2009 年,微软提出了响应式编程,旨在应对高并发服务器端开发场景 。随后各语言很快跟进,都拥有了属于自己的响应式编程实现,比如Go、Node 等新语言。Java 作为服务器端开发语言老大的地位受到了不小的挑战,后有追兵,不得不进化啊。
所以,响应式编程的初心,就是去满足高并发下的服务器端开发任务 。
二、Java的响应式编程原理
Java的响应式编程是借助Mina 和Netty 这样的NIO 框架。
说到NIO ,可以参考:什么是阻塞和非阻塞?什么是同步和异步?什么是BIO、NIO、AIO?
鉴于以上的NIO 知识,简单地说:
当我们调用socket.read()、socket.write()这类阻塞函数的时候,这类函数不能立即返回,也无法中断,需要等待socket可读或者可写,才会返回,因此一个线程只能处理一个请求。在这等待的过程中,cpu并不干活,(即阻塞住了),那么cpu的资源就没有很好地利用起来。因此对于这种情况,我们使用多线程来提高cpu资源的利用率:在等待的这段时间,就可以切换到别的线程去处理事件,直到socket可读或可写了,通过中断信号通知cpu,再切换回来继续处理数据。例如线程A正在等待socket可读,而线程B已经就绪了,那么就可以先切换到线程B去处理。虽然上下文切换也会花一些时间,但是远比阻塞在线程A这里空等要好。当然计算机内部实际的情况比这复杂得多。
而NIO的读写函数可以立刻返回,这就给了我们不开线程利用CPU的最好机会:如果一个连接不能读写(socket.read()返回0或者socket.write()返回0),我们可以把这件事记下来。因此只需要一个线程不断地轮询这些事件,一旦有就绪的时间,处理即可。不需要多线程。
即,阻塞型IO
- 需要多线程,即需要很大的线程池。
- 每个请求都要有一个单独的线程去处理。
非阻塞型IO
- 只需要数量非常少的线程。
- 固定的几个工作线程去处理事件。
因此,基于NIO 的响应式编程就是,当你做一个带有一定延迟的才能够返回的io操作时,不会阻塞,而是立刻返回一个流,并且订阅这个流,当这个流上产生了返回数据,可以立刻得到通知并调用回调函数处理数据。
但原生的Netty技术只掌握在少数高级开发人员手中,因为它们难度较大,并不适合大部分普通开发者。不过Spring 5 给我们做了很好的封装,借助基本的几个API就能实现响应式编程。Java 领域的响应式编程库中,最有名的算是 Reactor 了。Reactor 也是 Spring 5 中反应式编程的基础,Webflux 依赖 Reactor 而构建。
Reactor 是一个基于 JVM 之上的异步应用基础库。为 Java 、Groovy 和其他 JVM 语言提供了构建基于事件和数据驱动应用的抽象库。Reactor 性能相当高,在最新的硬件平台上,使用无堵塞分发器每秒钟可处理 1500 万事件。
三、响应式编程模型
首先需要理解响应式编程的基本模型,如上。
Flux 和 Mono
Reactor 中的发布者(Publisher )由Flux 和Mono 两个类定义,它们都提供了丰富的操作符(operator )。一个Flux 对象代表一个包含N 个元素的响应式序列,元素可以是普通对象、数据库查询的结果、http响应体,甚至是异常。Mono 表示的是包含 0 或者 1 个元素的异步序列。该序列中同样可以包含与 Flux 相同的三种类型的消息通知。Flux 和 Mono 之间可以进行转换。对一个 Flux 序列进行计数操作,得到的结果是一个 Mono对象。把两个 Mono 序列合并在一起,得到的是一个 Flux 对象。上图就是一个Flux 类型的数据流,Flux 往流上发送了3 个元素,Subscriber 通过订阅这个流来接收通知。
因此,响应式编程是基于发布订阅模式的,或者是基于事件的。(这里涉及到背压的概念,背压是指订阅者能和发布者交互,可以调节发布者发布数据的速率,解决把订阅者压垮的问题。订阅者一般有request和cancel 两个方法,用于通知发布者需要数据和通知发布者不再接受数据。)
四、Spring中的响应式编程代码示例
依赖:
<dependencies>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<version>3.2.3.RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
Flux
Flux 和Mono 作为“数据流”的发布者,都可以发出三种“数据信号”:元素值 、错误信号 、完成信号 。当消息通知产生时,订阅者中对应的方法 onNext() , onComplete() 和 onError() 会被调用。
Flux.just(value);
Flux.fromIterable(list);
Flux.range(i, n);
Flux.interval(Duration.ofSeconds(n));
调用 just 或者其他方法只是声明数据流,数据流并没有发出,只有进行订阅之后才会触发数据流,不订阅什么都不会发生的。
Subscriber
subscriber是一个订阅者,只有非常简单的4个接口,主要是对应发布者的三种数据信号:
public interface Subscriber<T> {
void onSubscribe(Subscription var1);
void onNext(T var1);
void onError(Throwable var1);
void onComplete();
}
Subscriber必须要订阅一个Flux才能够接收通知:
flux.subscribe(
value -> handleData(value),
error -> handleError(error),
() -> handleComplete()
);
上面这个例子通过lambda表达式,定义了Subscriber分别在收到消息,收到错误,和消息流结束时的行为,当Subscriber接收到一个新数据,就会异步地执行handleData方法处理数据。
五、WebFlux 是什么?
WebFlux 模块的名称是 spring-webflux ,名称中的 Flux 来源于 Reactor 中的类 Flux 。Spring webflux 有一个全新的非堵塞的函数式 Reactive Web 框架,可以用来构建异步的、非堵塞的、事件驱动的服务,在伸缩性方面表现非常好。
如图所示,WebFlux 模块从上到下依次是 Router Functions 、WebFlux 、Reactive Streams 三个新组件。
- Router Functions 对标准的 @Controller,@RequestMapping 等的 Spring MVC 注解,提供一套 函数式风格的 API,用于创建 Router、Handler 和Filter。
- WebFlux 核心组件,协调上下游各个组件提供 响应式编程 支持。
- Reactive Streams 一种支持 背压 (Backpressure) 的 异步数据流处理标准,主流实现有 RxJava 和Reactor,Spring WebFlux 集成的是 Reactor。
默认情况下,Spring Boot 2 使用 Netty WebFlux,因为 Netty 在异步非阻塞空间中被广泛使用,Netty 是高性能的 NIO 框架,异步非阻塞的框架,异步非阻塞连接可以节省更多的资源,提供更高的响应度。值得注意的是:支持 reactive 编程的数据库只有 MongoDB, redis, Cassandra, Couchbase。
一般来说,Spring MVC 用于同步处理,Spring Webflux 用于异步处理。 SpringMVC 采用命令式编程:一行一行执行。Webflux 采用异步响应式编程,因此使用在gateway网关服务场景。
Spring Boot Webflux 有两种编程模型实现,一种类似 Spring MVC 注解方式,另一种是基于 Reactor 的响应式方式。
代码演示:
@RestController
public class HelloController {
@GetMapping("/hello")
public Mono<String> hello() {
return Mono.just("Welcome to reactive world ~");
}
}
通过上面的示例可以发现,开发模式和之前 Spring Mvc 的模式差别不是很大,只是在方法的返回值上有所区别。just() 方法可以指定序列中包含的全部元素。响应式编程的返回值必须是 Flux 或者 Mono ,前面说过两者之间可以相互转换。
@RunWith(SpringRunner.class)
@WebFluxTest(controllers = HelloController.class)
public class HelloTests {
@Autowired
WebTestClient client;
@Test
public void getHello() {
client.get().uri("/hello").exchange().expectStatus().isOk();
}
}
SpringWebflux 执行过程和 SpringMVC 相似的:SpringMVC 核心控制器 DispatchServlet ;SpringWebflux 核心控制器 DispatchHandler 。
Webflux 特点:
- 非阻塞式:在有限资源下,提高系统吞吐量和伸缩性,以 Reactor 为基础实现响应式编程。
- 函数式编程:Spring5 框架基于 java8,Webflux 使用 Java8 函数式编程方式实现路由请求。
六、WebClient 是什么?
WebClient 是从Spring WebFlux 5.0 版本开始提供的一个非阻塞的基于响应式编程的进行Http请求的客户端工具。它的响应式编程的基于Reactor 的。WebClient 中提供了标准Http 请求方式对应的get 、post 、put 、delete 等方法,可以用来发起相应的请求。
举个完整的例子:
@Data
@AllArgsConstructor
public class User {
private String name;
private String gender;
private Integer age;
}
public interface UserService {
Mono<User> getUserById(int id);
Flux<User> getAllUser();
Mono<Void> saveUserInfo(Mono<Void> user);
}
public class UserHandler {
private final UserService userService;
public UserHandler(UserService userService) {
this.userService = userService;
}
public Mono<ServerResponse> getUserById(ServerRequest request) {
int userId = Integer.valueOf(request.pathVariable("id"));
Mono<ServerResponse> notFound = ServerResponse.notFound().build();
Mono<User> userMono = this.userService.getUserById(userId);
return userMono.flatMap(person ->
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
.body(fromObject(person))
.switchIfEmpty(notFound));
}
public Mono<ServerResponse> getAllUsers() {
Flux<User> users = this.userService.getAllUser();
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(users,User.class);
}
public Mono<ServerResponse> saveUser(ServerRequest request) {
Mono<User> userMono = request.bodyToMono(User.class);
return ServerResponse.ok().build(this.userService.saveUserInfo(userMono));
}
}
public class Server {
public RouterFunction<ServerResponse> routingFunction(){
UserService userService = new UserServiceImpl();
UserHandler handler = new UserHandler(userService);
return RouterFunctions.route(
GET("/users/{id}").and(accept(APPLICATION_JSON)),handler::getUserById)
.andRoute(GET("/users").and(accept(APPLICATION_JSON)),handler::getAllUsers);
}
}
public void createReactorServer() {
RouterFunction<ServerResponse> route = routingFunction();
HttpHandler httpHandler = toHttpHandler(route);
ReactorHttpHandlerAdapter adapter = new
ReactorHttpHandlerAdapter(httpHandler);
HttpServer httpServer = HttpServer.create();
httpServer.handle(adapter).bindNow();
}
public class WebClientTest {
public static void main(String[] args) {
WebClient webClient = WebClient.create("http://127.0.0.1:50066");
String id = "1";
User u = webClient.get().uri("/users/{id}",id)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(User.class)
.block();
System.out.println(u.getName());
Flux<User> results = webClient.get().uri("/users")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToFlux(User.class);
results.map(stu -> stu.getName())
.buffer().doOnNext(System.out::println).blockFirst();
}
}
|