2 高可靠消息服务

有了API网关,服务商可以很方便获取淘系数据,但是如何实时获取数据呢?轮询!数据的实时性依赖于应用轮询间隔时间,这种模式,API调用效率低且浪费机器资源。基于这样的场景,开放平台推出了消息服务技术,提供一个实时的、可靠的、异步双向数据交换通道,大大提高API调用效率。目前,整个系统日均处理百亿级消息,可支撑百万级瞬时流量,如丝般顺滑。

2.1 总体架构

消息系统从部署上分为三个子系统,路由系统、存储系统以及推送系统。消息数据先存储再推送,保证每条消息至少推送一次。写入与推送分离,发送方不同步等待接收方应答,客户端的任何异常不会影响发送方系统的稳定性。系统模块交互如图5所示。

图5 消息服务总体架构

路由系统,各个处理模块管道化,扩展性强。系统监听主站的交易、商品、物流等变更事件,针对不同业务进行消息过滤、鉴权、转换、存储、日志打点等。系统运行过程记录各个消息的处理状况,通过日志采集器输出给JStorm分析集群处理并记录消息轨迹,做到每条消息有迹可循。

存储系统,主要用于削峰填谷,基于BitCask存储结构和内存映射文件,磁盘完全顺序写入,速度极佳。数据读取基于FileRegion零拷贝技术,减少内存拷贝消耗,数据读取速度极快。存储系统部署在多个机房,有一定容灾能力。

推送系统,基于Disputor构建事件驱动模型,使用Netty作为网络层框架,构建海量连接模型,根据连接吞吐量智能控制流量,降低慢连接对系统的压力;使用WebSocket构建长连接通道,延时更低;使用对象池技术,有效降低系统GC频率;从消息的触发,到拉取,到发送,到确认,整个过程完全异步,性能极佳。

2.2 选择推送还是拉取

在消息系统中,一般有两种消费模式:服务端推送和客户端拉取。本系统主要面向公网的服务器,采用推送模式,有如下优点:

• 实时性高。从消息的产生到推送,总体平均延时100毫秒,最大不超过200毫秒。

• 服务器压力小。相比于拉取模式,每次推送都有数据,避免空轮询消耗资源。

• 使用简便。使用拉取模式,客户端需要维护消费队列的位置,以及处理多客户端同时消费的并发问题。而在推送模式中,这些事情全部由服务器完成,客户端仅需要启动SDK监听消息即可,几乎没有使用门槛。

当然,系统也支持客户端拉取,推送系统会将客户端的拉取请求转换为推送请求,直接返回。推送服务器会据此请求推送相应数据到客户端。即拉取异步化,如果客户端没有新产生的数据,不会返回任何数据,减少客户端的网络消耗。

2.3 如何保证低延时推送

在采用推送模式的分布式消息系统中,最核心的指标之一就是推送延时。各个长连接位于不同的推送机器上,那么当消息产生时,该连接所在的机器如何快速感知这个事件?

在本系统中,所有推送机器彼此连接(如图6所示),构成一个通知网,其中任意一台机器感知到消息产生事件后,会迅速通知此消息归属的长连接的推送机器,进而将数据快速推送给客户端。而路由系统每收到一条消息,都会通知下游推送系统。上下游系统协调一致,确保消息一触即达。

图6 消息事件触发流程

2.4 如何快速确认消息

评估消息系统另外一个核心指标是消息丢失问题。由于面向广大开发者,因此系统必须兼顾各种各样的网络环境问题,开发者能力问题等。为了保证不丢任何一条消息,针对每条推送的消息,都会开启一个事务,从推送开始,到确认结束,如果超时未确认就会重发这条消息,这就是消息确认。

由于公网环境复杂,消息超时时间注定不能太短,如果是内网环境,5秒足矣,消息事务在内存就能完成。然后在公网环境中,5秒远远不够,因此需要持久化消息事务。在推送量不大的时候,可以使用数据库记录每条消息的发送记录,使用起来也简单方便。但是当每秒推送量在百万级的时候,使用数据库记录的方式就显得捉襟见肘,即便是分库分表也难以承受如此大的流量。

对于消息推送事务数据,有一个明显特征,99%的数据会在几秒内读写各一次,两次操作完成这条数据就失去了意义。在这种场景,使用数据库本身就不合理,就像是在数据库中插入一条几乎不会去读的数据。这样没意义的数据放在数据库中,不仅资源浪费,也造成数据库成为系统瓶颈。

如图7所示,针对这种场景,本系统在存储子系统使用HeapMemory、DirectMemory、FileSystem三级存储结构。为了保护存储系统内存使用情况,HeapMemory存储最近10秒发送记录,其余的数据会异步写入内存映射文件中,并写入磁盘。HeapMemory基于时间维度划分成三个HashMap,随着时钟滴答可无锁切换,DirectMemory基于消息队列和时间维度划分成多个链表,形成链表环,最新数据写入指针头链表,末端指针指向的是已经超时的事务所在链表。这里,基于消息队列维护,可以有效隔离各个队列之间的影响;基于时间分片不仅能控制链表长度,也便于扫描超时的事务。

图7 消息确认流程

在这种模式下,95%的消息事务会在HeapMemory内完成,5%的消息会在DirectMemory完成,极少的消息会涉及磁盘读写,绝大部分消息事务均在内存完成,节省大量服务器资源。