TiDB 混沌工程实践:如何打造健壮的分布式系统?

2024-04-08 02:58

本文主要是介绍TiDB 混沌工程实践:如何打造健壮的分布式系统?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本文转载自 InfoQ 网站

作者:唐刘

策划:赵钰莹

原文链接:https://www.infoq.cn/article/bxGvrb_CxAZD6Wv3fUj8

作为一个分布式数据库,TiDB 面临的严重挑战在于如何让用户相信存储在 TiDB 里面的数据是安全的,不会出现丢失,损坏等情况。因此,在 TiDB 研发初期,PingCAP 就引入了混沌工程,来保证 TiDB 在各种极端情况下面的稳定性。本文整理自 ArchSummit 全球架构师峰会(深圳站)2019 峰会演讲,分享了 TiDB 应用混沌工程的方法,介绍基于 K8s 自研的自动化测试平台 Schrodinger,并通过实际例子说明如何在 Schrodinger 里应用混沌来测试系统。

大家好!我是唐刘,现在是 PingCAP 的首席架构师,同时负责 TiDB 底层组件 TiKV 的研发,该项目属于 CNCF 孵化中项目,应该也是国内唯一进入 CNCF 的数据库项目。同时,我也是典型的开源爱好者,做了很多 go-mysql, raft.rs ,grpc-rs 等开源组件的工作。

为什么需要混沌工程?

假设,我们现在开始建造一个系统,无论该系统的具体功能是什么,我们都需要保证系统的稳定性,但是如何知道系统是否处于稳定状态呢?通常,团队可以通过单元测试、集成测试和性能测试等手段进行验证。但是,无论这些测试写的多好,我们认为都远远不够,因为错误可以在任何时间发生,尤其是对分布式系统而言,此时就需要引入混沌工程(Chaos Engineering)。

以 TiDB 的实际生产流程为例,由于 TiDB 底层采用 Raft 一致性协议进行副本复制,因此存在 Follower 和 Leader 的概念,Follower 被动接受 Leader 的日志,同步相关数据。当新的 Follower 节点加入集群后,Leader 会给 Follower 发送 Snapshot,也就是说,Leader 会把当前整个数据进项打包成 Snapshot 发给 Follower。在 TiDB 里面,Snapshot 包括四部分,分别是 Meta 文件,default.sst,write.sst 和 lock.sst。Meta 文件记录数据文件源信息,包括数据文件的大小等,其余三个是数据文件。当 Follower 接收到 Snapshot 文件后,会进行 Save Snapshot 的操作,将四个部分存到不同的文件里面。然后,Follower 会 Check Snapshot,也就是检查 Snapshot 的正确性,如果 Snapshot 是正确的,就会将其应用到整个 Follower 状态。如上图所示,在 Save Snapshot 和 Check Snapshot 之间发生了 Panic,并且进行了重启。要知道,对 Linux 系统而言,如果写文件时进程挂掉,但 Linux 系统没有挂掉,那么这个文件还可以认为是安全的,虽然会把文件写到 Page  Cache 里面,但挂掉之后,Linux 系统会强制将 Page  Cache 刷到磁盘里面,保证文件安全。但是,当我们的 Follower 挂掉重启之后,我们发现文件出现丢失,如上图所示 write.sst 变成了 0 兆,但根据 Meta 文件,write.sst 不可能是 0 兆。也就是说,在磁盘没有任何问题的情况下,进程重启后出现了文件丢失。通过查看 dmseg,出现了 SLUB:unable to allocate memoy on node 的提示。这可以理解为,虽然系统没有出现问题,但可能由于内存不足等其他问题让文件 Page  Cache 无法正常进行,此时就会出现上述问题。对我们来说,虽然很多时候可以认为程序没有问题,但是与程序一起合作的操作系统可能会出现 Bug,导致整个数据丢失,这是对程序进行多少次单元测试都无法避免的事情。

上图是第二个示例,显示的是分布式系统里常见的 Gray Failure 问题。通常情况下,判断一个程序的死活,很直观的感觉就是写一个 Checker 程序,定期运行以试探程序状态。我们可能会出现一种情况,就是上文提到的 Gray Failure,具体指的是检查程序与整个系统相通,但客户端与系统很可能已经完全无法交互,我们自认为系统是好的,但实际上系统已经出现问题。综上,分布式系统会出现很多仅仅通过测试无法解决的问题,因此我们想到了非常好的解决方法就是混沌工程。

混沌工程是什么?

