分布式数据库的进度管理:TiDB 备份恢复工具 PiTR 的原理与实践

news2024/11/26 17:48:22

导读

对于一款企业级数据库产品而言,数据的安全性和可恢复性是至关重要的。PiTR(Point in Time Restore)作为 TiDB 备份工具的核心功能之一,提供了一种精细的数据恢复能力,允许用户将数据库集群恢复到过去的任意时间点。这种能力对于处理数据损坏、误操作或数据丢失等灾难性事件至关重要。

对于分布式系统而言,想实现精确的进度管理是十分复杂的,本文将深入解析 PiTR 在 TiDB 的分布式架构中的实现,包括其在 TiKV 层的备份流程,以及 TiDB 如何管理这些备份任务的进度。

希望本文能够帮助开发者和数据库管理员更好地理解 PiTR 的工作机制,有效地利用这一功能加固数据库基础设施。

PiTR 是 TiDB 备份工具中必不可少的一部分。如果说全量备份帮助我们获得了将集群回退到某个时间节点的能力,那么 PiTR 则更加精细地备份了集群的每一次写入,并且允许我们回到备份开始后的任意一个时点。

直觉上,当你启动一个 PiTR 任务,等于告诉集群:我需要知道从当前时间节点之后的全部变化。对于一个分布式数据库而言,这并不是一个简单的工作。

目前 TiDB 的数据存储结构

上图展示了目前 TiDB 的数据存储结构。用户以表和行的形式写入数据,每一行数据都会以一个键值对的形式存储在 TiKV 中,每一个 TiKV 又会被逻辑地划分为多个 region。

由于 TiDB 分布式写入的实质,各个 Region 的数据分布在不同宿主机上,也不存在一个确切统一的写入时点。所以我们需要找到一种方法分别管理每个 region 的写入工作,并且需要提供一个整体进度。在接下来的内容中,我们将详细展开 TiDB 的 PiTR 进度管理流程。从单个 TiKV 开始,逐步推进到整个集群。

TiKV 侧备份流程

如果我们希望管理备份工作的具体进度,首先需要了解的是,备份工作究竟是怎样完成的。在 TiDB 的实践中,PiTR 是一个分布式过程,每个 TiKV Server 自行记录备份数据,并将数据发送到远端储存,大致上按照下图所示的流程工作。

TiKV 侧备份流程

