Flink/Blink 原理漫谈(六)容错机制(fault tolerance)详解

本文深入探讨Flink/Blink的容错机制,包括检查点(checkpoint)的原理及实现过程,介绍了增量检查点(Incremental checkpoint)如何解决大规模流处理任务中的状态增长问题。

系列文章目录

Flink/Blink 原理漫谈(零)运行时的组件

Flink/Blink 原理漫谈(一)时间,watermark详解

Flink/Blink 原理漫谈(二)流表对偶性和distinct详解

Flink/Blink 原理漫谈(三)state 有状态计算机制 详解

Flink/Blink 原理漫谈(四)window机制详解

Flink/Blink 原理漫谈(五)流式计算的持续查询实现 详解

Flink/Blink 原理漫谈(六)容错机制(fault tolerance)详解



Flink 容错机制

Flink 检查点的核心作用是确保状态正确,即使遇到程序中断,也要正确。流计算Fault Tolerance的一个很大的挑战是低延迟,很多Blink任务都是7 x 24小时不间断,端到端的秒级延迟,要想在遇上网络闪断,机器坏掉等非预期的问题时候快速恢复正常,并且不影响计算结果正确性是一件极其困难的事情。在Blink中以checkpointing的机制进行容错,checkpointing会产生类似binlog一样的可以用来恢复的任务状态数据。花费的成本有低到高,如下:
• at-least-once
• exactly-once

检查点checkpoint

检查点像普通数据记录一样在算子之间流动。检查点分割线和普通数据记录类似。它们由算子处理,但并不参与计算,而是会触发与检查点相关的行为。
Checkpoint是故障修复机制的核心,是在某个时间点的一份拷贝。这个时间点是所有任务都恰好处理完一个相同的输入数据的时候。

从checkpoint恢复状态的步骤:
在这里插入图片描述
全局状态一致性的问题:
因为是流式计算,所以如何保证恢复的时候的全局一致性是一个棘手的问题。checkpoint使用的是分布式快照的方法,这种方法举例来说就是全学校的人需要拍一张毕业照,但是flink任务场景下,每个结点处理任务都是进度不同的,所以很难将全学校的人都在毕业的这一时刻叫到一起,拍一张照片;所以,我们就让学校中每一个同学各自拍一张自己毕业时候的照片,然后p图拼接在一起,这样就实现了在毕业的这一时刻得到一张全学校同学的毕业照。

Checkpoint的实现过程
Chekpoint用的就是这种分布式快照的方式,首先,JobManager向每个source任务发送一条带有检查点id的信息,启动检查点,source将他们的状态写入检查点,并发出一个检查点barrier,这个barrier信息就和正常的流式数据一样流动,当某个分区收到barrier信息之后,就会将当前状态保存到后端检查点中,接下来向后转发barrier,这个节点也急需处理数据,这个barrier被传递很多次之后,到达了最后的sink任务,sink也将状态保存,这时候所有的分区都已经将自己的状态发入检查点后端,一个checkpoint就完成了。

有些核心的点:
• barrier 由source节点发出;
• barrier会将流上event切分到不同的checkpoint中;
• barrier对齐之后会进行Checkpointing,生成snapshot;
• 完成snapshot之后向下游发出barrier,继续直到Sink节点;

这里引申出一个问题,这解决的是exactly-once还是at-least-once呢?这是分情况的,首先,为了满足exactly-once,我们使用的是BarrierBuffer,汇聚到当前节点的多流的barrier要对齐,Barrier提前到达,再有新的数据来,会被缓存;barrier尚未到达,数据被正常处理。如果是为了满足at-least-once,要使用的是BarrierTracker:BarrierTracker会对各个输入接收到的检查点的barrier进行跟踪。一旦它观察到某个检查点的所有barrier都已经到达,它将会通知监听器检查点已完成。

Incremental checkpoint

对于一个流计算的任务,数据会源源不断的流入,比如要进行双流join(Blink 漫谈系列 - Join 篇会详细介绍),由于两边的流event的到来有先后顺序问题,我们必须将left和right的数据都会在state中进行存储,Left event流入会在Right的State进行join数据,Right event流入会在LState中join数据。
由于流上数据源源不断,随着时间的增加,每次checkpoint产生的snapshot的文件(RocksDB的sst文件)会变的非常庞大,增加网络IO,拉长checkpoint时间,最终导无法完成checkpoint,Blink失去failover的能力。为了解决checkpoint不断变大的问题,Blink内部实现了Incremental checkpoint,这种增量进行checkpoint的机制,会大大减少checkpoint时间,并且如果业务数据稳定的情况下每次checkpoint的时间是相对稳定的,根据不同的业务需求设定checkpoint的interval,稳定快速的进行checkpointing,保障Blink任务在遇到故障时候可以顺利的进行failover。Incremental checkpoint的优化对于Blink成百上千的任务节点带来的利好不言而喻。

另外,为了实现端到端的一致性,我们仍然需要另外的一些机制,具体在state章节有详细的介绍。