混沌工程的概念很早之前就有,但是直到 2012 年,Netflix 公司才让外界更多人知道了“混沌”。为了更好地推广混沌工程,Netflix 引入了一只猴子(Chaos Monkey),试想一只猴子在系统里面,平时是安安静静的,什么事情都不做,突然一天发疯开始在系统里到处捣乱,作为工程师,要干的一件事情就是逮住这只猴子,让它别捣乱,这大概就是混沌工程要表达的意思。简单来说,混沌工程也是一个工程学科,这就意味着需要做实验,通过设计进行混沌实验,观察系统对各类故障的真实反映,以此来完善保证系统的稳定性。 但是在开始混沌工程之前,这一切的前提是确保系统是容错的 ,也就是平常所说的双活、多活。假设系统是典型的单点架构,只要单点损坏,整个系统就崩溃了,没法验证混沌工程的效果,因此系统必须能够支持容错,然后通过不断的故障引入来验证系统容错性,如果系统不能容错,我们不限要考虑的是让系统能容错,从而再去考虑混沌工程。

具体到实践层面,大家可以参考 Principles of Chaos Engineering 网页里面的步骤。如上图,第一步,需要定义系统稳态,通常情况下,可以通过 metrics 或者客户端指标定义系统,比如 QPS、延迟等,只要这些指标没有太大波动,就可以认为系统是稳定的;第二步,定义系统稳态后,我们分为实验组和对照组进行实验,假设无论对实验组做任何操作,整个系统都可以继续维持稳定状态;第三步,引进现实生活中的变量,也就是模拟现实世界可能发生的错误故障,比如硬件故障,网络延迟隔离等到实验组中;最后,比较实验组和对照组前后稳定状态的差异,是否可以满足预期。如果前后保持一致,则可以认为系统对该故障的有容错能力;反之,如果两者的稳定状态不一致,那就找到了一个系统弱点,从而可以修复它,提高系统可靠性。

如上图以 TiDB 为例,对三副本的 Raft 算法而言,Leader 对外提供客户端写入操作。如果把 Leader 干掉,Follower 会立刻选出一个新的 Leader,继续对外提供服务,对于这个系统来说,如果要做混沌工程,应该怎么做呢?首先,根据一些指标定义系统稳态,比如 QPS;其次,假设客户端的 QPS 在受到攻击,Leader 节点被杀死后会出现一个抖动,Follower 会立马选出新的 Leader 节点,迅速恢复至稳定状态;第三步,进行错误注入实验;最后,观察结果,如果发现系统 QPS 降为零并再也没有恢复,证明系统有 bug,我们就需要去找出问题并修正;反之,如果 QPS 恢复了,则证明系统可以容忍这次故障,可以继续进行下一个实验。为了更好地进行混沌工程实践,Netflix 在官网提供了相关原则:第一个原则是构建系统稳态的假设;第二个原则是引入现实环境的变量事件;第三个原则是在生产环境中运行实验,此处需要注意任何在生产环境进行的操作都是有风险的,因此需要提前与相关部门进行沟通,以免因为疏忽导致业务挂掉不可用;第四个原则是持续自动化运行实验,如果全部通过手工方式实现,效率将非常低;最后一个原则是要控制好“爆炸半径”,在进行混沌实验时一定要注意受影响的范围,如果没有预估好,很容易导致所有的用户都没法使用,这是很严重的问题。

PingCAP 在 TiDB 实践混沌工程

在 PingCAP,我们主要针对 TiDB 进行混沌工程实践,重点关注两个大方向:一是发现错误;二是注入错误。在 TiDB,我们采用的是比较原始的三种方法分析系统状态:Metrics、Log 和 Tracing。

第一种,是基于 Metrics,TiDB 使用的是普罗米修斯,以下是典型的 QPS 曲线图,可以看到凌晨两点,latency 曲线突然飙升。

于是,我们有一个非常粗暴简单的脚本,当检测到延迟大于某个阈值时,就会发起告警。

但是,如果认真观察前后两天的曲线,可以看出每天固定时间的延迟都会升高,这可认为是用户正常的工作负载,如果只是简单粗暴的通过 Metrics 等弱指标进行相关判断,并不能很好地发现系统相关问题,所以需要查看历史数据,尤其是 Metrics 的历史,然后进行比较,就可以基本判断出数据是否正常。当然,我们也会通过机器学习的方式进行更精确的判断。

