1. 引言
随着互联网技术的发展,大量公司的业务都在进行数字化转型,网络通信状态作为基础设施,业务闭环的开展必须依赖于稳定的网络通信状态。针对网络状态的监控是信息化服务管理者需要关注的核心问题,同时也是衡量服务稳定性的重要指标 [1] 。心跳检测是目前已广泛用于网络通信服务、故障检测,连接维持等领域的一项技术 [2] 。
2. 研究目的
为了保证部署在客户端计算机上的业务程序能够正常的与服务器进行数据交互,保证业务正常运行,设计一套监控系统。该监控系统通过在服务器与客户端之间传递心跳包数据,应用一定的算法判断服务器与客户机之间的连接状况,来监测客户端网络节点的通信状况,网络通信状态作为业务数据交换的基础设施,需要监控系统的稳定性和高可用性来保障整个通信的通畅性,可用性及故障快速感知。
2.1. 业务背景
对于某集团下分布在全国区域连锁商店内的网络状态进行监控,监控中心可以实时了解商店节点的网络状态,当网络出现异常时(如网络抖动造成的用户数据丢失,查询失败 [3] )触发监控机制,通知相关的工作人员及时处理和修复。对于网络异常,可以尽早发现并修复,预防大面积断网等极端情况的经济损失和其它负面影响,确保业务正常进行。
2.2. 设计目标
1) 通过消息队列组件解耦业务与数据存储,通过Redis存储需要处理的业务对象模型,实现数据库与业务数据隔离。
2) 监控中心的分析服务能够稳定执行,支撑万余节点的线上使用场景。
3. 研究方法
监控中心的网络检测依赖于心跳包的传送,心跳包的设计思路来源于分布式系统中网络节点数据同步的实现方式。
心跳一般是指某端(绝大多数情况下是客户端)每隔一定时间向对端发送自定义指令,以判断双方是否存活,因其按照一定间隔发送,类似于心跳,故被称为心跳指令 [4] 。
Redis Sentinel是Redis高可用的实现方案。Sentinel称为哨兵,是一个管理多个Redis实例的工具。这种模式下,Sentinel会不断的检查主服务器和从服务器是否正常运行。
默认情况下,每个Sentinel节点会以每秒一次的频率对Redis节点和其它的Sentinel节点发送PING命令,并通过节点的回复来判断节点是否在线。
如果在持续的几毫秒之内,Sentinel没有收到目标节点的有效回复,则会判定该节点为主观下线。
我们把分布在全国各地的连锁商店抽象成网络拓扑中的一个个节点,同时拥有一个集中式的监控中心,监控中心就是中央节点,总体采用星型拓扑结构。
要达到查看各个节点的网络状态,需要各个分节点通过心跳包的方式定时上报各自状态。服务监控中心对于每个节点的心跳包进行单独存储,分析,并汇总结果。
3.1. 系统结构组成
系统主要由客户端节点,云端监控服务中心,监控中心控制台三部分组成,见图1。主要采用PHP语言开发,基于LAMP环境进行部署和生产运行。
客户端节点依靠本身应用程序间隔5 s,上传数据到监控中心的Redis服务器。
监控中心服务按照每个节点对数据包进行分组,实时进行分析,如果在持续的两分钟之内,没有接收到节点的心跳包,则认为此节点网络中断。
监控中心控制台是一个UI系统,用于业务人员查看节点网络状态。
3.2. 实现原理
以往的心跳检测技术大多是基于Socket编程接口实现 [2] 。
完整的套接字格式{protocol, src_addr, src_port, dest_addr, dest_port}。这常被称为套接字的五元组。其中protocol指定了是TCP还是UDP连接,其余的分别指定了源地址、源端口、目标地址、目标端口。TCP/IP协议集提供了可供程序员网络开发所用的接口,即Socket编程接口。
本中的检测主要依赖httpapi接口传递,在数据包采集阶段客户端和服务端不需要保持长连接和强关联关系。
系统将需要处理的业务场景抽象成任务,以商店为维度,为任务编号,通过实时处理任务集合的方式实现商店网络状态监控。采用NoSql数据库Redis提供的消息模型作为基础,实现消息队列。
一个是使用生产者消费模式模式,另外一个方法就是发布订阅者模式。一个List或者sort集合就是一个队列,本文中我们采用生产者消费模式实现消息队列。出于避免客户端与服务器端建立长连接需要额外网络消耗的考虑,本文中采用生产消费实现轻量级的消息队列。
消息队列中每一个消息都会被分配唯一标记。唯一标记以业务组件名称加时间戳的生成规则,确保系统唯一。
在客户端节点数据包采集阶段,只需要各个客户端节点安装一个小型常驻代理服务,定时向服务中心发送http接口请求,时间间隔为5s,以json为数据格式,节点编号mall_id作为key,值为当前格式化时间,如(2019-08-02 17:44:30)。采集到的数据点在服务监控中心异步进入Redis消息队列,等待消费程序按照规则分析,从而得出网络状态。
假设服务端域名为center.com,http客户端请求报文如下,见表1。
数据点队列按照节点编号做隔离,以节点编号区分不同的队列,为了降低Redis队列的存储消耗,每个队列仅保存最近20条数据点。
3.3. 数据模型
客户端节点心跳包数据结构,见表2。