在 TiKV server 初始化期间,会同时(先后)初始化 BackupStreamObserver[1] 和 Endpoint[2] 两个组件。它们共用了同一个 scheduler(backup_stream_scheduler[3],通过向 scheduler 发送 Task 的方式进行互相沟通。

BackupStreamObserver 会实时监听 Raft 状态机的写入情况。其重点在于 on_flush_applied_cmd_batch()[4] 接口。这个接口会在 Raft 状态机 apply 时被调用,将 Raft 命令打包为 BatchEvent,然后作为一个任务发送给 scheduler。对于 PiTR 而言,这个任务被称为 Task::BatchEvent[5]。

pub struct CmdBatch {
    pub level: ObserveLevel,
    pub cdc_id: ObserveId,
    pub rts_id: ObserveId,
    pub pitr_id: ObserveId,
    pub region_id: u64,
    pub cmds: Vec<Cmd>,
}

可以看出,BatchEvent 的实质是一系列 Raft 命令的拷贝。PiTR 在备份时记录这些命令,并在恢复时重放,以实现日志备份功能。

而 Endpoint 负责沟通 TiKV Server 和外部储存。它会在启动之后进入一个循环,检查当前 scheduler 中是否包含新的任务,匹配并执行不同的函数。其中,我们需要关注的是 Task::BatchEvent,也就是从 Observer 发送来的写入数据。当 endpoint 匹配到 Task::BatchEvent,它会执行 backup_batch()[6] 函数开始备份这些键值对。

在这一步,Endpoint 先对 CmdBatch 进行简单检查,然后将它发往router.on_events()[7],并开始异步地等待结果。

Router 的作用是将写入操作按照 range 拆分,以提高并发度。每个 range 的写入并不是即时的,我们会在内存中储存一个临时文件,用于暂时存储从 raft store 更新的信息。当内存中储存的临时文件大小超出上限,或者超过指定刷盘间隔,我们才会真正将储存在临时文件中的数据写入远端储存,并视为完成了一次(部份)备份。目前 BackupStreamConfig 的默认设置中,max_flush_interval 为 3 分钟。

impl Default for BackupStreamConfig {
    fn default() -> Self {
        // ...
        Self {
            min_ts_interval: ReadableDuration::secs(10),
            max_flush_interval: ReadableDuration::minutes(3),
        // ...
        }
    }
}

当满足刷盘条件后,我们会跳转到 endpoint.do_flush() [8] 函数。并在这里完成将备份文件刷盘的逻辑。当这个函数完成之后,备份数据已经被写入远端存储,可以认为备份到此告一段落。此处正是汇报备份进度的最佳时刻。在并不令人注意的角落,这个任务是由一个回调完成的:flush_ob.after() [9]。

       async fn after(&mut self, task: &str, _rts: u64) -> Result<()> {
        let flush_task = Task::RegionCheckpointsOp(RegionCheckpointOperation::FlushWith(
            std::mem::take(&mut self.checkpoints),
        )); //Update checkpoint
        try_send!(self.sched, flush_task);

        let global_checkpoint = self.get_checkpoint(task).await?;
        info!("getting global checkpoint from cache for updating."; "checkpoint" => ?global_checkpoint);
        self.baseline
            .after(task, global_checkpoint.ts.into_inner()) //update safepoint
            .await?;
        Ok(())
    }

这个回调函数做了两件事,更新 service safe point 和 store checkpoint。它们是什么,又有什么用呢?

从检查点(Checkpoint)到全局检查点(Global Checkpoint)

上文中我们阅读了 PiTR 备份流程的细节。现在,我们可以回到正题,反思整个流程。

首先我们已经明确,对于 TiDB 这样的分布式数据库,所有的数据都储存在一个个单独的 TiKV 节点上。在 PiTR 流程中,这些 TiKV 也是各自将数据打包成文件,发送到远端储存上。这引出了一个重要的问题:如何进行进度管理?

为了确保备份进度的有效管理,我们需要跟踪每个 TiKV 节点上的数据备份进度。对于单个 Region,可以通过记录已备份数据的时间戳来实现进度管理:当数据被刷盘时,记录当前时间戳,这个时间戳就是该 Region 完成备份的最小时间节点,即 Checkpoint。

同时,我们需要了解到,需要备份的数据并不会永恒的保留。由于 MVCC 机制,每次数据修改都会产生一个新版本并保留旧版本,旧版本可以用于历史查询和事务隔离。随着时间的推移,这些历史数据会不断累积,因此需要通过 GC 机制来回收和清理旧版本,释放存储空间并提高性能。

我们需要确保在备份(Flush)完成之前,备份数据不会被 GC 清除。所以此处引入一个指标,通知 GC 可以安全清除的数据时间戳。这就是Service Safepoint。

值得注意的是,以上的讨论只是单个 region 的进度管理,一个集群中会同时存在多个 region,所以我们需要设计一个指标便于管理整个集群的备份进度,它被称之为Global Checkpoint。

在实践中,Global Checkpoint 是所有 TiKV Checkpoint 的最小值[10],这保证了所有 region 的进度都至少不小于这个时间节点。或者说,在这个时间节点之前,整个集群的数据都完成备份了。

而这个汇总所有 TiKV 进度并计算 Global Checkpoint 的工作,是在 TiDB 完成的。

TiDB 侧进度管理

TiDB 侧进度管理

既然我们了解了 TiKV 侧的备份进度管理流程。让我们转头看看 TiDB 的情况。

在 TiDB 侧,负责这项工作的组件被称为 CheckpointAdvancer [11]。它的本质是一个外挂在 TiDB 主程序上的守护进程,会随着时间执行一些周期性操作。它的工作主要包括两部分:

  1. 订阅更新来自 TiKV 的 FlushTSO 更新。
  2. 处理可能的错误并计算 Global Checkpoint。
  3. 计算总体更新进度并汇报给 PD。

具体地,在 CheckPointAdvancer 中有一个名为 FlushSubscriber[12] 的字段,TiDB 就是通过它监听 TiKV 的刷盘操作和 checkpoint 推进。FlushSubscriber 维持一个 gRPC 流,持续监听[13] 不同 range 的 checkpoint 并将其记录下来。随后通过 channel 发送给 advancer。

而 advacner 接收到这些 checkpoint 之后,会将它们放置于 checkpoints[14] 字段中。当接收到来自 TiKV 的进度信息之后,advancer 会尝试开始更新 Global Checkpoint。作为一个守护进程,更新过程并不是实时的,而是随着主进程调用它的 tick()[15] 方法间歇性完成。

func (c *CheckpointAdvancer) tick(ctx context.Context) error {
        //...        
        var errs error
        cx, cancel := context.WithTimeout(ctx, c.Config().TickTimeout())
        defer cancel()
        err := c.optionalTick(cx)
        if err != nil {
            // ...  
        }

        err = c.importantTick(ctx)
        if err != nil {
            // ...
        }

        return errs
}

这个过程实际上被分为了两个部分,optionalTick()[16] 和 importantTick() [17]。

optionalTick 主要负责与 FlushSubscriber 沟通,获取来自 TiKV 的进度更新。由于单个 TiKV 的 Checkpoint 并不一定会推进,所以取名为 optionalTick。一旦捕获到 TiKV FlushTSO 的更新,便会在这里记录并试图推进全局检查点。

而 importantTick 则负责管理全局进度。确认进度更新后,这里会产生新的 Global Checkpoint 和 Service Safepoint[18]。

这个行为是存在风险的。如果某个 TiKV 的 Checkpoint 因为种种原因一直没有成功推进,就会阻塞住 Global Checkpoint 的推进,进而可能阻塞住 GC,无法正确清除已经完成备份的冗余数据。在最糟糕的情况下,某个 TiKV 陷入了不可自动恢复的错误。它有可能会永远阻碍 GC 进度,造成对整体系统的更大破坏。

因此,importantTick 会检查[19] checkpoint 距离上次更新的时间差。如果某个 Checkpoint 长时间没有推进,这个备份任务会被标记为异常状态[20]。随后,advancer 会自动暂停这个任务,等待管理员手工运维的介入。

        isLagged, err := c.isCheckpointLagged(ctx)
        if err != nil {
                return errors.Annotate(err, "failed to check timestamp")
        }
        if isLagged {
                err := c.env.PauseTask(ctx, c.task.Name)
                if err != nil {
                        return errors.Annotate(err, "failed to pause task")
                }
                return errors.Annotate(errors.Errorf("check point lagged too large"), "check point lagged too large")
        }

此后,advancer 并不会停止,它只是跳过 [21] 了异常任务的 checkpoint 更新。如果 PD 恢复了这个任务,会向 advancer 发送信号[22],advancer 便可以回到正常的 tick 流程中。

此处介绍的异常处理机制是完全防卫性质的。它只能识别异常状态的存在,却无法指出问题的原因,最终还需要管理员手动介入。或许在未来,我们能够实现 PiTR 的自动运维,当 checkpoint 恢复推进之后,可以自动重启这个任务。

参考资料

[1]

BackupStreamObserver: https://github.com/tikv/tikv/blob/release-8.0/components/backup-stream/src/observer.rs#L94

[2]

Endpoint: https://github.com/tikv/tikv/blob/release-8.0/components/backup-stream/src/endpoint.rs#L1414

[3]

backup_stream_scheduler: https://github.com/tikv/tikv/blob/release-8.0/components/server/src/server.rs#L891

[4]

on_flush_applied_cmd_batch(): https://github.com/tikv/tikv/blob/release-8.0/components/raftstore/src/coprocessor/mod.rs#L581

[5]

Task::BatchEvent: https://github.com/tikv/tikv/blob/release-8.0/components/raftstore/src/coprocessor/mod.rs#L504

[6]

backup_batch(): https://github.com/tikv/tikv/blob/release-8.0/components/backup-stream/src/endpoint.rs#L479

[7]

router.on_events(): https://github.com/tikv/tikv/blob/release-8.0/components/backup-stream/src/router.rs#L595

[8]

endpoint.do_flush(): https://github.com/tikv/tikv/blob/release-8.0/components/backup-stream/src/endpoint.rs#L825

[9]

flush_ob.after(): https://github.com/tikv/tikv/blob/release-8.0/components/backup-stream/src/checkpoint_manager.rs#L526

[10]

最小值: https://github.com/pingcap/tidb/blob/release-8.0/br/pkg/streamhelper/advancer.go#L295

[11]

CheckpointAdvancer: https://github.com/pingcap/tidb/blob/master/br/pkg/streamhelper/advancer.go#L57

[12]

FlushSubscriber: https://github.com/pingcap/tidb/blob/release-8.0/br/pkg/streamhelper/flush_subscriber.go#L27

[13]

持续监听: https://github.com/pingcap/tidb/blob/release-8.0/br/pkg/streamhelper/flush_subscriber.go#L250

[14]

checkpoints: https://github.com/pingcap/tidb/blob/release-8.0/br/pkg/streamhelper/advancer.go#L56

[15]

tick(): https://github.com/pingcap/tidb/blob/release-8.0/br/pkg/streamhelper/advancer.go#L645

[16]

optionalTick(): https://github.com/pingcap/tidb/blob/master/br/pkg/streamhelper/advancer.go#L622

[17]

importantTick(): https://github.com/pingcap/tidb/blob/master/br/pkg/streamhelper/advancer.go#L587

[18]

Service Safepoint: https://github.com/pingcap/tidb/blob/master/br/pkg/streamhelper/advancer.go#L605

[19]

检查: https://github.com/pingcap/tidb/blob/master/br/pkg/streamhelper/advancer.go#L594

[20]

标记为异常状态: https://github.com/pingcap/tidb/blob/master/br/pkg/streamhelper/advancer.go#L598

[21]

跳过: https://github.com/pingcap/tidb/blob/master/br/pkg/streamhelper/advancer.go#L646

[22]

发送信号: https://github.com/pingcap/tidb/blob/master/br/pkg/streamhelper/advancer.go#L465

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2210397.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

通过多元蒙特卡罗模拟来预测股票价格的日内波动性

作者&#xff1a;老余捞鱼 原创不易&#xff0c;转载请标明出处及原作者。 写在前面的话&#xff1a; 日内价格波动对交易策略的重要性不言而喻&#xff0c;尤其是美跨式交易策略&#xff08;The American straddle&#xff09;。由于无法预测所有影响股价的因素&#x…

【原创】java+springboot+mysql法律咨询网系统设计与实现

个人主页&#xff1a;程序猿小小杨 个人简介&#xff1a;从事开发多年&#xff0c;Java、Php、Python、前端开发均有涉猎 博客内容&#xff1a;Java项目实战、项目演示、技术分享 文末有作者名片&#xff0c;希望和大家一起共同进步&#xff0c;你只管努力&#xff0c;剩下的交…

loadlocale.c:130: _nl_intern_locale_data: failed已放弃 (核心已转储)]问题