第二种是基于 Log,因为 Log 里面存放了详细的错误信息,但是作为一家创业公司,我们现阶段还没办法做一整套 Log 系统,因此采用了业界比较主流的开源方案,比如 FluentBit 或 Promtail,将这些数据导入 ES 或 LOKI 进行相关分析。后续我们也会自己写相关日志分析组件,比如,对于 transaction,我们会有一个 transaction ID,将事物查询可能会分到多个不同的组件上,都会有统一的 ID 详细显示出来,这其实是通过 Log 进行分析。

第三种是引入 Tracing,虽然我们采用的是业界通用的主流方案—— TiDB 支持 OpenTracing,但我一直认为,只有当 Log 或者 Metrics 没办法解决问题时,才不得已使用 Tracing,因为开启 Tracing 会对整个系统的性能产生一定影响。通常情况下,TiDB 默认关闭 Tracing,仅在必要时才会启动该方法,比如需要查询到底在哪个地方消耗较多时间等。现在,Metrics、Log 和 Tracing 也会被称作 Observability(可观测性),TiDB 的可观测性还是采用业界的主流方案,并没有做太多定制化。

故障注入

学会发现错误之后,接下来就是考虑如何注入错误,对系统引入各种故障。因为 TiDB 是一个分布式数据库,所以我们主要关心两个问题:网络和文件系统的故障。因为是分布式的,所以一定绕不开网络问题;因为需要进行数据存储,因此要考虑文件系统。虽然现在有很多网络拓扑结构,但如果要对网络进行错误注入,通常情况下有三种模型:

如上图所示,第一种是 Complete,两个节点之间的网络完全不通;第二种是 Simplex,A 能给 B 发送消息,但是 B 不能给 A 回复消息;第三种是 Partial,A 和 B 完全不通,但是 A 和 B 能够通过另一个节点也就是 C 进行互动。对 TiDB 而言,我们尽量模拟相关网络环境,尽可能多的发现在网络隔离下面的错误。

这里,一个比较典型的例子,我们称之为 Network Partition Ring 。如上图,假设有五个节点,一共分成五组,在这个组里面,N1 可以给 N2、N3、N4、N5 发送消息,但是 N1 只能够收取 N2 和 N3 的消息,不能收取 N4 和 N5 的消息。其实这种网络拓扑出现的问题在现实生活中很难被发现,为什么还需要来做这个事情呢?我们希望进行混沌实验,在还没出现对用户造成伤害之前,我们可以主动发现并解决这些问题。除了网络,存储也需要进行相应的故障注入。

在 TiDB 里,我们主要是通过 Fuse 的机制进行文件系统干扰,如上图所示,实际数据可能存储在 /Root/O 路径下,可以通过 Fuse Mount 到另外一个路径下面,让应用程序跟在 Mount 路径进行交互。因为采用的 Fuse,Mount 的时候可以在整个 IO 链上做错误注入。通过这种方式,我们能够非常方便地模拟各种 IO 错误的情况,如果不想使用 Fuse,也可以考虑 Linux 的其他 Debug 工具。

对文件系统而言,我们可能还有更加粗暴的一种方式。在 TiDB 里面,我们也会经常将电源拔掉,手工触发断电、断网等情况,以考察系统是否可以维持稳定,以下是我们常用的错误画像,仅供参考:

此外,对分布式系统测试而言,Jepsen 也是一个不错的工具,对错误注入感兴趣的可以参考 Jepsen 的代码。不过,Jepsen 是用 Clojure 语言编写的,有些难以理解。

云上混沌工程实践

在 TiDB 研发初期,PingCAP 就对其引入了混沌工程。早期,如果需要进行混沌工程实验,只能自己申请几台冗余或闲置的机器,所有实验都需要手动完成,包括自己构建并发布整个 TiDB 集群,虽然这个过程也发现了不少问题,但手工部署耗时且非常低效,在资源利用上也十分不合理。

我们决定简化整个流程。如上图,第一步是通过 Kubernetes 更好的管理机器;第二步是进行流程自动化,因此,基于 Kubernetes 平台我们搭建了一套自动化的混沌工程平台——薛定谔平台(Schrodinger)。

如上图,在 Kubernetes 里面有三个 Box,每个 Box 都有两个用例,通过随机注入来验证系统是否可以保持稳定。实现自动化之后,只需要将错误输入薛定谔平台,该平台就可以自动编译版本,自动运行相关测试用例。如果用例挂掉了,系统会通知我们进行相应处理。PingCAP 现在已经跟其他企业合作,努力优化做更加通用的混沌工程平台,让大家能够把自己的业务放到这个平台上跑。因为我们仍然基于 Kubernetes,只要将集群 Helm 的配置文件与混沌工程结合,就可以直接运行在我们的平台上。如果大家对一些 Kubernetes 的概念不熟悉,可以对比 Linux 的相关概念理解。

