响应式编程-基于Reactor模式WebFlux框架的Spring Gateway

背景

响应式编程在一些java生态的框架中应用很多,如Spring Cloud Gateway、Spring AI等中,Spring Cloud Gateway依靠响应式编程框架WebFlux基于Netty实现了一个非阻塞的网关,解决传统网关请求量大时候的线程耗尽问题,另外对于日常AIGC生产一般的大模型流式数据返回也是使用到了响应式编程,这样可以第一时间拿到响应内容,提升用户体验。

响应式编程

  • Observer、Observable
  • 发布者Flux、Mono
  • 调度器Schedulers

Spring Cloud Gateway原理

  • 自动配置相关组件
  • 工作流程
  • 定制优化动态路由配置、过滤器热加载的设计思路

响应式编程

响应式编程是一种面向数据流和变化传播的编程范式。

为了应对高并发服务器端开发场景,在2009 年,微软提出了一个更优雅地实现异步编程的方式——Reactive Programming,我们称之为响应式编程。随后,Netflix 和LightBend 公司提供了RxJava 和Akka Stream 等技术,使得Java 平台也有了能够实现响应式编程的框架。

在2017 年9 月28 日,Spring 5 正式发布。Spring 5 发布最大的意义在于,它将响应式编程技术的普及向前推进了一大步。而同时,作为在背后支持Spring 5 响应式编程的框架Spring Reactor,也进入了里程碑式的3.1.0 版本。

Observer、Observable

在Java8的版本,可以使用Observer、Observable实现响应式编程。

Java9 已弃用 此类和 Observer 接口已弃用。Observer 和 Observable 支持的事件模型非常有限,Observable 传递的通知顺序未指定,并且状态更改与通知不是一一对应的。对于更丰富的事件模型,请考虑使用 java.beans 包。要在线程之间实现可靠和有序的消息传递,请考虑使用 java.util.concurrent 包中的并发数据结构之一。有关反应式流样式编程,请参阅 java.util.concurrent.Flow API。

发布者Flux、Mono

发布者Publisher:Publisher是一个可以提供0-N个序列元素的提供者,并根据其订阅者Subscriber<? super T>的需求推送元素。一个Publisher可以支持多个订阅者,并可以根据订阅者的逻辑进行推送序列元素。

订阅者Subscriber:Publisher提供了subscribe方法,允许消费者在有结果可用时进行消费。如果没有消费者Publisher不会做任何事情,不会耗费资源去计算、传输数据,他根据消费情况进行响应,通俗理解把整个事件比作做菜,传统的数据处理如直接向List放元素在处理可以理解为不管有无处理,菜已经做好了,而响应的发布订阅模式只是提前准备好原始原料,等到消费者订阅的时候才开始做菜。

Publisher可能返回零或者多个,甚至可能是无限的,为了更加清晰表示期待的结果就引入了两个实现模型Mono和Flux,使用他们可以发布元素值、完成信号、错误信号,错误信号是终止数据流,然后把错误信息传递给Subscriber,错误信号和完成信号都是终止信号,不能共存。

  • Flux:传输0个或者多个事件,常用方法just表示创建的Flux序列在发布这些元素之后自动结束,generate、create一般创建比较复杂的Flux序列,generate方法表示同步、逐一的方式生成序列,表示往序列添加元素的next方法只能被调用一次,而create支持同步、异步的消息产生,next方法可以调用多次。
  • Mono:返回0个或者1个事件

调度器Schedulers

在响应式编程中,特别是使用Reactor框架时,调度器(Scheduler)用于控制代码执行的线程模型。以下是你提到的几种调度器的特点和区别:

  1. Schedulers.immediate()特点:在当前线程上立即执行任务。用途:适合需要在调用线程上同步执行的操作,通常用于测试或简单的同步任务。
  2. Schedulers.single()特点:使用单一的可复用线程。用途:适用于需要串行执行的任务,这样可以避免多线程环境中的竞争条件。
  3. Schedulers.elastic()特点:使用弹性的线程池,线程可以被复用。若线程闲置时间过长,则会被销毁。用途:适合I/O操作,如文件读写或网络请求,因为这些操作通常会阻塞线程。弹性线程池可以根据需要动态调整线程数量。
  4. Schedulers.parallel()特点:使用为并行操作优化的线程池,线程数量通常与CPU核心数一致。用途:适用于CPU密集型任务,如复杂计算或数据处理。这种调度器可以充分利用多核CPU的性能。
  5. Schedulers.timer()特点:支持任务调度的调度器。用途:适合需要在特定时间点或延时后执行的任务。通常用于定时任务或周期性任务。
  6. Schedulers.fromExecutorService()特点:从已有的ExecutorService对象创建调度器。用途:适合需要自定义线程池配置的场景,允许使用已有的ExecutorService来控制线程的创建和管理。

