前言
金林ssl适用于网站、小程序/APP、API接口等需要进行数据传输应用场景,ssl证书未来市场广阔!成为成都创新互联的ssl证书销售渠道,可以享受市场价格4-6折优惠!如果有意向欢迎电话联系或者加微信:028-86922220(备注:SSL证书合作)期待与您的合作!
上文我们简单阐述了一下接入层网关的实现原理
不少人对 Java 网关的实现也比较感兴趣,所以这篇文章我们来简单谈谈 Java 应用网关设计,本文将会从以下几个方面来阐述 Java 应用层网关的设计
Java 应用层网关的必要性
我们的 Java 网关分为应用层网关和业务嵌入式网关两部分,架构图如下
在这里插入图片描述
Java 网关分为核心网关和业务嵌入式网关服务两部分,主要工作原理如下
接入层流量首先进入 Java 核心网关,经过一系列的 pipeline 处理(风控,路由协议转换、流控、降级等操作)后发起泛化调用再打入业务层网关
业务层网关也会经过一系列的 pipeline(接口校验,验签,session 校验等)进入最终的业务逻辑,然后再调用相关 dubbo 服务最终完成本次 Java 请求的响应。
核心网关与嵌入式业务网关的功能如下
在这里插入图片描述
其中嵌入式网关是以 jar 包的形式集成到业务的工程里的,具体为啥要这样设计,后文会详述。
首先来看 Java 网关为啥要分成核心网关和嵌入式业务网关两部分,直接从接入层打到业务网关不是更省事吗,何必多此一举再加一层核心网关,多加一层不是多了一个损耗吗。
这里有三个原因
接下来我们简单谈谈核心网关和业务网关的设计思路。
核心网关技术选型
同步阻塞 VS 异步非阻塞
上节介绍可知 Java 核心网关承担着所有的流量入口,本身会调用大量的业务接口(打到业务网关里),所以 IO 操作会很频繁,在技术选型上是有要求的, 首先来看看传统的 Spring MVC(servlet 3.0之前)
很明显它是同步阻塞的, 一个请求需要对应一个 Servlet Thread 来处理,当有 DB,网络 IO 时,此线程会阻塞,可想而知用这种方案线程很快会占满,导致系统不可用。
显然我们应该采用异步非阻塞的编程模型,它是如何工作的呢,如下图示
工作原理如下
从以上的工作原理可以看出,负责处理请求的 request 线程只需求一个,线程数大大减少!更少的线程意味着更高的内存利用,也意味着线程间的切换开销大大减少!所以显然应该使用这种编程模型。
打个简单的比方,相信大家都有去酒店就餐的经历,对于酒店来说,怎么才能最大化地提高接客效率呢
最终我们选择了 Spring WebFlux 这种反应式(Reactive),基于事件驱动的异步非阻塞框架。
反应式编程与 Spring WebFlux 简介
反应式编程简介
反应式编程 (reactive programming) 是一种基于数据流 (data stream) 和 变化传递 (propagation of change) 的 声明式 (declarative) 的编程范式。它是一种编程思想,能够基于数据流中的事件(变化)进行相关反应处理,举个简单的例子:在 a = b + c 这个语句中,要得到 a 的值,如果用传统的编程模型,每次 b 或 c 变化后都需要重新计算以获得 a,而在反应式编程中,我们把 b,c 当作数据流,a 会对 b,c 作出的变化实时响应。
反应式编程有以下几个特点
1、事件驱动
在事件驱动的程序中,组件之间通过松藕合的生产者(也称被订阅者,即 Publisher)和订阅者模式(Subscriber)来实现,这些事件是以异步和非阻塞的方式来接收和发送的,基于事件驱动的编程有啥好处呢,简单地说它是依靠推模式而不是拉模式来动作的,也就是说只有生产者有消息(变化)时才会通知消费者作出响应,也就意味着消费者不需要轮询也不需要等待数据。
2、实时响应
以我们的网关为例, request 线程接收请求后,快速返回存储结果的上下文,把具体执行交给线程池里的线程(可以认为是后台线程),处理完成后,异步地将调用结果封装到结果的上下文中,可以看到此过程是完全异步的,也就是说实时响应必须通过异步编程实现,在 Java 8 中,发起调用后可以快速返回 CompletableFuture 对象。
3、弹性机制
事件驱动的松散耦合提供了组件在失败下可以抓获完全隔离的上下文场景,作为消息封装,发送到其他组件时,在具体编程时可以检查错误比如是否接受到,接受的命令是否可执行等等,并决定如何应对。
反应式编程主要工作流程如下
还有一个问题,如果 Publisher 发送消息过快超过 Subscriber 的处理速度了怎么办,所以就得提一下背压(BackPressure)的概念了,知乎网友扔物线对此概念解释我认为非常到位:
backpressure 是源自工程学中的概念:在管道运输中,气流或液流由于管道突然变细、急弯等原因导致由某处出现了下游向上游的逆向压力,这种情况称为「backpressure」,相应的在反应式编程中,在数据流从上游生产者向下游消费者传输的过程中,上游生产速度大于下游消费速度,导致下游的 Buffer 溢出,这种现象就叫做 Backpressure 出现,这里的重点在于「Buffer 溢出」,为什么需要 buffer, 因为 Publisher 生产速度大于 Subscriber 的消费速度,所以需要 Buffer, 因为外部条件限制,显然 Buffer 是有上限的,如果生产速度超过 buffer, 则 backpressure 产生,超过 buffer 的话,唯一的选择就是丢掉新事件。
这就好比,比如你的 server 只能承受 5000~6000 的请求,如果你把 buffer 设置为 5000,则一旦请求数超过 5000,则背压产生,超过的请求数丢弃,这样保证了机器不会被源源不断的 Publisher 生产事件压垮,有效提升了网关的可用性。
Spring WebFlux 简介
为了更好地促进反应式编程的应用,在 Java 平台上,Netflix(开发了 RxJava)、TypeSafe(开发了 Scala、Akka)、Pivatol(开发了 Spring、Reactor)共同制定了一个被称为 Reactive Streams 项目(规范),用于制定反应式编程相关的规范以及接口。
Reactor 基于 Reactive Stream 定制了一套反应式编程框架,而 WebFlux 则是以 Reactor 为基础实现了 Web 领域的反应式编程框架,由于反应式编程的异步非阻塞特性,所以 WebFlux 运行于 Netty , Undertow 等支持异步编程模型的 server 之上,当然也可运行于支持 Servlet 3.1 的 Server 容器上(Servlet 3.1 开始支持异步)
如图示,左侧是传统的 Spring MVC 结构, 右侧是 webflux 组件。
为了让大家更好利用 webflux 编程,Spring 贴心地兼容了 @Controller 等 Spring MVC 的注解在 webflux 的使用,能让使用者更好地过渡到 webflux 编程中来,不过在底层实现中,与 Spring MVC 的实现的请求 InputStream 和响应 OutputStream 不同,webflux 实现了一套反应式的请求(ServerHttpRequest) 和响应(ServerHttpResponse),这两个类将请求体与响应体以 Flux(Flux 下文会简单介绍下)的形式暴露出来,同时 webflux 底层也实现了基于 Flux的 JSON,XML 的序列化和反序列化,HTML 实图的渲染,Server 发送事件等。
通过介绍可以看到 webflux 实现了从请求到响应,到渲染,事件发送等一整套反应式事件的支持,是的,要最大程度地发挥 webflux 的性能,中间所有的事件都应该以 Mono 或 Flux 响应式事件流的形式存在!
WebFlux 的底层实现其实是基于 Reactor 实现的,在 Reactor 的核心类中,以下两个类代表了发布者
这玩意怎么用呢,如下图示
- @RequestMapping("/demo")
- @RestController
- public class DemoController {
- @RequestMapping(value = "/foobar")
- public Mono
foobar() { - return Mono.just(new Foobar());
- }
- }
本来是要返回 foobar 对象的,结果最终以 Mono(或 Flux)的形式存在,这样就构建了响应式编程中的生产者(Publisher),再调用 subscribe 即可完成对生产者的监听消费。
在我们的网关设计中,当收到请求后,使用了 Mono 来充当发布者,如果中间出现了问题,会调用 onError, 最终成功后会调用 onSuccess,以下是网关实现采用的总体框架。
图中 Mono.empty 代表创建一个不包含任何元素,只发布消息的队列。发送消息后,会在线程池里处理网关的 slot ,最后处理成功后会调用 onSuccess 方法,处理失败则会调用 onError。下一节我们来看看这些网关 slot 是如何处理的。
网关的责任链设计
不管是核心网关还是嵌入式网关我们都采用了责任链模式来实现网关的核心处理流程,将每个处理逻辑看成一个slot,每个 slot 按照预先设定的顺序先后执行,与开源kong,zuul等类似,我们也采用了PRPE模式(Pre、Routing、Post、Error)
Pre 阶段:
Route 阶段:
POST Slots: 后置处理
采用这样的设计方式,各个 slot 各司其职,也有较好的可扩展性,如果还想加什么 slot,定义好此 slot 功能,指定好其在调用链中的位置即可。
需要注意的是有些 Slot 的请求结果依赖于前面 Slot 的执行结果,这种情况下需要对前面的执行事件用 Mono 的形式封装起来,这样这些 slot 就构成了一个个的响应式事件流,保证了这些 Slot 都是异步执行的,不会阻塞主线程。
另外注意高亮的 dubboSlot 阶段,在 dubbo 2.7 之前 dubbo 底层返回 Future(会一直占用一个线程轮询结果),对异步编程不友好,2.7 之后返回了 CompleteFuture,与 webflux 的异步编程模型完美结合(发起调用嵌入式网关后立马返回,等调用完成后才执行,是真正的异步)。
嵌入式网关设计
首先我们要明白为啥会有嵌入式网关的需求,主要有三个原因
那么嵌入式网关如何实现呢,业务服务是以 dubbo 服务的形式存在的,而在 dubbo 中有一个 Filter 机制,是专门为服务提供方和服务消费方调用过程进行拦截设计的,每次远程方法执行,该拦截都会被执行。这样就为开发者提供了非常方便的扩展性,所以嵌入式网关的主要设计思路就是自定义 dubbo 的 filter,然后在此 filter 中执行相关的扩展逻辑即可,伪代码如下:
这样通过自定义 filter 的方式我们解决了扩展性的问题,注意我们使用了Activate注解,这样 dubbo 就会把注释的Filter 作为 dubbo 原生的 Filter 自动加载,而不需要显示的配置 provider 或者 consumer 的 filter,也就避免了对代码的侵入性。
这里的业务逻辑执行前后的扩展也是通过责任链的模式来执行一个个的的 slot, 我们先定义好时间戳校验,签名校验,Session转id等 slot, 然后在 xml 中指定这些 slot 的执行顺序
每个业务都有一个 gateway.xml 文件,可以在此文件中配置 H5, app, 小程序需要执行的 slot。
以对 app 请求配置需要执行的前置 slot 和后置处理 slot 为例 ,伪代码如下
这样只要在启动函数中引入(ImportResource)需要支持的 gateway 的 xml 文件,配置的 bean 就能生效,然后在 filter 中会分别取 bizChannel(请求必传,代表是业务哪一端标识,如 biz_h5, biz_app, biz_小程序)对应的 slotBizList 即可执行业务逻辑前后的扩展。
通过这样的方式就有效地指定了业务逻辑执行前后需要执行的 slot,每个业务如果想在业务逻辑执行前后进行扩展,只要定义好自己的 slot 逻辑,在 xml 文件中指定此 slot 的位置即可生效。
嵌入式网关按以上思路实现后,就通过 jar 包分发到各个业务系统。好处是:稳定性提升,每个业务集成一个稳定版本的网关 Jar,某一个业务系统做网关 Jar 升级时,其他业务系统都不受干扰
总结
本文详细介绍了网关的实践思路,相信大家对反应式编程,dubbo filter 等应该有了一定的了解,首先 Java 核心网关作为承载所有流量的入口,必然对其性能有较高的要求,而使用反应式编程的异步非阻塞编程模型能很好地满足我们的需求(关于反应式编程的介绍如有不明白的,可以再看看文末的参考链接,介绍的清晰明了),其次不同业务在业务逻辑执行前后需要做各种各样的扩展,所以我们使用自定义的 filter 实现了这种需求,这种需求显然放在嵌入式网关实现更合理,而让嵌入式网关以 jar 包的形式嵌入业务服务中,做到了对业务层的无侵入,也有较强的可扩展性。
本文转载自微信公众号「码海」,可以通过以下二维码关注。转载本文请联系码海公众号。
分享题目:高性能Java应用层网关设计实践
分享链接:http://www.shufengxianlan.com/qtweb/news40/498890.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联