Hey,man,are you ok? -- 关于心跳、故障监测、lease机制

本文探讨了分布式系统中故障检测的方法,包括心跳机制、Ping-Ack交互及Lease机制的应用,并讨论了这些方法在不同场景下的优缺点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

   

  电话之于短信、微信的一个很大的不同点在于,前者更加及时,有更快速直接的反馈;而后面两个虽然称之为instant message,但经常时发出去了就得等对方回复,等多久是不确定的。打电话能明确知道对方在不在,我所表达的信息是否已经传达;而短信或者微信,只知道消息发出去了,但对方是否收到,或者是否查看就不清楚了。

  在通过网络通信的环境下,也是很难知道一个消息对方是否已经处理,因为要知道对方是否处理,依赖于对方的回复(ack),但即使对方没有回复,也不能说明对方就没有处理,也许仅仅是对方回复的那条消息丢失了

  很多时候,一个进程需要判断另外一个进程是否还在工作,如何判断呢?判断是否准确呢,能否保证一致性呢?本文尝试回答这些问题。

  本文中,节点通常就是指一个进程,一个提供服务的进程。后文中,只要不加以强调,进程和节点是同一个意思。

  本文地址:http://www.cnblogs.com/xybaby/p/8710421.html

进程的状态

  一个进程是否crash(在本文中,只要进程是非预期的终止,就成为crash),在单机环境下是很好判断的,只要查看还有没有这个进程就行了。这里有两个问题:

  第一:分布式环境下能否准确判断进程crash?

  第二:是否需要明确一个进程是否crash?

  对于第一个问题,答案是几乎不能的,后面详细分析。

  而第二个问题,有的时候是无需明确一个进程是否已经crash,而需要明确的是该进程是否持续对外提供服务,即使没有crash,如果不按程序的预期工作了(比如进程死循环、死锁、断网),那么也可以说这个服务挂了,“有的人活着,他已经死了”。分布式环境中,我们关心的是,节点(进程)是否对外提供服务,真死(crash)假死(活着但不工作)都是死(从系统的角度看)。

 

  我们称一个对外提供服务的进程处于active状态,否则处于none-active状态。

Failure detection

  如何判断一个进程是否处于active状态,最简单明了的方式就是:每隔一段时间就和这个进程通通信,如果目标进程在一定的时间阈值内回复,那么我们就说这个进程是active的,否则就是none-active的。这种定时通信的策略我们统称为心跳。

  心跳有两种方式:第一种是单向的heartbeat;第二种是ping pong(ping ack)

  在后文中,被检测的进程称之为target,而负责检测的进程称之为detector

  第一种方式,target进程需要告知detector进程自己的存活性,只需要定时给detector发消息就行了,“hi, duddy, I am Ok!”。detector无需给target回复任何消息,detector的任务是,每隔一定时间去检查一下target是否有来汇报,没有的话,detector就认为target处于none-active状态了

  

 

  而第二种方式,ping pong或者ping ack,更为常见:

  detector给target发消息:hi,man,are you ok?

  target回复detector:Yes, I am ok!

  然后detector、target定时重复上面两步

  

 

  detector负责发起检测:如果target连续N次不回复消息,那么detector就可以认为target处于none-active状态了。

  这两种方式,区别在于谁来主动发消息,而相同点在于:谁关心active状态,谁来检测。就是说,不管是简单的heartbeat,还是ping ack,都是detector来检测target的状态。在实际中,ping ack的方式会应用得多一些,因为在ack消息中也可以携带一些信息,比如后文会提到的lease。

gunicorn failure detection

  gunicorn是一个遵循python wsgi的http server,使用了prefork master-worker模型(在gunicorn中,master被称为Arbiter),能够与各种wsgi web框架协作。在本文,关注的是Arbiter进程对Worker进程状态的检测。

  既然Worker进程是Arbiter fork出来的,即Arbiter是Worker进程的父进程,那么当Worker进程退出的时候,Master是可以通过监听signal.SIGCHLD信号来知道子进程的结束。但这还不够,gunicorn还有另外一招,我在《gunicorn syncworker 源码解析》中也有所提及:

  (1)gunicorn为每一个Worker进程创建一个临时文件

  (2)worker进程在每次轮训的时候修改该临时文件的属性

  (3)Arbiter进程检查临时文件最新一次修改时间是否超过阈值,如果超过,那么就给Worker发信号,kill掉该Worker

  不难看出,这是上面提到的第一种检测方式(单向的heartbeat),worker进程(target)通过修改文件属性的方式表明自己处于active状态,Arbiter(detector)进程检测文件属性来判断worker进程最近是否是active状态。

  这个检测的目的就是防止worker进程处于假死状态,没有crash,但是不工作。

