vlambda博客
学习文章列表

阿里雷卷:RSocket从入门到落地,RSocket让AJP换发青春


本文来源 | 阿里巴巴中间件


考虑很久,决定还是写一下这篇文章,主要是 AJP 技术太老,我只能说 Long long ago ,估计我在用这个技术的时候,很多同学小学还没有毕业。但是没有问题,这篇文章只是一个架构启发,不会浪费你时间让你学习 20 年前的技术和知识。


Apache JServ Protocol


Apache JServ 协议,简称 AJP ,是一种二进制协议,可以将来自 Web 服务器的入站请求代理到位于 Web 服务器后面的应用程序服务器,部署结构如下:


阿里雷卷:RSocket从入门到落地,RSocket让AJP换发青春

通常我们不希望直接将应用服务暴露到互联网上,有安全问题,当然还涉及到 DNS,IP等问题,我们会做一个互联网请求入口的 Gateway,也就是一个Web服务负责入站请求,然后再转发给内部的Web应用服务器,这样架构就灵活很多。


为何要使用 AJP 这个二进制协议?我们知道 HTTP 1.1 是文本协议,所以解析协议的工作量还是有的,如果 Gateway 的 Web 服务器已经将 HTTP 协议解析啦,为何不复用解析后的结果,形成一个更高效的二进制结构,然后传送给后端的 Web 服务器,这样后端 Web 服务器就会省去解析 HTTP 文本协议这个动作,节约了计算,速度也快啦;


此外 AJP 是长连接,和 HTTP 1.1 的短连接也不一样,可以避免反复的 HTTP 短连接创建,也提高了网络的传输效率,这些就是 AJP 的作用。如果 Gateway 直接是反向代理到后端服务器,还是走普通 HTTP 请求,就会涉及大量短连接、 HTTP 协议重新解析的问题。


当然在实际的开发中,进行 AJP 配置非常少的,大家还是采取的标准的 HTTP 协议的反向代理方式,其中一个主要的原因,就是 AJP 还是有些复杂。首先 gateway 上要配置 AJP ,同时应用还需要提供 AJP 接入能力,如果使用 Tomcat 还好,现在基本都是基于 Netty 的嵌入式 Web Server ,几乎没有人考虑 AJP 这件事情啦。当然我个人也是这样的,早期大家都是 Apache + mod_jk + tomcat 部署的,现在也都是 HTTP 协议的。


负载均衡之动态主机问题


假设我们有了下图的部署结构, Gateway 负责入站请求,然后由 Nginx 进行转发,可以选择 HTTP 1.1 或者 AJP ,都还好。


阿里雷卷:RSocket从入门到落地,RSocket让AJP换发青春

上图的这个结构,大家会发现要解决一个问题,就是维护后端服务列表的问题,也就是 Nginx 中所说 Upstreams 主机动态维护的问题,看一个典型的 Nginx 配置,如下:


阿里雷卷:RSocket从入门到落地,RSocket让AJP换发青春

如果 appservers 背后的应用有上线下线的问题,那该怎么办?也好办,就是通知 Nginx ,动态更新 upstream 对应的主机列表,这样就是可以啦。这个特性,你需要购买 Nginx Plus,当然也有一些 Nginx 开源的方案,都会提供对应的 upstream 主机动态更新的特性。


当然如果你的服务架构中有服务注册中心,如采用 Spring Cloud Gateway 架构,如果有了 Eureka 等服务注册中心,那么就不用担心这些 upstream 主机动态维护的问题,服务注册中心就会解决这个问题,如下图:


阿里雷卷:RSocket从入门到落地,RSocket让AJP换发青春


RSocket 和 AJP 整合


我们都知道 RSocket 采取是的外连方式,就是我不提供端口监听,我会连接到一个 Broker ,然后 Broker 来帮助我处理入站请求。借助 RSocket 这一模式,我们将 Gateway 的模式调整为如下:


阿里雷卷:RSocket从入门到落地,RSocket让AJP换发青春

首先我们通过 Webflux 对外提供 HTTP 访问需求,这个是异步化的。当然 Webflux 会默认解析 HTTP 头, Body 设置为不解析,还是 Netty 的 ByteBuf 。接下来我们将 HTTP 请求转为换为 AJP 的数据结构,其实就是上面讲到那个高效的二进制结构,大概的结构如下:


阿里雷卷:RSocket从入门到落地,RSocket让AJP换发青春