这些调度器提供了多种线程管理策略,可以根据任务的特性选择合适的调度器,以优化性能和资源使用。

在响应式编程中,元素发送的顺序可能会受到使用的调度器和操作符的影响。以下是一些可能影响元素发送顺序的因素:

  • Schedulers.immediate() 和 Schedulers.single():通常会按照代码的顺序发送元素,因为这些调度器在单一线程上执行。
  • Schedulers.elastic() 和 Schedulers.parallel():可能会改变元素的发送顺序,因为这些调度器涉及多线程操作,尤其是在并行执行的情况下。

Spring Cloud Gateway

Spring Cloud Gateway 原理介绍和应用

在Spring Cloud Gateway未发布前,Spring Cloud使用的是Zuul网关,Zuul使用的是基于Servlet的阻塞模型,每次请求需要分配专门的线程处理,所以资源开销比较大,在高并发场景需要大量的线程,线程数成为了系统的瓶颈,所以作为替代,Spring Cloud Gateway网络层使用了基于非阻塞的Netty服务,从而解决线程瓶颈提升了性能。

Spring Cloud Gateway 是基于 Spring 5 和 Spring Boot 2 搭建的,本质上是一个 Spring Boot 应用。网关作为统一的流量入口,一些请求预处理如鉴权、限流、服务保护等可以在做在网关层,这样各个微服务只需要专注自己的业务逻辑,另外网关的作用

  • 请求路由
  • 修改请求响应
  • 权限校验
  • 限流熔断
  • 请求重试:可以对那些幂等性的接口网关转发到微服务失败的重试
  • 响应缓存:缓存一些频繁访问不经常变动的静态资源
  • 响应聚合:将不同的微服务响应聚合一块响应
  • 灰度流量
  • 异常响应处理

SpringMVC使用了传统的阻塞Servlet框架,Gateway使用了Spring WebFlux非阻塞Reactor框架,网络层使用的是非阻塞Netty,主要作用是处理请求进行预处理以及转发,包含的组件

  • Route:路由ID + 转发URI + 多个 Predicate + 多个 Filters 组成,可以针对一个api配置多个Route,匹配的时候按照优先级
  • Predicate:路由的匹配条件,一个Route可以包含多个Predicates,最终会被合并一个Predicate
  • Filter:对请求、响应进行pre、post处理,分为Route Filter和全局Filter
    • 全局Filter:作用域全部的Route
    • Route Filter:作用域是某个Route

自动配置相关组件

在GatewayAutoConfiguration类中,相关注解

@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
@EnableConfigurationProperties
@AutoConfigureBefore({
   
    HttpHandlerAutoConfiguration.class, WebFluxAutoConfiguration.class })
@AutoConfigureAfter({
   
    GatewayReactiveLoadBalancerClientAutoConfiguration.class,
		GatewayClassPathWarningAutoConfiguration.class })
@ConditionalOnClass(DispatcherHandler.class) // 仅在WebFlux存在的时候该配置类生效

自动配置类注册一些功能bean

  • 转换器相关的bean:比如字符串和时间日期互转、键值对相关转换bean
  • 路由定位器相关的bean:主要是RouteLocator的编排类CompositeRouteLocator、CompositeRouteDefinitionLocator可以设置多种加载方式编排所有的RouteLocator然后放入CachingRouteLocator组合路由定位器缓存,提高路由查找性能。
    • RouteLocatorBuilder:提供一个DSL API 路由定位器构建器,用于创建和配置路由。
    • RouteDefinitionLocator:通过getRoutes获取全部的路由定位器
      • RouteDefinitionRouteLocator:负责从不同的数据源(如配置文件、数据库等)加载RouteDefinition,通过getRouteDefinitions获取全部的RouteDefinition
        • PropertiesRouteDefinitionLocator:从配置属性中加载路由定义。
        • InMemoryRouteDefinitionRepository:在内存中存储路由定义,作为默认的路由定义存储库。
  • 路由刷新监听相关的bean
    • RouteRefreshListener:监听路由刷新事件,当路由变化时,触发响应的处理
  • 处理器映射器HandlerMapping相关bean
    • FilteringWebHandler:处理请求的过滤逻辑,应用全局过滤器和路由过滤器。
    • RoutePredicateHandlerMapping:据路由谓词匹配请求,将请求映射到相应的Handler处理器
  • 跨域相关bean
    • GlobalCorsProperties:配置全局跨域属性
    • CorsGatewayFilterApplicationListener:处理跨域请求,应用跨域过滤器
  • 全局过滤器相关bean:一些请求通用的过滤器,如:适配缓存的请求体、处理转发路径、处理WebSocket路由请求
  • 路由谓词工厂相关的bean:一些根据请求头部、路径、方法、Cookie等信息判断请求是否匹配的类
  • 相关内部配置bean
    • Bucket4jConfiguration:配置Bucket4j限流相关的bean
    • NettyConfiguration:配置Netty相关的bean