tcp keepalive

  在计算机网络中,心跳也使用非常广泛。比如为了检测目标IP是否可达的ping命令、TCP的keepalive。

A keepalive (KA) is a message sent by one device to another to check that the link between the two is operating, or to prevent the link from being broken.  

   在TCP协议中,是自带keepalive来保活的,有三个很重要的选项(option)

tcp_keepidle :对一个连接进行有效性探测之前运行的最大非活跃时间间隔
tcp_keepintvl :两个探测的时间间隔
tcp_keepcnt :关闭一个非活跃连接之前进行探测的最大次数

  三个选项是这么配合的:如果一条连接上tcp_keepidle 这么长时间没有发消息之后,则开始心跳检测,心跳检测的时间间隔是tcp_keepintvl ,如果连续探测tcp_keepcnt 这么多次,还没有收到对应的回应,那么主动关闭连接。

  这三个参数的默认值都比较大,就是说需要较长时间才能检测网络不可达,因此一般情况下,程序会将三个参数调小。

  可以看到,这是典型的ping ack探测方式。

应用层心跳

  如果我们使用TCP最为传输层协议,那么是否就可以依赖TCP的keepalive来检查连接的状态,或者说对端进程的active状态呢?

  答案是不能的,因为,TCP只能保证说链接是通的,但并不能表明对端是可用的,比如说对端进程处于死锁状态,但链接仍然是通的,tcp keepalive会持续收到回应,因此传输层的心跳无法发现进程的不可用状态。所以我们需要应用层的心跳,如果收到应用层的心跳回复,那么对端肯定不会是none-active的。

Failure detector与consensus

  前面提到,通过心跳,是无法准确判断target是否是crash,但有的情况下我们又需要明确知道对方是crash了?还是说只是网络不通?毕竟这是两个不同的状态。

  而且target的crash还分为crash-stop,crash-recovery两种情况。crash-stop是说一个进程如果crash了,那么不会重新启动,也就不会接受任何后续请求;而crash-recovery是说,进程crash之后会重启(是否在同一个物理机上重启没有任何关系)。

completeness vs accuracy

  如果目标是检测出target的crash状态,那么检测算法有两个重要的衡量标准:

Completeness: every process failure is eventually detected (no misses)

Accuracy: every detected failure corresponds to a crashed process (no mistakes)

  前者(completeness)是说,如果一个进程挂掉,那么一定能被检测到;后者(accuracy)是说,如果detector认为target进程挂掉了,那么就一定挂掉了,不会出现误判。

  对于crash-stop模型,只能保证completenss,不能保证accurary。completeness不难理解,而accuracy不能保证是因为网络通信中, 由于延时、丢包、网络分割的存在,导致心跳消息(无论是单向的heartbeat还是ping ack)无法到达,也就没法判断target是否已经crash,也许还活得好好的,只是与detector之间的网络出了问题。

  那如果是crash-recovery模型呢,通过简单的心跳,不仅不能保证accuracy,甚至不能保证completeness,因为target进程可能快速重启,当然增加一些进程相关的信息还是能判断crash-recovery的情况,比如target进程维护一个版本号,心跳需要对比版本号。

  总之,网络环境下,很难保证故障检测的准确性(accuracy)。

