导航菜单
路很长,又很短
博主信息
昵   称:Cocodroid ->关于我
Q     Q:2531075716
博文数:356
阅读量:1744970
访问量:218075
至今:
×
云标签 标签球>>
云标签 - Su的技术博客
Tags : 亿级流量,架构,API网关发表时间: 2021-11-03 14:18:02

作者:张松然 松然聊技术

这不是一个讲概念的专栏,而且我也不擅长讲概念,每一篇文章都是一个故事,我希望你可以通过这些故事了解我当时在实际工作中遇到问题和背后的思考,架构设计是种经验,我有幸参与到多个亿级系统的架构设计中,有所收获的同时也希望把这些收获分享与大家。

承接上篇,网关是负责接口调用获取请求数据的,HTTP 属于单向操作,建客户端与服务平台的 TCP 双向通道,保持客户端与服务平台的会话状态,则可以提供更多、更灵活的技术实现和业务实现。在业务服务调用上通过 HTTP 网关,在平台服务调用上则通过 TCP 网关,实现平台与业务解耦,并且平台采用 TCP 通道还可以增加对平台的控制力,如下图所示:

图片

本文继续讲述构建 TCP 长连接网关的故事。

1。Session 管理

网关为什么需要 Session 管理?因为 HTTP 网关是单向通信的短连接,是无状态的;而 TCP 网关是双向通信的长连接,是有状态的。所谓有状态,简单的理解,用户在 Web 网站登录会后浏览器会基于 Session 缓存用户的认证信息,以实现用户有状态下的服务访问。HTTP 网关 API 的安全访问目前是基于 OAuth2 技术,而 TCP 网关则可以通过长连接实现访问鉴权后基于 Session 管理实现 API 有状态下的服务访问。而且 TCP 属于 OSI 的传输层,所以建立 Session 管理机制构建会话层来提供应用层服务,可以极大的降低系统复杂度。

TCP 网关的 Session 是客户端与服务端建立的一次会话链接,会话信息中保存着 SessionId、连接创建时间、上次访问事件,以及 Connection 和 SessionListener 对象。因为 TCP 网关采用 Netty 框架,所以 Session 中的 Connection 保存了 Netty 的 ChannelHandlerContext 上下文信息。所有连接的 Session 会话信息会保存在 SessionManager 内存管理器中。

TCP 网关通过在 Netty 的 ChannelPipe 中加载自定义 ChannelHandler,构建 Container 容器,将每个 TCP Connection 封装到一个 Session 会话中,保存在 Container 容器中,由 Container 容器构建 Session Layer 提供 Logic 请求调用,如下图所示:

图片

每一次 Session 的会话请求(ChannelRead)都是通过 Proxy 代理机制调用 Service 层,数据请求完毕后通过写入 ChannelHandlerConext 再传送到 Channel 中。同样,数据下行主动推送也是如此,通过 Session Manager 找到 Active 的 Session,轮询写入 Session 中的 ChannelHandlerContext,就可以实现广播或点对点的数据推送逻辑。

数据上行

数据上行特指从客户端发送数据到服务端,数据从 ChannelHander 的 channelRead 方法获取数据。数据包括创建会话、发送心跳、数据请求等。这里注意的是,channelRead 的数据包括客户端主动请求服务端的数据,以及服务端下行通知客户端的返回数据,所以在处理 object 数据时,通过数据标识区分是请求-应答,还是通知-回复。

数据下行

数据下行通过 MQ 广播机制到所有服务器,所有服务器收到消息后,获取当前服务器所持有的所有 Session 会话,进行数据广播下行通知。如果是点对点的数据推送下行,数据也是先广播到所有服务器,每天服务器判断推送的端是否是当前服务器持有的会话,如果判断消息数据中的信息是在当前服务,则进行推送,否则抛弃。

2。心跳

心跳是用来检测保持连接的客户端是否还存活着,客户端每间隔一段时间就会发送一次心跳包上传到服务端,服务端收到心跳之后更新 Session 的最后访问时间。在服务端长连接会话检测通过轮询 Session 集合判断最后访问时间是否过期,如果过期则关闭 Session 和 Connection,包括将其从内存中删除,同时注销 Channel 等。

