第六章 Reactive Web框架 —— Spring Cloud技术初探系列

前言

在Spring 5中的Reactive Web框架是一个包含了一套完整的服务端技术框架,这里面包括Web容器以及Web应用框架,是一套非常值得学习的技术栈。

本章目录

    • 前言
    • 6.1 Reactive简介
    • 6.2 Reactive Web框架的设计
    • 6.3 Spring WebFlux简介
    • 小结

6.1 Reactive简介

Reactive Streams 是 JVM 中面向流的库标准和规范,它包含以下特性:

  • 处理可能无限数量的元素
  • 按顺序处理
  • 组件之间异步传递
  • 强制性非阻塞背压(Backpressure)
    可以说是为了方便进行异步程序开发的一种新的编程模型。经过这样简单的介绍,响应式编程这个概念还是很抽象,不过可以参考这里仔细学习一下。

在Spring官网中,提供了下图的框架图:
第六章 Reactive Web框架 —— Spring Cloud技术初探系列_第1张图片
从这张结构图我们可以清晰地区分出基于Servlet api和Reactive各自的技术栈结构,不过在最上层,Spring都给我们封装成了我们在SpringMVC中使用的基于注解的Web层api。如果以前在使用SpringMVC的时候,Controller里面没有耦合太多Servlet api的话,那么迁移过去就是一件很轻松的事情,在新的框架上开发也和以往没有太大的区别,除非使用到了Reactive的新特性。这一点Spring做的非常棒,充分体现出了Spring框架设计的优雅。

异步框架在处理IO密集型业务上有比较突出的性能优势,因此有了这一套框架后,以后在相关的技术选型上又多了一套方案。Spring Cloud Gateway也要求必须运行在Reactive Web中。

6.2 Reactive Web框架的设计

针对Reactive框架,Spring对Server端重新进行了抽象,其中一个最底层的接口就是HttpHandler,它的定义如下:

public interface HttpHandler {
	/**
	 * Handle the given request and write to the response.
	 * @param request current request
	 * @param response current response
	 * @return indicates completion of request handling
	 */
	Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response);
}

但是真正在Web层中,它提供的抽象是WebHandler接口,它的定义如下:

public interface WebHandler {
	/**
	 * Handle the web server exchange.
	 * @param exchange the current server exchange
	 * @return {@code Mono} to indicate when request handling is complete
	 */
	Mono<Void> handle(ServerWebExchange exchange);
}

两者通过HttpWebHandlerAdapter进行适配。结构如下:
第六章 Reactive Web框架 —— Spring Cloud技术初探系列_第2张图片
比起以往我们熟悉的HttpServletRequest和HttpServletResponse这两个Servlet api,在Reactive框架中的api就再也看不到Servlet身影了。正因为如此,底层的Web容器就不一定要是Servlet容器了,所以Spring为此增加了一个新的基于Netty的内嵌Web容器。这个Web容器只是做了很简单的封装,使用了Netty原生HttpServerCodec对http请求和响应进行decode和encode。接下来的内容需要一些Netty的知识才能理解Spring的这套设计。
Spring封装了HttpServer、NettyWebServer以及NettyReactiveWebServerFactory来注入和启动整个Netty框架。整体流程大致如下:
第六章 Reactive Web框架 —— Spring Cloud技术初探系列_第3张图片
过程稍微有些复杂,其中关键的地方就是TcpBridgeServer(TcpServer的子类)中的newHandler方法了:

@Override
	public final Mono<? extends NettyContext> newHandler(BiFunction<? super NettyInbound, ? super NettyOutbound, ? extends Publisher<Void>> handler) {
		Objects.requireNonNull(handler, "handler");
		return Mono.create(sink -> {
			ServerBootstrap b = options.get();
			SocketAddress local = options.getAddress();
			b.localAddress(local);
			ContextHandler<Channel> contextHandler = doHandler(handler, sink);
			b.childHandler(contextHandler);
			if(log.isDebugEnabled()){
				b.handler(loggingHandler());
			}
			contextHandler.setFuture(b.bind());
		});
	}

这里就是很熟悉的Netty框架的启动代码了,我们可以看到doHandler方法返回的ContextHandler被添加进Netty的pipeline中,而这里的doHandler已经在TcpBridgeServer中被重写,节选部分代码:

return ContextHandler.newServerContext(sink,
					options,
					loggingHandler,
					(ch, c, msg) -> {
						HttpServerOperations ops = HttpServerOperations.bindHttp(ch,
								handler,
								c,
								compressPredicate,
								msg);
						if (alwaysCompress) {
							ops.compression(true);
						}
						return ops;
					})
			                     .onPipeline(this)
			                     .autoCreateOperations(false);

这里的onPipeline(this)方法就是设置一个回调,因为ContextHandler是Netty的ChannelInitializer,所以在它initChannel方法中会回调onPipeline设置进去的回调方法用来设置Netty的pipeline,具体被回调的就是TcpBridgeServer中的accept方法:

@Override
public void accept(ChannelPipeline p, ContextHandler<Channel> c) {
	p.addLast(NettyPipeline.HttpCodec, new HttpServerCodec(
	        options.httpCodecMaxInitialLineLength(),
	        options.httpCodecMaxHeaderSize(),
	        options.httpCodecMaxChunkSize(),
	        options.httpCodecValidateHeaders(),
	      options.httpCodecInitialBufferSize()));
	if (ACCESS_LOG_ENABLED) {
		p.addLast(NettyPipeline.AccessLogHandler, new AccessLogHandler());
	}
	p.addLast(NettyPipeline.HttpServerHandler, new HttpServerHandler(c));
}

