Spring Boot - 实用功能24 - webflux
文章目录
注意:spring WebFlux是Spring 5之后才有的模块
一:webflux介绍
1:什么是webflux
- WebFlux是spring5.0以后添加的模块
- WebFlux是一种异步非阻塞的框架,在 Servlet3.1 以后才支持,核心是基于 Reactor 的相关API实现的
- WebFlux能够在有限资源下,提高系统吞吐量和伸缩性,并以 Reactor 为基础实现响应式编程
- WebFlux可在Netty,Undertow和Servlet 3.1+容器等服务器上运行
- WebFlux和WebMVC是在Spring Framework中共存的,应用程序可以同时使用这两个模块
2:异步非阻塞
同步和异步
调用者发送请求,如果等着对方回应之后才去做其他事情就是同步,如果发送请求之后不等着对方回应就去做其他事情就是异步
阻塞和非阻塞
被调用者收到请求之后,做完请求任务之后才给出反馈就是阻塞,收到请求之后马上给出反馈然后再去做事情就是非阻塞
- spring-webmvc + servlet + tomcat <- 命令式的,同步阻塞的
- spring-webflux + reactor + netty <- 响应式的,异步非阻塞的
3:响应式编程
响应式编程是一种面向数据流和变化传播的编程范式
这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播
电子表格程序就是响应式编程的一个例子。单元格可以包含字面值或类似"=B1+C1"的公式,而包含公式的单元格的值会依据其他单元格的值的变化而变化
4:Reactive 和 Reactor
Reactor 是基于Reactive Streams 规范的第四代响应库,用于在JVM上构建非阻塞的应用程序
Reactor是Spring WebFlux的首选响应库。
Reactor提供了 Mono和 Flux API类型,并通过丰富运算符集来处理0…1(Mono)和0…N(Flux)数据序列
Reactive 是一种响应式编程的规范,而 Reactor 是此规范的一种具体实现,他是支撑 WebFlux 实现响应式编程的基础
5:mvc or webflux
下面是选型建议
- 如果有运行正常的Spring MVC应用程序,则无需更改。命令式编程是编写,理解和调试代码的最简单方法。您有最大的库选择空间,因为从历史上看,大多数库都是阻塞的。
- 如果已经在购买无阻塞的Web堆栈,Spring WebFlux可以提供与该领域其他服务器相同的执行模型优势,还可以选择服务器(Netty,Tomcat,Jetty,Undertow和Servlet 3.1+容器),选择编程模型(带注释的控制器和功能性Web端点),以及选择反应式库(Reactor,RxJava或其他)
- 如果对与Java 8 lambda或Kotlin一起使用的轻量级功能性Web框架感兴趣,则可以使用Spring WebFlux功能性Web端点。对于要求较低复杂性的较小应用程序或微服务(可以受益于更高的透明度和控制)而言,这也是一个不错的选择。
- 在微服务架构中,可以混合使用带有Spring MVC或Spring WebFlux控制器或带有Spring WebFlux功能端点的应用程序。在两个框架中都支持相同的基于注释的编程模型,这使得重用知识变得更加容易,同时还为正确的工作选择了正确的工具
二:webflux说明
1:webFlux核心类
响应式编程操作中,Reactor 是满足 Reactive 规范的框架,Reactor 有两个核心类,Mono 和 Flux
这两个类实现接口 Publisher,提供丰富操作符
Flux 对象实现发布者,返回 N 个元素;Mono 实现发布者,返回 0 或者 1 个元素
Flux 和 Mono 都是数据流的发布者,使用 Flux 和 Mono 都可以发出三种数据信号: 元素值,错误信号,完成信号
错误信号和完成信号都代表终止信号,终止信号用于告诉订阅者数据流结束了,错误信号终止数据流同时把错误信息传递给订阅者
三种信号的特点如下:
- 错误信号和完成信号都是终止信号,不能共存
- 如果没有发送任何元素值,而是直接发送错误或者完成信号,表示是空数据流
- 如果没有错误信号,没有完成信号,表示是无限数据流
reactor demo
<!-- https://mvnrepository.com/artifact/io.projectreactor/reactor-core -->
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.4.1</version>
</dependency>
// 声明数据流
//第一种形式
Flux.just(1, 2, 3, 4);
Mono.just(1);
//第二种形式
Integer[] array = {1, 2, 3, 4};
Flux.fromArray(array);
//第三种形式
List<Integer> list = Arrays.asList(array);
Flux.fromIterable(list);
//第四种形式
Stream<Integer> stream = list.stream();
Flux.fromStream(stream);
// 订阅数据流
// 调用 just 或者其他方法只是声明数据流,数据流并没有发出
// 只有进行订阅之后才会触发数据流,不订阅什么都不会发生的
Flux.just(1, 2, 3, 4).subscribe(System.out::println);
Mono.just(1).subscribe(System.out::println);
@Test
void test1() {
// 方式一创建 & 订阅
Flux<Integer> just = Flux.just(1, 2, 3, 4);
just.subscribe(System.out::println);
// 方式二创建 & 订阅
Integer[] arr = {1, 2, 3, 4};
Flux<Integer> fromArray = Flux.fromArray(arr);
fromArray.subscribe(System.out::println);
// 方式三创建 & 订阅
List<Integer> list = Arrays.asList(1, 2, 3, 4);
Flux<Integer> fromIterable = Flux.fromIterable(list);
fromIterable.subscribe(System.out::println);
// 方式四创建 & 订阅
Flux<Integer> range = Flux.range(1, 4);
range.subscribe(System.out::println);
// 方式五:stream
Stream<Integer> stream = Stream.of(1, 2, 3, 4);
Flux<Integer> fromStream = Flux.fromStream(stream);
fromStream.subscribe(System.out::println);
}
2:WebFlux操作符
操作符就是对数据进行一道又一道的操作,就好比生产车间的流水线
@Test
void test1() {
// 操作符map的演示
Flux<Integer> map = Flux.just(1, 2, 3, 4).map(i -> i * 2);
map.subscribe(System.out::println);
// 输出:2 4 6 8
// 操作符flatMap的演示
// flatMap操作符将Flux中的元素转换成Flux
Flux<Integer> flatMap = Flux.just(1, 2, 3, 4).flatMap(i -> Flux.just(i * 2));
flatMap.subscribe(System.out::println);
// 输出:2 4 6 8
// 操作符filter的演示
Flux<Integer> filter = Flux.just(1, 2, 3, 4).filter(i -> i % 2 == 0);
filter.subscribe(System.out::println);
// 输出:2 4
// 操作符take的演示
// take操作符从Flux中取出指定数量的元素
Flux<Integer> take = Flux.just(1, 2, 3, 4).take(2);
take.subscribe(System.out::println);
// 输出:1 2
// 操作符skip的演示
// skip操作符从Flux中跳过指定数量的元素
Flux<Integer> skip = Flux.just(1, 2, 3, 4).skip(2);
skip.subscribe(System.out::println);
// 输出:3 4
// 操作符concat的演示
// concat操作符将两个Flux中的元素连接成一个新的Flux
Flux<Integer> concat = Flux.concat(Flux.just(1, 2), Flux.just(3, 4));
concat.subscribe(System.out::println);
// 输出:1 2 3 4
// 操作符merge的演示
// merge操作符将两个Flux中的元素混合成一个新的Flux
Flux<Integer> merge = Flux.merge(Flux.just(1, 2), Flux.just(3, 4));
merge.subscribe(System.out::println);
// 输出:1 2 3 4
// 操作符zip的演示
// a, b, 指定的压缩方式,将两个Flux中的元素压缩成一个新的Flux
Flux<Integer> zip = Flux.zip(Flux.just(1, 2), Flux.just(3, 4), Integer::sum);
zip.subscribe(System.out::println);
// 输出:4 6
}
3:关键接口
WebHandler
: 处理请求的核心接口WebFilter
: 过滤器接口WebExceptionHandler
: 异常处理器
4:基本控制器-编程风格
注解式风格
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
public Mono<User> getUser(@PathVariable String id) {
return userRepository.findById(id);
}
@GetMapping
public Flux<User> getAllUsers() {
return userRepository.findAll();
}
}
函数式风格
@Configuration
public class UserRouter {
@Bean
public RouterFunction<ServerResponse> route(UserHandler userHandler) {
return RouterFunctions.route()
.GET("/users/{id}", userHandler::getUser)
.GET("/users", userHandler::getAllUsers)
.POST("/users", userHandler::saveUser)
.build();
}
}
@Component
public class UserHandler {
public Mono<ServerResponse> getUser(ServerRequest request) {
String id = request.pathVariable("id");
return userRepository.findById(id)
.flatMap(user -> ServerResponse.ok().bodyValue(user))
.switchIfEmpty(ServerResponse.notFound().build());
}
}
5:核心组件说明
5.1:响应式 WebClient
WebClient client = WebClient.create("http://example.com");
Mono<User> userMono = client.get()
.uri("/users/{id}", 1)
.retrieve()
.bodyToMono(User.class);
Flux<User> usersFlux = client.get()
.uri("/users")
.retrieve()
.bodyToFlux(User.class);
5.2:响应式数据库
/**
MongoDB
Cassandra
Redis
Couchbase
R2DBC (关系型数据库)
*/
public interface UserRepository extends ReactiveCrudRepository<User, String> {
Flux<User> findByAgeGreaterThan(int age);
Mono<User> findByUsername(String username);
}
5.3:响应式安全
@EnableWebFluxSecurity
public class SecurityConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http.authorizeExchange()
.pathMatchers("/admin/**").hasRole("ADMIN") // 要求指定的要求admin权限
.anyExchange().authenticated()
.and()
.httpBasic()
.and()
.build();
}
}
三:高级特性
1:WebFlux 配合 SSE
SSE (Server-Sent Events) 是一种服务器向客户端推送事件的轻量级协议,基于 HTTP 协议实现。
- 单向通信:服务器到客户端的单向数据流
- 文本协议:基于纯文本,简单易用
- 自动重连:内置重连机制
- 简单高效:比 WebSocket 更轻量级
@GetMapping(path = "/stream-sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<String>> streamEvents() {
return Flux.interval(Duration.ofSeconds(1))
.map(sequence -> ServerSentEvent.<String>builder()
.id(String.valueOf(sequence)) // 事件id
.event("periodic-event") // 事件类型
.data("SSE - " + LocalTime.now().toString()) // 事件内容
.build());
}
动态数据源
@GetMapping(value = "/random-numbers", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<Integer>> randomNumbers() {
Random random = new Random();
return Flux.interval(Duration.ofSeconds(1))
.map(seq -> ServerSentEvent.<Integer>builder()
.id(String.valueOf(seq))
.event("random")
.data(random.nextInt(100))
.build());
}
数据库变更推送
@Autowired
private ReactiveMongoTemplate mongoTemplate;
@GetMapping(value = "/users/changes", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<User>> userChanges() {
return mongoTemplate.changeStream(User.class)
.watchCollection("users")
.listen()
.map(change -> ServerSentEvent.builder()
.event(change.getOperationType().name())
.data(change.getBody())
.build());
}
背压控制
@GetMapping(value = "/controlled-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<String>> controlledStream() {
return Flux.<String>create(emitter -> {
// 模拟数据源
for (int i = 0; i < 100; i++) {
emitter.next("Event " + i);
sleep(200); // 控制生产速度
}
emitter.complete();
})
.onBackpressureBuffer(10) // 缓冲区大小
.map(data -> ServerSentEvent.builder().data(data).build());
}
2:大模型的流式输出
客户端 (浏览器/APP)
↓ SSE 长连接
Spring WebFlux 控制器 (SSE 端点)
↓ 流式调用
大模型服务 (本地/远程)
↑ 分块返回
模型推理引擎
模型输出 tokens → 服务端分块收集 → SSE 事件推送 → 客户端实时渲染
2.1:服务端实现
基本SSE端点
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import org.springframework.http.codec.ServerSentEvent;
@RestController
public class ModelStreamingController {
@PostMapping(value = "/stream-generate", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<String>> streamGeneration(@RequestBody PromptRequest request) {
return modelService.streamGenerate(request.getPrompt())
.map(token -> ServerSentEvent.builder(token).build());
}
}
模型服务层
@Service
public class ModelStreamingService {
// 模拟大模型流式输出
public Flux<String> streamGenerate(String prompt) {
return Flux.generate(() -> 0, (state, sink) -> {
// 实际应用中替换为真实模型调用
String nextToken = simulateModelGeneration(prompt, state);
if ("[DONE]".equals(nextToken)) {
sink.complete();
} else {
sink.next(nextToken);
}
return state + 1;
});
}
private String simulateModelGeneration(String prompt, int step) {
try {
Thread.sleep(50); // 模拟生成延迟
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
String[] sampleOutput = {
"思考", "中", "...",
"这是", "一个", "流式", "输出", "示例",
"[DONE]"
};
return step < sampleOutput.length ? sampleOutput[step] : "[DONE]";
}
}
2.2:集成真实大模型
本地大模型的集成
public Flux<String> streamLocalModel(String prompt) {
return Flux.create(emitter -> {
try {
Process process = new ProcessBuilder("python", "model_script.py", prompt)
.redirectErrorStream(true)
.start();
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
emitter.next(line);
}
emitter.complete();
} catch (IOException e) {
emitter.error(e);
}
});
}
远程API集成
public Flux<String> streamOpenAI(String prompt) {
OpenAIApi api = new OpenAIApi();
return Flux.create(emitter -> {
CompletionRequest request = new CompletionRequest();
request.setPrompt(prompt);
request.setStream(true);
api.createCompletionStream(request, new ResponseBodyCallback() {
@Override
public void onResponse(String chunk) {
emitter.next(chunk);
}
@Override
public void onComplete() {
emitter.complete();
}
@Override
public void onFailure(Exception e) {
emitter.error(e);
}
});
});
}
3:API 网关中的WebFlux
WebFlux 作为响应式编程框架,在 API 网关场景中表现出色,能够高效处理高并发、低延迟的网关请求。
3.1:核心应用
netty服务器接收请求 —> webflux调度请求(reactor) -> gateway handler mapping(匹配路由规则) -> 过滤器链的执行(执行post & pre过滤器) -> 代理服务调用(通过webclient进行响应式http调用) -> 响应返回(通过webflux返回响应)
package com.cui.first.config;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class GatewayConfig {
/**
* 配置路由
* @param builder 路由构建器
* @return 路由
*/
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
// id <-> predicate <-> filter <-> uri
.route("path_route", r -> r.path("/get")
.filters(f -> f.stripPrefix(1))
.uri("lb://SERVICE-PROVIDER"))
.route("host_route", r -> r.host("*.myhost.org")
.uri("lb://SERVICE-PROVIDER"))
.route("rewrite_route", r -> r.host("*.rewrite.org")
.filters(f -> f.rewritePath("/foo/(?<segment>.*)", "/${segment}"))
.uri("lb://SERVICE-PROVIDER"))
.build();
}
}
3.2:自定义过滤器
package com.cui.first.filter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {
/**
* 过滤器逻辑
* exchange: 请求对象
* chain: 过滤器链
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 前置处理, 在请求处理之前添加一个requestTime属性,用于记录请求开始时间
exchange.getAttributes().put("requestTime", System.currentTimeMillis());
return chain.filter(exchange).then(
Mono.fromRunnable(() -> {
// -------------> 后置处理 <-------------
// 拿到请求开始时间
Long startTime = exchange.getAttribute("requestTime");
if (startTime != null) {
System.out.println("请求耗时:" + (System.currentTimeMillis() - startTime) + "ms");
}
})
);
}
/*
* 优先级,数字越小优先级越高, 默认是0
*/
@Override
public int getOrder() {
return -1;
}
}