[关闭]
@eric1989 2017-02-20T11:42:44.000000Z 字数 2142 阅读 996

哨兵机制


背景

票号中心是一个事件驱动的架构。数据库用于保存历史数据和快照数据。而票号中心程序自身内存内则拥有着最新的数据。这也意味着票号中心是一个有状态的服务。由于单点故障的存在,因此需要设计一种可靠的主备容灾机制。由于票号中心是有状态的,如果两个票号中心同时对外提供服务就会导致出现数据混乱的情况。因此设计的主备机制需要确保主备集群中只有一台对外提供服务。抽象即可得到以下问题:共享可靠存储的有状态计算服务,在确保只能有一个节点提供服务的情况,如何完成主备机制

算法基础

通过上面的介绍,我们可以得出整个主备机制的约束。

  1. 计算服务本身是有状态的
  2. 多个计算服务使用同一个数据库,即不具备私有数据库这一基础设施(这一约束会导致需要私有状态机的一致性算法实现困难)
  3. 主备出现切换时,当备机提供服务之前,必须保证主机已经下线(特定于票号池而言,意味着该票号池进程已经关闭)
  4. 主备节点为不同的进程节点。如果该进程运行于不同的物理环境(服务器),则可以防止宕机风险。如果运行于同一个物理环境,则只能防止进程崩溃等程序意外。

在这个前提下,为了实现主备,提供了一种称之为"哨兵"的主备切换机制。该机制包含两个个模块:哨兵和仲裁者。哨兵与仲裁者之间通过RPC方式进行通讯(通讯协议并不重要,在工程实践中,为了简化开发,建议采用RPC或者简单的私有协议)。

哨兵
哨兵是一个可与仲裁者通信的独立模块,其本身应该作为应用程序的依赖之一。该模块具备待命和激活两种状态。初始化启动的时候为待命状态。哨兵会定期(时间N)向仲裁者报告自己的当前状态,并且请求获得激活授权或者延长激活授权(已经得到授权的情况)。
仲裁者
仲裁者是一个可与哨兵沟通的独立模块,负责激活授权和哨兵状态监测。

哨兵

哨兵在启动后会用周期时间N向仲裁者发送授权请求(如果在已经获得授权的情况,则成为心跳信息)。哨兵内部存在一个随机时间T的定时器。每次哨兵成功向仲裁者发送请求时会重置该定时器。而当定时器超时,哨兵会认为自身无法联系上仲裁者,会终止自身的进程。时间约束T>>N(在工程实践中,可以让T=20s,N=500ms)。
如果哨兵的授权请求被批准,则哨兵会初始化启动计算服务。成功后将自身转化为激活状态。哨兵只会由待命状态转化为激活状态。
为了方便监测,哨兵在启动时会监听一个端口作为自身的身份标识。如果哨兵在启动时发现此端口被占用,则会终止自身程序。以确保在一个运行环境中只有一个提供服务的哨兵进程。

仲裁者

仲裁者负责接收哨兵的授权请求,并且在允许的情况下批准该请求。
为了保证架构简单,仲裁者需要可靠的持久化授权信息并且自身做到无状态(无状态就可以很容易主备避免单点)。仲裁者需要可靠持久化的信息有
授权信息(频繁变化)

名称 类型 备注
granted_sentinel string 授权激活被批准的哨兵id
granted_time datetime 上一次给予授权的时间

哨兵信息(静态数据)

名称 类型 备注
sentinel_id string 哨兵id
ip string 该哨兵的链接ip
port int 该哨兵的监听端口

仲裁者优先在内存中存储授权信息,并周期时间G下持久化到可靠性存储(工程中建议G为1s)。而一旦仲裁者需要更新授权信息,需要作废内存数据并且操作可靠性存储中的数据,并以其中的数据为准。
仲裁者在先到先得的原则下批准请求,同时,批准请求需要具备以下几个条件之一。
1. 可靠性存储中不存在授权数据(系统的初始化状态)。
2. 曾经得到授权的哨兵,在时间M(M>>T且M>>G)内未发送过心跳包续约授权,并且检测到该哨兵的端口并不存活。
3. 当前请求授权的哨兵就是之前得到授权的哨兵。

算法的目的是为了保证当备份节点上线提供计算能力的时候,主节点必然已经下线这个事实。在时间约束M>>T>>N的情况下,如果主节点在定时器T到达时仍然无法向仲裁者发送心跳则会终止,而仲裁者在足够长的安全时间(M-T)后监测主节点确实不存活(通过监测ip和端口情况),则会批准备份节点的授权请求。因此,在备份节点上线的时候,主节点必然是下线状态。

部署结构

通过上面的算法基础,我们可以得到如下的部署拓扑
mark
从算法基础可知,授权批准是计算服务初始化计算能力的关键,因此仲裁者的存活是很重要的。为了避免单点故障以及保持架构的简单,对于仲裁者而言使用负载均衡策略部署。常规而言,2台物理机器已经足够。同时,为了不引入额外的依赖(例如Nginx类的负载前端),哨兵内部直接存储了仲裁者的服务列表(通常来说就是2个)。哨兵发送请求时从仲裁者列表中随机选择,如果失败则选择剩余的重试。如果都失败,则认为本次请求未曾送达。

路由机制

对于使用计算服务的客户端而言,在当前的容灾机制下(后面会说到容灾机制的演进方向),客户端需要内置静态的路由列表。客户端选择计算服务节点的逻辑流程如下:
mark

请求和数据结构

哨兵与仲裁者是单向通讯,请求由哨兵发起,仲裁者进行响应。

authorityRequest(授权请求)

参数:

名称 类型 备注
sentinel_id string 请求授权的哨兵id

响应:

名称 类型 备注
granted boolean 授权是否被批准
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注