接下来 Gateway 会将 AJP 的二进制结构体添加到为 RSocket Payload 的 header 中,将 HTTP Body 设置为 Payload 的 data ,然后根据虚拟主机名或服务名从连接到 Gateway 的 RSocket 连接中找到对应的 TCP 连接,然后将这个 RSocket Payload 发送给后端 Web 服务器。


后端 web 服务器在收到 RSocket 请求后,然后读取出 AJP 数据,构建出内部的 HTTPRequest 对象,然后转发给对应的 HTTPHandler 完成 HTTP 请求处理,最后将返回的 HTTPResponse 对象再进行 AJP 处理,构建出 Payload ,返回给 Gateway ,然后 Gateway 再解析 AJP ,输出 HTTP Response ,当然这个也是标准的 AJP 流程。


这里我们进行了一些调整,传统的是给 Web 应用配置 AJP 监听端口,相当于 AJP Server ,接受 AJP 请求,现在调整为 RSocket ,没有监听端口,而是直接连接到 Gateway 。


RSocket AJP 这种架构有什么好处?


无监听端口:RSocket 采用的是外连的方式,本地并没有启动 HTTP 端口和 AJP Server端口,这样比较安全,同时也节省了系统的资源。


负载均衡简单:由于后端 web 服务器都主动连接到 Gateway 上啦,而且提供了对应的元信息,如对应的域名等, Gateway内部就建立好路由表啦,不需要服务注册中心等接入,当然也不需要你手动维护,都只自动化的,只要控制应用上下线就可以。


AJP 序列化方式非常高效,这个前面说过,对比 HTTP 解析,这个性能不用说啦。

部分 Zero Copy 支持:如 HTTP Body 这部分,基于 Netty 的 ByteBuf,这个是完全没有问题的,不需要反复的内存 copy ,而且 RSocket 是直接支持 Netty 的 ByteBuf 构建 Payload 的。


长连接支持:RSocket 是长连接的,这个和 AJP 是一致的,不用在担心 HTTP 短连接搞出的 TIME WAIT 问题啦,而不用搞什么 TIME WAIT 优化等,默认就可以啦。你不相信 TIME WAIT 问题?你在 ATA 上搜索一下试试,都有 1274 篇文章,不我不知道有多少同学碰到过,反正我不止一次啦。


非常好的灵活性:gateway 已经进行了 http 解析,我们经常说的 session sticky ,也就是根据 cookie 绑定到某一台 backend server ,这个就非常容易实现。


这种方式非常有灵活性,开发阶段打开 HTTP 服务,直接 HTTP REST API 测试,这些都没有问题,在上线后,只要开启 RSocket ,然后连接到 Gateway Broker 上就可以,然后这一切都是自动化的。


拓扑扩展延伸


当然这个架构,还可以扩展到各种部署结构上,如 Kubernetes 上,你不需要什么 ingress ,容器启动后直接连接得到 gateway broker 就可以啦,只需要提供 web 应用对应的接入域名或者服务名就可以啦,也不需要你创建什么服务名, DNS 等。这种方式完全对网络和运维系统无任何要求,无论你使用任何容器管理系统都可以。


多语言扩展


由于 AJP 是标准的协议,所以同样可以套用在其他的语言开发上,其实就是减少 HTTP 协议解析,然后从 AJP 中构建出 HTTP 请求,然后安装标准的 Web 框架处理就可以。如和 JavaScript 结合时, 你完全基于 AJP 构建出 Request 对象,然后交给特定函数处理,其实就是遵循 Service Worker 规范。


阿里雷卷:RSocket从入门到落地,RSocket让AJP换发青春

其他语言,也都有对应的 webserver interface 规范,如 Ruby的 Rack , Python 的 WSGI 等,主要 AJP 和这些规范对接即可。


总结


借助 RSocket 的架构提供,我们可以将之前比较复杂的方案简化,当然最最重要的是性能的提升,即便之前的一些性能提升技术点,可能由于一些约束等,现在和 RSocket 对接,那些问题都不存在啦。


有同学可能问,要实现这个架构复杂否?如果你基于 Spring 架构的话,我可以说任何人都能开发出来。你只需要创建一个 Spring Boot 应用,启动 RSocket 监听,然后其他 Spring Boot Web 应用通过 RSocketRequester 连接上来,接下来就是一些AJP相关的编解码工作,然后调用一下 Spring Web 提供的 HTTPHandler 接口,就这些工作量,Spring RSocket 已经提供对应的功能。


更多精彩


阿里雷卷:RSocket从入门到落地,RSocket让AJP换发青春





 点此阅读作者更多好文!