lease

  接下来,考虑一个很常见的场景,在系统中有两类进程:一个master和多个slave,master给slave分配任务,master需要通过心跳知道每个slave的状态,如果某个slave处于none-active状态,那么需要将该slave负责的任务转移到其他处于active状态的slave上。master也充当了detector的角色,每个slave都是被检测的target。

  在这个场景中,有两点需要注意,第一,使用了心跳作为探测方式,而心跳探测只能保证completeness(完整性),而不能保证accuracy(准确性)。第二,slave负责的任务是否是可重复的?即同一份任务是否可以同时在多个slave上进行。failure detection的不准确性对slave任务的重新分配有至关重要的影响。

  如果任务是可重复的,比如幂等的运算,那么当master通过心跳判断slave处于none-active状态的时候,只需要将任务重新分配给其他slave就行,不用管该slave是否还存活着。MapReduce就是这样的例子,master在worker(MapReduce中的slave进程)进程失活(甚至只是运算进度太慢)的时候,将该worker负责的任务(task)调度到其他worker上。因此可能出现多个worker负责同一份任务的情况,但这并不会产生什么错误,因为任务的计算结果需要回报给master,master保证对于一份任务只会采纳一份计算结果,而且同一份任务,不管是哪个worker执行,结果都是一致的。

  但如果任务只能由一个节点来执行呢,由于心跳检测的不准确性,那么将任务从本来还在工作的节点重新调度到其他节点时,就会出现严重的问题。比如在primary-secondary副本控制协议中,primary的角色只能由一个节点来扮演,如果同时有两个primary,那么就出现了脑裂(brain-split),从这名字就能听出来问题的严重性。因此,即使在心跳检测出现误判的情况下,也要保证任务的唯一性,或者说,需要detector与target之间达成共识:target不要对外提供服务了。这个时候Lease机制就是很不错的选择,因为Lease机制具有很好的容错性,不会受到网络故障、延迟的影响。

  关于lease机制,我在《带着问题学习分布式系统之数据分片》中有简单介绍,这里再简单提一下Lease在GFS中的使用。

  GFS采用了primary-seconday副本控制协议,primary决定了数据修改的顺序。而primary的选举、维护是由master节点负责的,master与primary的心跳中,会携带lease信息。这个lease信息是master对primary的承诺:在lease_term这个时间范围内,我不会选举出新的primary。那么对于primary,在这个时间内,执行primary的职责,一旦过了这个时间,就自动失去了primary的权利。如果primary节点本身是ok的,并且与master之间网络正常,那么在每次心跳的时候,会延长这个lease_term,primary节点持续对外服务。一旦超过lease_term约定的时间,master就会选出新的primary节点,而旧的primary节点如果没有crash,在恢复与master的心跳之后,会意识到已经有了新的primary,然后将自己降级为secondary。

关于detector

  从上面GFS的例子,可以看到master是一个单点,也就是说由一个节点来负责所以其它节点的failure detection,这就是集中式的故障检测。如下图所示:

   由于detector是单点,因此压力会比较大。更为严重的问题,在使用了lease机制的系统中,一旦detector故障,所以节点都无法获取lease,也就无法提供服务,整个系统完全不可用。

  因此,detector的高性能、高可用非常重要,以一个集群的形式提供failure detection功能。

references

buffalo cse486 failure_detectors.pdf

gunicorn.org

Failure_detector

Leases: An Efficient Fault-Tolerant Mechanism for Distributed File Cache Consistency 

   

 