具体来说,要想将业务运行在该平台之上,主要是使用 Chaos Operator,Operator 会把所有对象就是 Chaos 定义成 CRD,在不同的物理节点上启动一个 DaemonSets,这个 DaemonSets 就负责干扰不同的 Load,以及上面不同的 Pod,对应的 Pod 里面会注入一个 Sidecar,这可以认为是一个 Thread,Sidecar 帮我们进行注入,负责破坏 Pod。对用户来说,只要提供他自己的 Helm  Chart,同时把我们的 Chaos CRD 一起放到 Chaos  Operator 里面即可。Chaos Operator 启动之后,会通过 Web  Hook 的方式把 Daemmsets 起来,随后进行系列操作。

嘉宾介绍:唐刘,PingCAP 首席架构师,主要是负责分布式 key-value TiKV 的研发工作,也会折腾下 TiDB 整个产品的测试,工具开发等工作。

这篇关于TiDB 混沌工程实践:如何打造健壮的分布式系统?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/884385

相关文章

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

基于 YOLOv5 的积水检测系统:打造高效智能的智慧城市应用

在城市发展中,积水问题日益严重,特别是在大雨过后,积水往往会影响交通甚至威胁人们的安全。通过现代计算机视觉技术,我们能够智能化地检测和识别积水区域,减少潜在危险。本文将介绍如何使用 YOLOv5 和 PyQt5 搭建一个积水检测系统,结合深度学习和直观的图形界面,为用户提供高效的解决方案。 源码地址: PyQt5+YoloV5 实现积水检测系统 预览: 项目背景

pip-tools:打造可重复、可控的 Python 开发环境,解决依赖关系,让代码更稳定

在 Python 开发中,管理依赖关系是一项繁琐且容易出错的任务。手动更新依赖版本、处理冲突、确保一致性等等,都可能让开发者感到头疼。而 pip-tools 为开发者提供了一套稳定可靠的解决方案。 什么是 pip-tools? pip-tools 是一组命令行工具,旨在简化 Python 依赖关系的管理,确保项目环境的稳定性和可重复性。它主要包含两个核心工具:pip-compile 和 pip

Jenkins构建Maven聚合工程,指定构建子模块

一、设置单独编译构建子模块 配置: 1、Root POM指向父pom.xml 2、Goals and options指定构建模块的参数: mvn -pl project1/project1-son -am clean package 单独构建project1-son项目以及它所依赖的其它项目。 说明: mvn clean package -pl 父级模块名/子模块名 -am参数

Prometheus与Grafana在DevOps中的应用与最佳实践

Prometheus 与 Grafana 在 DevOps 中的应用与最佳实践 随着 DevOps 文化和实践的普及,监控和可视化工具已成为 DevOps 工具链中不可或缺的部分。Prometheus 和 Grafana 是其中最受欢迎的开源监控解决方案之一,它们的结合能够为系统和应用程序提供全面的监控、告警和可视化展示。本篇文章将详细探讨 Prometheus 和 Grafana 在 DevO

springboot整合swagger2之最佳实践

来源:https://blog.lqdev.cn/2018/07/21/springboot/chapter-ten/ Swagger是一款RESTful接口的文档在线自动生成、功能测试功能框架。 一个规范和完整的框架,用于生成、描述、调用和可视化RESTful风格的Web服务,加上swagger-ui,可以有很好的呈现。 SpringBoot集成 pom <!--swagge

分布式系统的个人理解小结

分布式系统:分的微小服务,以小而独立的业务为单位,形成子系统。 然后分布式系统中需要有统一的调用,形成大的聚合服务。 同时,微服务群,需要有交流(通讯,注册中心,同步,异步),有管理(监控,调度)。 对外服务,需要有控制的对外开发,安全网关。

分布式系统的主要考虑

异构性:分布式系统由于基于不同的网路、操作系统、计算机硬件和编程语言来构造,必须要考虑一种通用的网络通讯协议来屏蔽异构系统之间的禅意。一般交由中间件来处理这些差异。缺乏全球时钟:在程序需要协作时,它们通过交换消息来协调它们的动作。紧密的协调经常依赖于对程序动作发生时间的共识,但是,实际上网络上计算机同步时钟的准确性受到极大的限制,即没有一个正确时间的全局概念。这是通过网络发送消息作为唯一的通信方式