在进行交叉编译cortex-a9架构时&#xff0c;出现以上错误。 问题描述&#xff1a; 在使用 arm-none-linux-gnueabi-gdb 进行 Cortex-A9 架构交叉编译调试时&#xff0c;出现如下错误&#xff1a; arm-none-linux-gnueabi-gdb: loadlocale.c:130: _nl_intern_locale_data: As…

新型物联网电力数据采集器 智能网关通讯协议有哪些?

随着智能化技术的快速发展&#xff0c;电气监测与管理在各个域的应用愈发重要&#xff0c;在物联网&#xff08;IoT&#xff09;应用的发展中&#xff0c;网关扮演着至关重要的角色。它作为连接设备与云平台或数据中心的桥梁&#xff0c;负责数据的收集、处理和传输。网关不仅支…

鸿蒙开发之ArkUI 界面篇 三十四 容器组件Tabs二 常用属性

barPosition&#xff1a;位置开头或结尾,vertical 水平或者垂直,scrollable手势滑动切换,animationDuration 滑动动画时间。BarPosition.Start 效果如下图&#xff1a; BarPosition.End 效果如下图&#xff1a; 如果显示在左边&#xff0c;使用的是vertical属性,下图&#xff0…

Chrome(谷歌)浏览器 数据JSON格式美化 2024显示插件安装和使用