<think>好的,我现在需要帮助用户了解Kubernetes中的--endpoint-reconciler-type=lease这个参数。首先,我应该回忆一下这个参数的作用和背景。 记得Kubernetes中的Endpoints Controller负责维护Service对应的Endpoints资源,也就是将Service的后端Pod列表更新到Endpoints中。在旧版本中,可能使用的是传统的Endpoint Reconciler,而到了某个版本之后,引入了基于Lease机制。 用户提到的参数--endpoint-reconciler-type=lease,应该是在控制平面组件kube-controller-manager中使用的。这个参数的作用是选择使用哪种机制来协调Endpoints。传统的可能有一些性能问题,尤其是在大规模集群中,频繁更新Endpoints会导致etcd负担过重。而Lease机制可能通过减少直接写入etcd的次数来优化这个问题。 接下来,我需要确认Lease机制的工作原理。可能是在使用Lease资源来跟踪Endpoint的变化,而不是每次变更都直接更新Endpoints资源。当Endpoint状态变化时,先更新Lease对象,然后定期或根据某种条件同步到Endpoints资源。这样可以减少对etcd的写操作,提高性能。 另外,需要说明这个参数的适用场景,比如大规模集群,或者对API Server负载敏感的环境。同时,可能需要注意版本兼容性,这个参数可能是在Kubernetes某个版本之后引入的,用户需要确认他们的集群版本是否支持。 可能还需要比较一下两种reconciler类型的区别,比如lease和传统的(比如endpoint或者其它类型)。传统的方式可能每次Pod变化都会触发Endpoints的更新,而lease方式则通过Lease对象来批处理或延迟这些更新,从而减轻负担。 不过,我需要查阅一下官方文档或相关资料,确认这些细节是否正确。例如,Kubernetes官方文档中是否有关于--endpoint-reconciler-type参数的说明,以及Lease机制的具体实现方式。 另外,用户可能想知道如何配置这个参数,是否需要额外的步骤,比如启用特定的Feature Gate,或者在kube-controller-manager的启动参数中添加--endpoint-reconciler-type=lease。同时,可能需要注意升级集群时的注意事项,比如是否需要逐步切换,或者是否存在回滚的风险。 总结一下,回答的结构应该是先介绍Endpoints Controller的作用,然后说明该参数的作用,比较不同reconciler类型的区别,详细说明lease机制的工作原理和优势,适用场景,配置方法以及版本要求。同时,需要确保使用正确的中文术语,比如“租约”而不是直接使用英文Lease,但可能需要保留原文术语以避免混淆。 需要注意的是,用户可能对Kubernetes内部机制不太熟悉,所以需要用比较易懂的语言解释,避免过于技术化的术语。同时,提供实际例子或建议,比如在什么情况下应该考虑使用lease类型,如何验证是否生效,或者监控效果的方法。 最后,检查是否有错误,比如是否混淆了Endpoint和Endpoints的概念,确保术语准确。例如,Endpoints是Kubernetes中的一个资源对象,而Endpoint可能指单个的地址和端口。</think>在 Kubernetes 中,`--endpoint-reconciler-type=lease` 是一个与控制平面组件 `kube-controller-manager` 相关的参数,用于优化 **Endpoints Controller** 的工作机制。以下是逐步解释: --- ### 1. **Endpoints Controller 的作用** - Kubernetes 中,每个 `Service` 对应一个 `Endpoints` 资源,记录该 Service 后端 Pod 的 IP 和端口。 - **Endpoints Controller** 负责监听 Pod 变化,并动态更新 `Endpoints` 对象,确保流量正确路由到健康的 Pod。 --- ### 2. **传统 Endpoint Reconciler 的问题** - 在旧版本中,Endpoints Controller 使用直接更新 `Endpoints` 的方式(`endpoint-reconciler-type=legacy`): - 每次 Pod 状态变化(如重启、扩缩容)都会触发对 `Endpoints` 的更新。 - 高频写入 `etcd` 可能导致性能问题,尤其是在大规模集群中。 --- ### 3. **Lease 机制的引入** - Kubernetes v1.17+ 引入了 `--endpoint-reconciler-type=lease` 选项,改为基于 **Lease 对象**的协调机制- **Lease 对象**:一种轻量资源,用于记录某个组件(如 kubelet)的活跃状态。 - **工作原理**: 1. Endpoints Controller 不再直接频繁更新 `Endpoints`,而是通过更新 `Lease` 对象(位于 `kube-system` 命名空间)来跟踪健康状态。 2. 仅在 `Lease` 失效或发生重大变化时,才更新 `Endpoints` 资源。 - **优势**: - 减少对 `etcd` 的直接写入压力。 - 提升大规模集群的稳定性和扩展性。 --- ### 4. **适用场景** - **大规模集群**:Pod 数量多且变化频繁时,显著降低 API Server 和 `etcd` 的负载。 - **高可用性要求**:减少因频繁更新导致的潜在性能抖动。 --- ### 5. **配置方法** - 在 `kube-controller-manager` 的启动参数中添加: ```bash --endpoint-reconciler-type=lease ``` - **版本要求**:Kubernetes v1.17+(需确认集群版本支持)。 --- ### 6. **验证机制是否生效** - 检查 `kube-controller-manager` 日志,确认参数已加载。 - 观察 `kube-system` 命名空间中的 Lease 对象: ```bash kubectl get leases -n kube-system ``` --- ### 7. **注意事项** - **兼容性**:确保集群版本支持 Lease 机制- **平滑升级**:从传统模式切换到 Lease 机制时,需滚动重启控制平面组件以避免中断。 --- ### 总结 `--endpoint-reconciler-type=lease` 通过引入 Lease 对象,优化了 Endpoints 更新的效率,特别适合大规模 Kubernetes 集群。这一改进降低了系统组件的负载,提升了整体稳定性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值