Table 2. Heartbeat package message
表2. 心跳包数据结构
客户端节点编号结构,请见图2。
4. 主要研究内容
本文的研究内容主要围绕心跳包数据收集,监控中心数据存储设计,检测任务的数据模型及整个任务生命周期进行。上文提到的数据模型中,客户端节点心跳包在redis数据库中以string类型存储,检测任务以sortset和string结合的方式存储。消息的生成与流转涉及到的基本概念和任务生命周期在本节中详述。
4.1. 基本概念
· Job
需要异步处理的任务,每一个网络节点的网络状态分析会构建成一个任务,Job是延迟队列里的基本单元。与具体的Topic关联在一起。
· JobId
任务编号,由业务使用方决定的,一定要保证全局唯一性。唯一标记以业务组件名称加时间戳的生成规则,确保系统唯一,即realtime_netstate_timstamp。
· Topic
业务主题,一组相同类型Job的集合(队列)。供消费者来订阅或者消费。本文中的实时网络监控就是一个Topic,我们定义为 realtime_netstate。
Topic具有很强的业务属性,本文中业务涉及连锁零售集团下的所有实体店面的网络状态,网络状态可以认为是一个业务,而分析的领域对象是所有的对象。
· DelayJob
需要延迟的时间。单位:秒。(服务端会将其转换为绝对时间)
· TTR
(time-to-run),Job执行超时时间。单位:秒。
· Body
Job的内容,供消费者做具体的业务处理,以json格式存储,可以根据不同的情况进行调整和扩充,不同的topic的body是不一致的。
· JobPool
一组需要处理的任务集合统称为任务池,见图3。

Figure 3. Message queue data structure
图3. 消息队列数据结构
4.2. 任务存储结构
本系统中的云端监控服务中心,可以认为是一个小型的消息中间件,消息中间件对消息队列进行相应的封装操作,为用户提供了消息发送与消费的接口 [5] 。客户端节点通过消息发送接口上报数据,监控服务中心通过消费接口分析数据,以辅助快速定位问题,实现灵活可扩展的配置告警 [6] 。系统中涉及到的任务存储结构见表3。

Table 3. Redis storage structure list
表3. Redis存储结构清单
4.3. 任务生命周期
任务的生命周期是指从任务到创建(init),等待执行(waiting),就绪(reday),完成销毁(finished,deleted)的过程,服务负责跟踪,维护和执行。
1) 创建任务元素进入等待队列,存储任务节点
云端监控服务中心按照网络节点编号对客户端上报的原始数据点进行分组和排序,网络节点进行单独分析,任务的最小单位以网络节点纬度进行。也就是说在监控服务中心进行一次分析时,假设有5000个网络节点,那么就会分配5000个任务,每个任务包含任务编号和任务实体数据,一并存入元数据中,进入等待队列。
为保证实时性要求,包的大小应尽可能小,以减少带宽的占用 [7] ,同时也减少了redis存储数据文件的大小。
2) 消费者执行消费由等待队列状态转移到就绪队列,等待消费
服务端会定时统一执行消费程序,时间间隔是5 s,对等待队列和就绪队列中的任务进行消费。根据每个网络节点的原始数据上报数据的最后时间与消费时间做时间差,在持续的120 s之内,稳定的接收到数据上报,认为网络状态优,大于120 s未接收到数据,认为网络状态差,否则是良。
3) 销毁分发发送邮件任务,删除任务节点,当次任务结束
对上一步网络监测结果良和差的网络节点,将联系人信息写入发送邮件队列,进行发送邮件通知。
4.4. 监控服务部署
本文中涉及到的工程实践包括客户端节点,云端监控服务中心,监控中心控制台三部分组件程序,以PHP语言开发,基于LNMP环境部署和生产实践,同时定时服务借助CentOs系统的Crontab服务。
当服务器轮询程序对任务队列进行消费时,会遇到同一个任务被多次执行消费的情况,为了解决这一问题,本例中在任务消费过程中使用了Redis分布式锁,确保同一个任务只能被执行一次,实现了消息服务消费的幂等性。
4.5. 结论
本文通过对待监控的销售商店的程序抽象,提出一种基于API接口上报和Rediis数据库结合的网络监测模型,借助消息队列服务组件对业务数据处理和元数据存储进行解耦,深入地使用了Redis中Sortset,List,string等数据结构,很好地完成了网络状态的实时监测分析。
系统经过半年的线上运行,结果记录如下:
1) 系统应用Redis存储需要处理的业务对象模型,通过消息队列组件解耦业务与数据存储,很好地实现了原始数据与业务分析的程序分层隔离。
2) 监控中心的分析服务能够稳定执行,完成一次全量网络节点,数据量7000左右的网络状态分析,结果控制在1分半内,能够满足业务方的业务。
5. 后续优化建议
5.1. 队列组件
本例中采用的是Redis作为消息队列中间件,优势是基于Redis的消息队列模型实现比较轻量级,不足是队列中的元素出队列即为消费完成,Redis本身不提供元素的消费确认机制,需要程序自身完成,增加了程序复杂性。消息队列中间件,后续可以由RabitMQ来代替Redis,消费的确认交给RabitMQ完成。
5.2. 消费模型升级
消息的处理模式由消息队列模型转为发布订阅模式,替换监控服务中心的定时轮询机制,提高消息的处理时效,降低轮询对服务器资源的无谓消耗。