文章目录 目录 文章目录 安装流程 小结 概要安装流程技术细节小结 概要 没有美化的格式浏览器展示 美化之后效果图 安装流程 下载地址 https://github.com/gildas-lormeau/JSONVue 点击下载 下载成功&#xff0c;如图所示 解压文件 添加成功&#xff0c;如图所示 通过浏览器…

密码学算法概览大全

区块链密码学 目录 对称密码算法 1. 流密码2. 分组密码3. 对称密码算法小结4. 对称密码算法在区块链中的应用 非对称密码算法 1. RSA2. ECC3. 非对称密码算法小结4. 非对称密码算法在区块链中的应用 Hash函数 1. SHA2. RipeMD-1603. Hash函数在区块链中的应用 PKI 1. PKI组成2…

STM32——USART原理及应用

1.什么是USART&#xff1f; 1.1 基本概念 USART英文全称&#xff1a;universal asynchronous receiver and transmitter &#xff0c;翻译过来就是&#xff1a;通用同步异步收/发器。USART是STM32内部集成的硬件外设&#xff0c;可根据数据寄存器的一个字节数据自动生成数据帧…

Python | Leetcode Python题解之第476题数字的补数

题目&#xff1a; 题解&#xff1a; class Solution:def findComplement(self, num: int) -> int:highbit 0for i in range(1, 30 1):if num > (1 << i):highbit ielse:breakmask (1 << (highbit 1)) - 1return num ^ mask

NVIDIA Bluefield DPU上的启动流程4个阶段分别是什么?作用是什么?

