5 从开源网关到自研网关
5.1 前言介绍
5.1.1 Socket
传统的HTTP协议,是基于TCP/IP的应用层协议,该协议被广泛应用于Web资源的获取。HTTP协议是一种基于请求/响应模式的协议,由客户端主动向服务器发送请求,拉取数据实现半双工通信。因此,在该协议下,服务器端难以直接向客户端推送消息。
Socket协议是一种在HTTP底层的TCP连接上建立一个全双工通信的协议。通过双向的通信连接,网络上的两个程序可以实现数据交换,通信连接的每一端都是一个Socket。一次连接多次使用,当客户端与服务器端建立连接后,并不会马上关闭连接,后续客户端发送请求以及服务器响应数据都使用该连接进行通信。
Socket协议依赖HTTP协议进行第一次握手,在客户端与服务端建立一个不受限的双向通信通道,客户端和服务端就可以在任意时刻发送消息给对方。握手成功后数据就直接从TCP通道传输,传输数据时与HTTP无关,图5.1是Socket协议握手过程:
图5.1 Socket协议握手过程图
5.1.2 Socket网关
Socket网关是介于Socket客户端、Socket服务端之间的服务器,与普通的Web网关服务器不同,Web网关服务器使用短连接,即客户端请求服务器,服务器响应完成后就会断开并释放连接;Socket网关服务器使用长连接。
Socket网关作为客户端与服务端的通信中介,主要提供认证鉴权、安全过滤、服务管理、消息路由、长连接管理、消息送达等功能。客户端发起建立Socket连接请求时,Socket网关统一对来自客户端的Socket接入请求进行身份认证与鉴权。Socket网关还能够对客户端消息进行消息路由,即通过路由算法将客户端消息转发至相应服务器。同时,还需要作为服务器的消息中转器,将服务器消息转发至与网关服务器连接的所有客户端。
Socket客户端与Socket网关服务、Socket网关服务与Socket服务端之间通过Socket协议进行通信,图5.2为Socket网关模型图。
图5.2 Socket网关模型图
5.2 技术选型
5.2.1 Netty选型
Socket实现方式有很多种方式,常见实现方式有:Netty、Jetty、Undertow、Spray等,针对常见实现方案对比如表1所示。其中Netty是一个高性能、事件驱动、异步非阻塞的网络通信框架,可以处理大量用户的并发请求,且能够快速搭建高性能、高可靠性的网络应用程序框架。通常Netty框架也可作为与设备保持长连接的通讯网关。
表1 Socket实现方式对比
实现方式 |
优点 |
缺点 |
Netty |
多协议,功能强大;定制能力强;性能高;安全可靠;成熟稳定;应用广泛 |
相对复杂,不易于调试 |
Jetty |
支持高并发;资源使用率较低 |
依赖于Jetty容器;内存占用较大,会导致频繁垃圾回收;存在并发瓶颈 |
Undertow |
支持高并发;性能高;资源使用率较低 |
依赖于Undertow容器;内存使用率相比Netty占用较大 |
Netty是目前业界最流行的NIO框架,鉴于其功能、性能、健壮性、可定制性和扩展性,现已得到成千上万商业项目的实践应用。Netty采用三层网络架构模式,从上到下依次为:Reactor调度层、PipeLine责任链层、Service业务逻辑层,能够共同提升数据接收的效率,Netty网络架构模式如图5.3所示:
图5.3 Netty网络架构图
5.2.2 Spring Boot选型
Spring Boot是基于Spring4搭建的微框架,其宗旨是为了简化Java企业级应用开发。Spring Boot继承了Spring框架的核心特性及相关扩展功能,简化了对Spring已有技术的使用,无须繁杂配置即可快速构建应用程序。Spring Boot是开发单一服务框架的基础,Spring Cloud也建立于Spring Boot框架基础上,利用Spring Boot的开发便捷性简化了微服务基础设施的开发,提供了一套微服务开发工具包,用于增强基于Spring Boot创建的应用程序。
5.2.3 Rocket MQ选型
通常服务端会主动向客户端发送数据,使用WebSocket协议可以实时的完成服务端向客户端推送数据的操作。Socket网关与服务端之间的通信则通过消息队列机制来实现。图5.4为Socket网关与客户端、服务端推送数据通信模型。
图5.4 Socket网关推送数据通信模型
服务端与Socket网关之间选择轻量级的事件广播方案,Socket网关与服务端则基于消息队列的事件监听和处理。其中,实现广播可以选择基于RocketMQ的消息广播、基于Redis的发布/订阅、基于ZooKeeper的通知等方案,其优缺点如表2常用事件广播方案比较所示。
表2常用事件广播方案比较
|
优点 |
缺点 |
RocketMQ |
吞吐量高、高可用、保证可靠 |
实时性不如Redis |
Redis |
实时性高、实现简单 |
不能保证消息可靠性 |
ZooKeeper |
实现简单 |
写入性能较差,不适合频繁写入场景 |
5.2.4 小结
本方案采用基于Netty框架的WebSocket协议技术作为Socket网关的实现,其中技术选型特点如表3所示。
表3技术选型
|
选型 |
特点 |
Socket网关 |
Netty(Spring Boot+Netty) |
轻量,高并发,传输快,多路复用,异步非阻塞等 |
客户端通信 |
WebSocket |
长连接,减少网络交互,减少连接次数,推送数据 |
服务间通信 |
HTTP REST、MQ |
简单、可扩展,业务解耦 |
语言选择 |
JAVA |
支持JAVA实现,与平台无关 |
5.3 方案选择
5.3.1 OpenResty
OpenResty 是一个基于 Nginx 与 Lua 的高性能、可伸缩的 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。其中采用OpenResty开发的网关,使用较流行的有Apache Apisix、Kong等。
基于OpenResty 开发的优缺点及适用场景如下:
OpenResty具有高并发、热更新等特点。OpenResty提供了大量组件如Mysql、Redis、Memcached等,使其在Nginx上开发Web应用更方便更简单。同时,借助于Nginx的事件驱动模型和非阻塞IO,可以实现高性能的Web应用程序。OpenResty适用于处理准入控制和安全检查,请求限流等场景,比如:新浪微博采用OpenResty 构建了上行请求和下行推送的一站式应用网关,新浪新闻、微博广告、天气通也同时在使用OpenResty;京东的实时价格、秒杀、动态服务、单品页、列表页等都在使用Nginx+Lua架构;美团网内部的Oceanus也是基于Nginx和ngx-lua扩展实现的,主要提供服务注册与发现、动态负载均衡、可视化管理、定制化路由、安全反扒、Session ID复用、熔断降级、一键截流和性能统计等功能;其他公司如淘宝、去哪儿网等。
使用OpenResty作为Socket网关的不足之处:开发层面,需要Lua开发,定制化开发成本较高,可维护性较差,不便于程序调试;业务层面,不适用于处理复杂的业务逻辑,如单聊、群聊、广播、组播等复杂通信场景,及长时间阻塞调用的过程;技术人员层面,需要开发人员精通Lua,LuaJIT,以及 Nginx等相关技术栈。
5.3.2 Spring Cloud Gateway
Spring Cloud Gateway是Spring官方提供的基于Spring 5、Spring Boot 2.0、Project Reactor等核心技术开发的网关框架,其核心目标是为微服务架构的应用提供简单、有效和统一的API路由管理方案,还用于替代Netflix的网关组件Zuul。它可以在 Web 协议(如 HTTP 与 WebSocket)与内部使用的非 Web 友好协议之间转换。
5.3.3 Netflix Zuul 2.x
Zuul是Netflix开源的网关服务组件,是一个提供了微服务路由、负载均衡和过滤器解决方案的边缘服务框架,它的主要设计目标是动态路由、监控、弹性和安全。Zuul 1.x基于同步的I/O模型实现,但其不支持长连接;目前,多数大型互联网公司基于Zuul 1.x来实现API网关服务,比如携程(日流量超 50 亿)。Zuul2.x是Zuul1.x升级版本,基于Reactor模式,选择成熟的Netty 框架实现了异步非阻塞,引入了事件、总线、队列机制,事件环处理,并加入了对WebSocket的支持,但Spring尚未有引入计划,商业应用成熟度不高。
5.3.4 WebSocket实现框架
在2.2节中介绍了Spring Boot选型的相关内容,其中实现WebSocket 的框架,比较主流的有Netty、SocketIO、WebSocket容器三个框架,并对这三个框架从易用性、协议支持、性能、业务场景等方面进行比较,具体如表4所示:
表4实现WebSocket的框架比较
|
WebSocket容器 |
Netty-SocketIO |
Netty |
易用性 |
简单 |
简单 |
中等 |
协议支持 |
WebSocket |
WebSocket、Polling |
多协议 |
代码开发量 |
少量 |
少量 |
较多 |
容器支持 |
需要 |
不需要 |
不需要 |
性能 |
一般 |
高 |
高 |
业务场景分析 |
适合用户连接数较少、业务不复杂的服务 |
适合用户连接较多及对性能有要求的服务 |
适合平台型服务;适合用户连接较多及对性能有要求的服务; |
优势 |
可支持HTTP与WebSocket两种协议;与Spring结合非常方便 |
框架内部封装较好,拿来即用;多语言客户端支持 |
灵活性强;可定制化,二次开发;支持多种协议 |
劣势 |
强依赖容器性能 |
不支持HTTP协议 |
研发成本高,需要手写较多代码 |
(1)基于支持WebSocket 的容器,开发简单,例如Tomcat、Jetty、Undertow;但在高并发方面表现较差,存在并发瓶颈,连接的时会出现断开情况,强于依赖容器,可扩展性差。
(2)Netty-SocketIO是基于Netty框架基础上的封装,效率同Netty 一样,是一个全平台方案,提供API支持。京东Logbook也是用了SocketIO 来传递日志。
(3)Netty 是业内主流的NIO框架,Netty对Java NIO 做了封装,让开发者更多关注业务,降低开发成本。很多著名的 RPC 框架都采用了Netty作为传输层,功能非常强大,内置了很多编解码协议,实现WebSocket 协议十分方便。
Netty的高性能是基于Reactor模式的线程模型,Reactor模式采用IO多路复用结合线程池的设计思想,如图5.5 Reactor模型所示;而Netty对Reactor 线程模型的支持提供了灵活的定制能力,所以可以满足不同业务场景的性能诉求。
图5.5 Reactor模型
5.3.5 综合比较
本章节对常见的Socket网关实现方案进行了详细调研,其中各方案优缺点综合比较如表5所示。
表5常见的Socket网关方案比较
5.4 技术实现方式
5.4.1 架构设计
根据第二、三章节的技术选型与方案对比,对不同的Socket应用场景均使用基于Netty框架技术构建Socket网关,具体可划分为Netty Socket网关和WebSocket路由网关,如图5.6所示。客户端发起的Socket请求由Socket网关负责统一接入,以加强系统安全性。
图5.6 Socket网关架构设计
5.4.2 服务化拆分
5.4.2.1 基于Netty的Socket网关
基于Netty的Socket网关,其运行模式如图5.7所示,其主要职责如下:
(1)负责建立及保持与客户端之间的长连接;
(2)接收客户端发起请求,并对客户端请求进行解码处理;
(3)处理高并发上下行通信,提供通信加解密、白名单过滤等基础能力;
(4)能够支持消息队列、Restful接口为应用服务器提供数据推送通道,网关服务器接收应用服务器数据,并通过Socket通道推送给与网关服务器建立Socket连接且在线的客户端。
图5.7 基于Netty的WebSocket网关运行模式
5.4.2.2 Websocket路由网关
WebSocket路由网关,可采用Spring Cloud Gateway实现,其运行模式如图5.8所示,其主要职责如下:
(1)接收客户端请求,并将客户端请求转发至目标Socket服务器;
(2)保持长连接,并透传数据;
(3)实现对Socket服务端的负载均衡
该模式适用于Socket客户端与Socket服务端通信,WebSocket网关完全起到中介作用,通过路由将数据透传给目标Socket服务端。
图5.8 基于Spring Cloud Gateway的WebSocket网关运行模式
5.4.3 功能模块
根据对Socket网关功能特性分析,将Socket网关整体功能划分模块如图5.9所示:
图5.9 Socket网关整体功能模块
5.4.4 自定义通信协议
通信模块是Socket网关的核心,Socket网关采用基于TCP协议的长连接通信方式,而由于TCP数据传输是无边界的字节流传输形式,接收方接收到的数据流可能是半个数据包,也可能是多个数据包黏在一起,即TCP的黏包/拆包问题。为解决这个问题,需要网关层设计消息的边界,因此对于Socket网关架构中的通信协议采用自定义消息协议,以保证数据的完整性和安全性。消息协议主要包括协议头和消息体两个部分,如图5.10所示,数据的编码和解码均按此协议进行解析,具体每个字段解释如下:
图5.10自定义消息协议格式
l command:8bit,指令编码,客户端请求服务端时,网关根据command寻址;
l version:2byte,版本号
l security:8bit,安全位,用于识别该协议是否加密
l length:4byte,消息内容长度
l data:长度未知,消息内容,由length指定
5.5 关键功能设计
5.5.1双向心跳检测机制
心跳是指在TCP长连接中客户端和服务端定期的互相发送数据包;心跳检测机制是发送方(客户端或服务端)按照一定规则(周期性发送、空闲发送等)向接收方(客户端或服务端)发送固定格式的消息,接受方收到消息后回复一个固定格式的消息,如果长时间没有收到,比如心跳周期的3倍,则认为当前连接失效,并主动将其断开,如图5.11所示。
心跳检测通常用于判断长连接是否存活:当长连接没有流量时,无法判断是通信异常引起还是通信正常没有业务流量引起,通过发送心跳包进行判断。
图5.11 心跳检测机制
5.5.2 拉取模式与推送模式
推送数据模式是由服务端通过Socket网关主动向客户端(在线)发送数据的过程,其推送方式可通过接口或消息队列实现;同时,还需要网关负责整合各类厂商消息通道,其运行流程如图5.12推送模式所示。
图5.12 推送数据模式
拉取数据模式是由客户端主动发送指令,Socket网关根据指令匹配到对应的业务服务API,并使用HTTP短连接方式,请求目标业务服务获取数据的过程。其运行流程如图5.13拉取模式所示。
图5.13 拉取数据模式
5.5.3 离线模式
离线模式是在推送模式基础上的扩展,业务服务通过接口或消息队列向客户端推送数据,当客户端不在线时,Socket网关将无法向客户端正常推送消息。为了确保消息能够安全地送达客户端,应由Socket网关结合离线消息处理机制,将消息进行暂存,在客户端重新上线时,采取拉取或再次推送的方式将数据推送到客户端。如图5.14离线数据模式。
图5.14 离线数据模式
5.6 应用场景
5.6.1 消息推送/提醒
唯品会采用Netty作为底层通信协议设计了消息网关,以作为消息发送的总入口,对接上游各个业务系统,为业务系统提供友好的发送受理服务。其消息网关实现了基于优先级队列的消息分发,反馈统计,延时发送,订阅控制,以及其他一些辅助功能。
爱奇艺号是爱奇艺内容创作、分发和变现的平台,涵盖自媒体、网大、网剧、儿童、知识、纪录片等多个业务,是爱奇艺内容生态的重要组成。爱奇艺号作为前台系统,对用户体验有较高要求,直接影响着创作者的创作热情。目前,爱奇艺号有多个业务场景中用到了WebSocket推送技术,包括:
l 用户评论。实时的将评论消息推送到浏览器。
l 实名认证。合同签署前需要对用户进行实名认证,用户扫描二维码后进入第三方的认证页面,认证完成后异步通知浏览器认证的状态。
l 活体识别。类似实名认证,当活体识别完成后,异步将结果通知浏览器。
目前WebSocket长连接网关已在爱奇艺号图片滤镜结果通知、MCN电子签章等多个业务场景中得到应用。
5.6.2 网页游戏
网页游戏通常需要游戏服务器主动推送消息客户端,例如广播消息;游戏服务器对即时性要求高,通常采用客户端与服务端Socket直连的架构模式。而直连架构具有一定的缺陷和不足,比如:安全方面,容易被第三方拦截抓包,造受外部恶意攻击;业务方面,区服之间无法相互通信,无法实现跨服战场等业务需求;客户端或服务端需要额外实现负载均衡器。
网关服务器的主要职责是将客户端和游戏服务器隔离,客户端程序直接与这些网关服务器通信,并不需要知道具体的游戏服务器内部架构,通过网关服务器转发数据包间接地与游戏服务器交互。同样,游戏服务器也不直接与客户端通信,发给客户端的协议都通过网关服务器进行转发。如图5.15 分区服游戏架构模型。
图5.15 分区服游戏架构模型
5.6.3 物联网
Socket网关也被用于物联网,比如设备控制(扫码开锁或者扫码乘车),智能家居、城轨列车运行监测等。其中以北京地铁7号线的城轨列车地面在线监测与分析系统为例,其架构如图5.16所示,该系统负责接收、解析、存储城市轨道交通车辆的状态信息数据,在此基础上进行数据应用与分析。平台主要分为 4 个层级,包括数据接入层、数据缓冲层、数据解析层、数据存储层与数据应用层,其中数据接入层的功能主要由地面数据网关实现,网关负责接收和解析城轨列车传送的数据。
图5.16地面在线监测与分析系统架构
5.6.4 即时通讯
在即时通讯架构中,网关承担着与异构即时通信系统的互联互通,网关接入层负责维护与客户端之间的长连接,它是唯一一个与客户端进行直接通信的服务入口。