上文已提到,TCP 网关长连接容器的 Handler 就是放在 Pipeline 的中,所以,每一个 Channel 对应一个 Connection,一个 Connection 就对应一个 Session,Session 由 Session Manager 管理,Session 与 Connection 是一一对应,Connection 保存着 ChannelHandlerContext(ChannelHanderContext 可以找到 Channel),Session 通过心跳机制来保持 Channel 的 Active 状态。

当然,除了这种方法之外,还可以通过在服务端基于 Session Manager 发送心跳包检测客户端是否还活着,如果尝试无响应,则关闭 Session 和 Connection。

除此之外,Netty 框架也提供了 IdleStateHandler 方法,IdleStateHandler 会检测 channelRead 和 write 方法多久没有被调用了,如果长时间没有调用,就会调用 userEventTriggered 方法。

3。断线重连

客户端与服务端通过 TCP 长连接进行通信,但在中国复杂的网络环境下,移动客户端可能由于网络抖动、弱网络情况下,遭遇非正常网络闪断,如何处理断开后的断线重连?TCP 长连接断开之后,如果重连?如果处理断开后的连接再重连后,找到上一次连接的会话呢?

这个解决得益于 Netty 的 ChannelHandlerContext,它可以存储一些自定义属性到 Channel 的上下文中。在每次成功建立请求的 Channel 里存入 SessionId,将它作为一个钩子,当网络闪断后,再次请求建立连接的 Channel,可以通过判断是否存在 SessionId 的钩子,进行处理。

图片

客户端每通过 TCP 与服务端进行一次建连,都会在服务容器里创建一个 Session 会话,该会话保存 Connection 的句柄,对应 Netty 的一个 Channel 通道。建连成功后,通过定时的心跳保持 Channel 属于 Active 活跃。但客户端进入弱网络环境下,客户端可能已经掉线,但并未向服务端主动发送关闭 Channel 请求,而服务端仍认为该 Channel 仍存活。直到在由服务端的会话存活检测机制检测到 Channel 已经 InActive,才会由服务端销毁该 Channel。

服务端的会话存活检测是 5 分钟一次,所以存在客户端掉线后,在 5 分钟内又重新建连,而这时服务端的建连逻辑,不是重新创建一个 Session,而是去寻找上一次的 Session,并更新标识存活。具体的实现是在每次建连的 Channel 里存入 SessionId,当网络闪断后,判断 Channel 是否存在 Session,之所以实现是得益于 Netty 的 ChannelHandlerContext,可以存储一个自定义属性到 Channel 的上下文中。

当然,TCP 网关一定是集群,所以,断线重连也是极有可能请求到不同的服务器上,而这种情况按照新 Connection 创建的 Session 处理,只有出现重连到同一服务器时,才需要考虑上述的处理逻辑。

4。总结

言而总之,本篇文章重点讲述了 TCP 网关的心跳、Session 管理、断线重连。下篇文章,我将介绍架构演进重构消息 PUSH 系统。如果你觉得有收获,欢迎你把今天的内容分享给更多的朋友。

5。扩展阅读

故事1:从零构建亿级流量API网关
01 | API网关:统一接入、分层架构、高可用架构
02 | 流量调度:配置中心、泛化调用

故事2:架构演进构建TCP长连接网关
03 | TCP网关:Netty框架、Protobuf格式、业务线程池
04 | TCP长连接:心跳、Session管理、断线重连

故事3:架构演进重构消息PUSH系统
05 | 消息PUSH:消息推送、消息送达率、APNs

故事4:从焦油坑爬出来的交易系统
06 | 交易平台:订单管道、订单状态机、服务编排、任务引擎
07 | 微服务化:服务治理、领域设计

故事5:烦人的焦油开始到处都是
08 | 新老系统:业务整合、数据融合、系统迁移
09 | 高可用架构:隔离部署、系统监控与日志、可灰度、可降级

故事6:稳定性架构与大促保障
10 | 大道至简:系统复杂度、三明治架构
11 | 大促保障:自动化测试、故障演练、性能压测

...阅读原文
推荐文章