故障检测主要有两种机制:心跳和租约。
- 心跳:假设总控机 A 需要确认工作机 B 是否发生故障,那么总控机 A 每隔一段时间,比如 1 秒,向工作机 B 发送一个心跳包。
- 租约:租约在一定期限内给予持有者特定权力的协议。
租约的协议决定了其用途,它能做的事情远不止故障检测。
- 如果协议内容是服务器确认客户端还存活,那么这个租约的功能就相当于心跳;
- 如果协议内容是服务器保证内容不会被修改,那么这个 租约就相当于读锁;
- 如果协议内容是服务器保证内容只能被这个客户端修改,那么这个租约就相当于写锁。
那么在判断客户端是否存活的问题上,心跳和租约有什么区别呢?
Completeness vs Accuracy
故障检测算法有两个重要的衡量标准:
- Completeness: every process failure is eventually detected (no misses)
- Accuracy: every detected failure corresponds to a crashed process (no mistakes)
前者(completeness)是说,如果一个进程挂掉,那么一定能被检测到;后者(accuracy)是说,如果 detector 认为 target 进程挂掉了,那么就一定挂掉了,不会出现误判。
心跳
心跳有两种方式:
- 单向的 heartbeat
- 交互的 ping-pong
第一种方式下,target 进程需要定时给 detector 发送消息,告知自己的存活性。而 detector 无需给 target 回复任何消息,只是每隔一段时间去检测 target 是否有汇报。
第二种方式更为常见,Redis 就采用的这种方式:
detector -> target: Are you ok?
target -> detector: Yeah, pretty good.
Detector 发起检测,如果 target 连续 N 次不回复消息,那么 detector 就认为其处于 non-active 状态。
不管是上述哪种方式,都能满足 completeness,但不满足 accuracy。因为有时候,detector 无法判断究竟是对方进程挂掉了,还是网络不通。如果是因为网络拥塞或者瞬断,还容易引发“双主”问题:target 并未意识到自己被认定失效,还在继续提供正常的服务。
租约
假设机器 A 需要检测机器 B 是否发生故障,机器 A 可以给机器 B 发放租约,机器 B 持有的租约在有效期内才允许提供服务,否则主动停止服务。机器 B 的租约快要到期的时候向机器 A 重新申请租约。租约机制是怎么解决心跳问题的不足呢?
这里的期限是租约的根本特性,正是这一特性使得租约能够容忍机器失效和网络分割:如果出现网络故障,B 就会因为未成功续约而停止服务。
参考