<think>我们正在讨论NuttX中TCB的flinkblink指针如何用于实现优先级继承机制。优先级继承是一种解决优先级反转问题的技术,当高优先级任务因低优先级任务持有共享资源而阻塞时,临时提升低优先级任务的优先级至与高优先级任务相同,使其尽快执行并释放资源。 在NuttX中,优先级继承机制(Priority Inheritance)主要通过互斥锁(mutex)实现。当任务尝试获取已被持有的互斥锁时,如果持有者优先级低于当前任务,系统会提升持有者优先级以避免优先级反转。而flinkblink指针作为双向链表的连接件,在就绪任务队列中用于快速调整任务的位置(因为就绪队列是按优先级排序的)。 具体实现步骤: 1. **检测优先级反转**:当高优先级任务(H)尝试获取低优先级任务(L)持有的互斥锁时,内核发现L的优先级低于H。 2. **提升持有者优先级**:系统临时将L的优先级提升到与H相同(或略低,具体取决于策略)。 3. **调整就绪队列**:由于L的优先级提升,需要将其在就绪队列中的位置前移(因为就绪队列是按优先级从高到低排序的双向链表)。这里就用到`flink`和`blink`指针: - 将L从当前链表中移除(通过修改相邻节点的`flink`和`blink`指针) - 根据新优先级将L插入到就绪队列的合适位置(同样通过调整链表指针) 4. **任务调度**:调度器下一次调度时,由于L的优先级已提升,会优先执行L,使其尽快释放互斥锁。 5. **恢复原优先级**:当L释放互斥锁后,系统将其优先级恢复为原值,并调整其在就绪队列中的位置。 关键代码逻辑(以互斥锁获取为例): ```c // 当任务H尝试获取已被L持有的互斥锁时 if (H->priority > L->priority) { // 提升L的优先级至H的优先级 nxsched_set_priority(L, H->priority); // 调整L在就绪队列中的位置(使用flink/blink指针) sched_remove_readytorun(L); // 从原位置移除 sched_add_readytorun(L); // 按新优先级插入 } ``` 在调整链表位置时,`sched_remove_readytorun`和`sched_add_readytorun`函数内部会操作`flink`和`blink`指针,实现$O(1)$复杂度的链表节点删除和插入。例如: ```c void sched_remove_readytorun(FAR struct tcb_s *tcb) { // 获取前驱和后继 FAR struct tcb_s *prev = tcb->blink; FAR struct tcb_s *next = tcb->flink; // 更新前驱的后继指针 if (prev != NULL) { prev->flink = next; } // 更新后继的前驱指针 if (next != NULL) { next->blink = prev; } } ``` **总结**:`flink`和`blink`指针在优先级继承中的作用是**动态调整任务在就绪队列中的位置**,确保因优先级提升而需要提前执行的任务能被快速调度。这种机制有效缓解了优先级反转问题,提高了系统的实时性。</think>### NuttX中如何利用`flink/blink`实现优先级继承机制 在NuttX中,`flink`和`blink`指针通过**动态调整任务在就绪队列中的位置**来实现优先级继承机制。该机制主要用于解决优先级反转问题,具体实现过程如下: #### 1. **优先级继承触发条件** 当高优先级任务($T_{high}$)因等待低优先级任务($T_{low}$)持有的资源(如互斥锁)而阻塞时,系统触发优先级继承: ```c /* 互斥锁获取函数片段 */ if (mutex->holder && mutex->holder->sched_priority < current->sched_priority) { nxsched_priority_inherit(mutex->holder, current->sched_priority); // 触发继承[^1] } ``` #### 2. **优先级提升的核心操作** 通过`flink/blink`动态调整任务在就绪队列中的位置: ```mermaid graph LR A[检测优先级反转] --> B{提升持有者优先级} B --> C[从就绪队列移除T_low] C --> D[按新优先级插入队列] D --> E[更新调度指针] ``` 具体实现步骤: 1. **移除原优先级位置** 通过`flink/blink`将$T_{low}$从当前队列位置移除: ```c void sched_remove_readytorun(FAR struct tcb_s *tcb) { // 更新前驱节点的flink if (tcb->blink) tcb->blink->flink = tcb->flink; // 更新后继节点的blink if (tcb->flink) tcb->flink->blink = tcb->blink; } ``` 2. **插入新优先级位置** 使用优先级排序算法重新插入: ```c void sched_add_readytorun(FAR struct tcb_s *tcb) { // 遍历就绪队列(g_readytorun) FAR struct tcb_s *prev = NULL; FAR struct tcb_s *next = g_readytorun.head; // 按优先级查找插入点 while (next && next->sched_priority > tcb->sched_priority) { prev = next; next = next->flink; // 通过flink遍历 } // 更新flink/blink指针 tcb->flink = next; tcb->blink = prev; if (next) next->blink = tcb; if (prev) prev->flink = tcb; } ``` #### 3. **调度器行为变化** 优先级提升后: 1. 就绪队列头部`g_readytorun.head`指向$T_{low}$(因其临时拥有最高优先级) 2. 调度器通过`flink`遍历时优先执行$T_{low}$: ```c FAR struct tcb_s *next = g_readytorun.head; // 获取最高优先级任务 while (next->flink && !ready_to_run(next)) { // 通过flink遍历 next = next->flink; } up_switchcontext(current, next); // 切换到继承优先级的任务 ``` #### 4. **优先级恢复机制** 当$T_{low}$释放资源后: 1. 通过`flink/blink`将其移出当前队列位置 2. 按原始优先级重新插入队列 3. 恢复$T_{high}$到就绪队列头部 #### 设计优势 1. **$O(1)$时间复杂度**:指针操作确保优先级调整高效完成 2. **无锁设计**:单核环境下通过开关中断保证原子性 3. **动态响应**:实时调整`g_readytorun.head`指向当前最高优先级任务 4. **资源优化**:复用TCB现有指针,无需额外存储 > 示例:当I²C驱动(HPWORK)持有资源时,其优先级可临时提升至与等待任务相同,确保实时性要求[^1]。 #### 总结 NuttX通过`flink/blink`实现: 1. **动态队列重组**:快速调整任务在双向链表中的位置 2. **优先级提升**:使资源持有者获得请求者的优先级 3. **实时调度**:确保高优先级任务最小化等待时间 4. **自动恢复**:资源释放后优先级自动回退 这种机制有效解决了嵌入式系统中的优先级反转问题,同时保持调度器的高效性(平均$O(1)$操作复杂度)。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值