文章目录 Bluefield上的硬件介绍启动流程启动流程:eMMC中的两个存储分区:ATF介绍ATF启动的四个阶段:四个主要步骤:各个阶段依赖的启动文件一次烧录fw失败后的信息看启动流程综述Bluefield上的硬件介绍 本文以Bluefield2为例,可以看到RSHIM实际上是Boot相关的集合。也能看…

QT 连接SQL SEVER 之后无法读取浮点和整型

1、ODBC Driver 的版本要对应上。 if (!strDbDirPath.isEmpty())m_strDbDirPath strDbDirPath;m_strDatabaseName strDatabaseName;if (m_database.isOpen() || m_bConnected){qDebug() << QString("QODBC:已经连接成功&#xff01;") << "\n&quo…

八、Linux之实用指令

1、指定运行级别 1.1 基本介绍 运行级别说明 0 &#xff1a;关机 1 &#xff1a;单用户【找回丢失密码】 2&#xff1a;多用户状态没有网络服务&#xff08;用的非常少&#xff09; 3&#xff1a;多用户状态有网络服务&#xff08;用的最多&#xff09; 4&#xff1a;系统未使…

《Linux运维总结:基于ARM64+X86_64架构CPU使用docker-compose一键离线部署mongodb 7.0.14容器版分片集群》

总结&#xff1a;整理不易&#xff0c;如果对你有帮助&#xff0c;可否点赞关注一下&#xff1f; 更多详细内容请参考&#xff1a;《Linux运维篇&#xff1a;Linux系统运维指南》 一、部署背景 由于业务系统的特殊性&#xff0c;我们需要面向不通的客户安装我们的业务系统&…

【自动驾驶汽车通讯协议】GMSL通信技术以及加串器(Serializer)解串器(Deserializer)介绍

文章目录 0. 前言1. GMSL技术概述2. 为什么需要SerDes&#xff1f;3. GMSL技术特点4.自动驾驶汽车中的应用5. 结论 0. 前言 按照国际惯例&#xff0c;首先声明&#xff1a;本文只是我自己学习的理解&#xff0c;虽然参考了他人的宝贵见解及成果&#xff0c;但是内容可能存在不准…

图解 微信开发者工具 小程序源码 调试、断点标记方法 , 微信小程序调试器,真机调试断点调试方法,小程序网络API请求调试方法 总结

在我们使用微信开发者工具进行微信小程序开发的时候&#xff0c;在这个微信开发者工具的代码编辑框里面我们是无法像使用vscode, idea等IDE工具时那样直接对代码打断点进行调试&#xff0c; 原因是小程序实际上他就是一个web浏览器应用的包装, 在其内部使用的还是类似chrome的…

mac 桌面版docker no space left on device

报错信息 docker pull镜像时报&#xff1a; failed to register layer: Error processing tar file(exit status 1): write /home/admin/oceanbase_bak/bin/observer: no space left on device 解决 增加 docker 虚拟磁盘大小。 调整完点击重启即可。

如何使用vscode的launch.json来debug调试

1、创建一个launch.json文件 选择Python Debugger&#xff0c;再选择Python文件&#xff0c;创建处理如下 默认有下面五个参数 "name": "Python Debugger: Current File","type": "debugpy","request": "launch"…

使用OpenCV处理视频并显示灰度图像

目录 导入OpenCV库打开视频文件定义显示图像的函数检查视频是否成功打开并读取第一帧循环读取视频帧释放资源完整代码 导入OpenCV库 import cv2 # 导入OpenCV库打开视频文件 注意&#xff1a;视频随便去搜一个就可以 我用的是风景的 # 打开视频文件 vc cv2.VideoCapture(b…

ubuntu18.04系统中图形化界面

一、Ubuntu 18.04 中&#xff0c;使用 GDM 作为默认的图形用户界面&#xff08;GUI&#xff09;管理器。GDM 是 GNOME Display Manager 的缩写&#xff0c;它是用于 Ubuntu 的显示管理器&#xff0c;负责处理登录和会话管理。 通过命令行重启 Ubuntu 18.04 上的图形界面服务&am…

本地部署Ollama+千文大模型,docker openui访问

文章目录 前言 1. 运行Ollama 2. 安装Open WebUI 2.1 在Windows系统安装Docker 2.2 使用Docker部署Open WebUI 前言 本文主要介绍如何在Windows系统快速部署Ollama开源大语言模型运行工具&#xff0c;并安装Open WebUI结合cpolar内网穿透软件&#xff0c;实现在公网环境也能访问…