于是,经过配置后每个请求的Netty pipeline都会有如下结构的Handler:
第六章 Reactive Web框架 —— Spring Cloud技术初探系列_第4张图片
由此可以看到,核心是HttpServerHandler,在这里面会将经过Netty的原生HTTP解码器处理过后的请求封装为ChannelOperations,并在ChannelOperations里面调用HttpHandler(在构造NettyWebServer的时候从Spring容器获取)的apply方法,交由web框架来处理请求。相关代码片段如下:

//136行 HttpServerHandler的channelRead方法中
parentContext.createOperations(ctx.channel(), msg);

//249行 ContextHandler的createOperations方法中
channel.eventLoop().execute(op::onHandlerStart);

//380行ChannelOperations的applyHandler方法中
Mono.fromDirect(handler.apply((INBOUND) this, (OUTBOUND) this))
			    .subscribe(this);

以上介绍的都是Spring根据Netty实现的Reactive Web容器,同时,Spring Boot也对常见的Servlet容器进行了适配,使得Reactive框架也能运行在Servlet 3.1容器里。至于适配则是通过ServletHttpHandlerAdapter,将Servlet的api转换成对应的Reactive api。

6.3 Spring WebFlux简介

从SpringBoot原生的HttpHandlerAutoConfiguration中可以发现,前面介绍的HttpHandler其实是一个Web容器的底层抽象,Spring愿意暴露给应用层的还是6.1节中提到的WebHandler,这两者之间的适配是在HttpHandlerAutoConfiguration里面进行配置的,这里面使用到了WebHttpHandlerBuilder来进行构造WebHandler bean:

public HttpHandler build() {
		WebHandler decorated = new FilteringWebHandler(this.webHandler, this.filters);
		decorated = new ExceptionHandlingWebHandler(decorated,  this.exceptionHandlers);
		HttpWebHandlerAdapter adapted = new HttpWebHandlerAdapter(decorated);
		if (this.sessionManager != null) {
			adapted.setSessionManager(this.sessionManager);
		}
		if (this.codecConfigurer != null) {
			adapted.setCodecConfigurer(this.codecConfigurer);
		}
		if (this.localeContextResolver != null) {
			adapted.setLocaleContextResolver(this.localeContextResolver);
		}
		if (this.applicationContext != null) {
			adapted.setApplicationContext(this.applicationContext);
		}
		return adapted;
	}

即将Spring容器中注入的WebHandler和WebFilter经过FilteringWebHandler和ExceptionHandlingWebHandler两层委派,然后由HttpWebHandlerAdapter进行适配。FilteringWebHandler是一个能够使用WebFilter过滤器的WebHandler,让整个Web容器具备了过滤器功能,可以类比于Servlet中的Filter。从这里就可以得到一些总结和经验,不管是tomcat、原生netty,还是SpringBoot里面封装的EmbeddedNettyServer,或者是SpringMVC等和web服务相关的框架,都应用了责任链的设计模式,可见它在web服务器设计上的重要性。

在应用层中有一个WebHandler的实现是DispatcherHandler,它则是WebFlux的核心分发处理器。其实从名字上可以看出来它充当的角色和SpringMVC里面的DispatcherServlet是一样的,两者在核心处理的设计上也基本上是一样的。在DispatcherHandler中,同样包含了HandlerMapping、HandlerAdapter等抽象概念,处理逻辑也是按照下图所示:
第六章 Reactive Web框架 —— Spring Cloud技术初探系列_第5张图片
其实,这样的设计和SpringMVC的DispatcherServlet有着异曲同工之处。因为Spring在web层没有直接强耦合SpringMVC的api,而是提供了Spring Web层,这也充分体现了Spring在设计框架时的严谨和优雅。如果我们在自己web层的代码遵循它的设计思想的话,没有耦合像ModelAndView、HttpServletRequest、HttpServletResponse(其实也没有必要耦合)等api的话,从理论上讲是很容易迁移到WebFlux上的。因为我们项目中要使用到Spring Cloud Gateway这种IO密集型业务,所以在技术选型的时候,想尝试一下异步框架可能带来的性能或者吞吐量的提升,所以在一开始便考虑使用WebFlux。其实对于以往使用SpringMVC的业务,也并非就需要迁移,也都是根据实际情况而定的。在Spring官网有更多关于WebFlux的背景和特性介绍。可以关注一下它关于Applicability和Performance的描述。

从WebFlux的设计上来看,它是不依赖Servlet容器的,虽然Spring也针对Tomcat,Jetty等Web容器进行了适配,但是我觉得Spring内置的Netty Server里应该才是它最合适的选择。所以这里就要求classpath下面不能有Servlet相关框架的依赖,也不能使用官方提供的方式来打war包在外置的Servlet容器中启动一个Spring Boot工程。

小结

至此,Spring Boot中内嵌的Netty容器已经分析完了,可以看出来,这个Web容器很轻薄,只是基于Netty原生提供的HTTP编解码handler上面封装了一层HttpHandler。不过在如何将这个HttpHandler作用进Netty的pipeline中,Spring做了一些设计,有一些复杂。不过理解了配置后的pipeline最终结构,就可以清楚它的工作原理了,这样的设计也对我们使用Netty提供了一个很好的参考示例,所以个人觉得很有学习的价值。

你可能感兴趣的:(spring)