工作流程

当接收到一个请求,需要的处理过程

  • 路由匹配 :根据请求的属性(如路径、请求头等),匹配到对应的路由规则。
  • 过滤器处理 :对匹配到的路由应用一系列的过滤器,包括全局过滤器和路由特定的过滤器。
  • 请求转发 :将处理后的请求转发到目标服务。
  • 响应处理 :接收目标服务的响应,并应用过滤器进行处理,最后返回给客户端

调度器初始化

调度器初始化:在此过程DispatcherHadler作为调度器,在Spring启动后,会扫描响应的处理器、处理器适配器类型的bean加载到成员属性

public class DispatcherHandler implements WebHandler, PreFlightRequestHandler, ApplicationContextAware {
   
   

    // 请求处理器
	@Nullable
	private List<HandlerMapping> handlerMappings;

    // 处理器适配器
	@Nullable
	private List<HandlerAdapter> handlerAdapters;

    // 处理器处理结果
	@Nullable
	private List<HandlerResultHandler> resultHandlers;


    // 省略...


    // 在Spring启动之后调用initStrategies
    @Override
	public void setApplicationContext(ApplicationContext applicationContext) {
   
   
		initStrategies(applicationContext);
	}


    // 扫描相关的bean放入成员属性 
	protected void initStrategies(ApplicationContext context) {
   
   
		Map<String, HandlerMapping> mappingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
				context, HandlerMapping.class, true, false);

		ArrayList<HandlerMapping> mappings = new ArrayList<>(mappingBeans.values());
		AnnotationAwareOrderComparator.sort(mappings);
		this.handlerMappings = Collections.unmodifiableList(mappings);

        // 这个方法是 Return all beans of the given type or subtypes
		Map<String, HandlerAdapter> adapterBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
				context, HandlerAdapter.class, true, false);

		this.handlerAdapters = new ArrayList<>(adapterBeans.values());
		AnnotationAwareOrderComparator.sort(this.handlerAdapters);

		Map<String, HandlerResultHandler> beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
				context, HandlerResultHandler.class, true, false);

		this.resultHandlers = new ArrayList<>(beans.values());
		AnnotationAwareOrderComparator.sort(this.resultHandlers);
	}
}

路由匹配

需要匹配对应的Route信息:接收到请求之后,请求相关信息被封装到了ServerWebExchange类,DispatcherHandler会调用handler方法,会找对应的所有HandlerMapping异步处理请求,调用handlerMapping.getHandler方法拿到handler处理请求

// ServerWebExchange 接口提供了一个统一的方式来处理 HTTP 请求和响应,以及与服务器端处理相关的其他属性和特性。
// 通过实现该接口,可以方便地构建自定义的 Web 应用程序。

@Override
public Mono<Void> handle(ServerWebExchange exchange) {
   
   
    if (this.handlerMappings == null) {
   
   
        return createNotFoundError();
    }
    // 检查当前请求是否为 CORS 预检请求。如果是,则调用 handlePreFlight 方法处理预检请求,并返回处理结果
    if (CorsUtils.isPreFlightRequest(exchange.getRequest())) {
   
   
        return handlePreFlight(exchange);
    }
    return Flux.fromIterable(this.handlerMappings) // 将 handlerMappings 列表转换为一个 Flux 对象,用于异步处理每个 HandlerMapping
            .concatMap(mapping -> mapping.getHandler(exchange)) // 对每个 HandlerMapping 调用 getHandler 方法,获取能够处理当前请求的处理器,并将结果合并为一个新的 Flux 对象。
            .next() // 从 Flux 中获取第一个元素,如果没有元素,则返回一个空的 Mono 对象。
            .switchIfEmpty(createNotFoundError())
            .onErrorResume(ex -> handleResultMono(exchange, Mono.error(ex)))
            // 如果成功获取到处理器webHandler(即找到了Route放在了WebServerExchange本地请求上下文并返回了FilteringWebHandler)
            // 调用 handleRequestWith 方法处理请求,并返回处理结果。
            .flatMap(handler -> handleRequestWith(exchange, handler)); 
}

// AbstractHandlerMapping#getHandler方法
@Override
public Mono<Object> getHandler(ServerWebExchange exchange) 
    // 调用子类的getHandlerInternal
    return getHandlerInternal(exchange).map(handler -> {
   
   
        if (logger.isDebugEnabled()) {
   
   
            logger.debug(exchange.getLogPrefix() + "Mapped to " + handler);
        }
        ServerHttpRequest request = exchange.getRequest();
        if
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

scl、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值