Spring Boot - 实用功能24 - webflux

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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值