DDIA 读书分享 第五章:Replication,主从

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
云原生数据库 PolarDB PostgreSQL 版,标准版 2核4GB 50GB
云原生数据库 PolarDB MySQL 版,通用型 2核4GB 50GB
简介: DDIA 读书分享 第五章:Replication,主从

DDIA 读书分享会,会逐章进行分享,结合我在工业界分布式存储和数据库的一些经验,补充一些细节。每两周左右分享一次,欢迎加入,Schedule 在这里[1]。我们有个对应的分布式&数据库讨论群,每次分享前会在群里通知。如想加入,可以加我的微信号:qtmuniao,简单自我介绍下,并注明:分布式系统群。

本书第一部分讲单机数据系统,第二部分讲多机数据系统。

冗余(Replication)是指将同一份数据复制多份,放到通过网络互联的多个机器上去。其好处有:

  1. 降低延迟:可以在地理上同时接近不同地区的用户。
  2. 提高可用性:当系统部分故障时仍然能够正常提供服务。
  3. 提高读吞吐:平滑扩展可用于查询的机器。

本章假设我们的数据系统中所有数据能够存放到一台机器中,则本章只需考虑多机冗余的问题。如果数据超过单机尺度该怎么办?那是下一章要解决的事情。

如果数据是只读的,则冗余很好做,直接复制到多机即可。我们有时可以利用这个特性,使用分治策略,将数据分为只读部分和读写部分,则只读部分的冗余就会容易处理的多,甚至可以用 EC 方式做冗余,减小存储放大的同时,还提高了可用性。

  • 想想 EC 牺牲了什么?
    以计算换存储。

但难点就在于,数据允许数据变更时,如何维护多机冗余且一致。常用的冗余控制算法有:

  1. 单领导者(single leader)
  2. 多领导者(multi-leader)
  3. 无领导者(leaderless)

这需要在多方面做取舍:

  1. 使用同步复制还是异步复制
  2. 如何处理失败的副本

数据库冗余问题在学术界不是一个新问题了,但在工业界,大部分人都是新手——分布式数据库是近些年才大规模的在工业界落地的。

领导者与跟随者

冗余存储的每份数据称为副本(replica)。多副本所带来的最主要的一个问题是:如何保证所有数据被同步到了所有副本上?

基于领导者(leader-based)的同步算法,是最常用解决办法。

  1. 其中一个副本称为领导者(leader),别称主副本(primary、master)。主副本作为写入的协调者,所有写入都要发给主副本。
  2. 其他副本称为跟随者(follower),也称为只读副本(read replicas)、从副本(slaves)、次副本(secondaries)、热备(hot-standby)。主副本将改动写到本地后,将其发送给各个从副本,从副本收变动到后应用到自己状态机,这个过程称为日志同步(replication log)、变更流(change steam)。
  3. 对于读取,客户端可以从主副本和从副本中读取;但写入,客户端只能将请求发到主副本。

image.png

                      主从同步,一主两从,主写从读

根据我的习惯,下面通称主副本和从副本。

有很多数据系统都用了此模式:

  1. 关系型数据库:PostgreSQL(9.0+)、MySQL 和 Oracle Data Guard 和 SQL Server 的 AlwaysOn
  2. 非关系型数据库:MonogoDB、RethinkDB 和 Espresso
  3. 消息队列:Kafka 和 RabbitMQ。

同步复制和异步复制

同步(synchronously)复制异步(asynchronously)复制和关键区别在于:请求何时返回给客户端。

  1. 如果等待某副本写完成后,则该副本为同步复制。
  2. 如果不等待某副本写完成,则该副本为异步复制。

image.png

                         同步复制和异步复制

两者的对比如下:

  1. 同步复制牺牲了响应延迟部分可用性(在某些副本有问题时不能完成写入操作),换取了所有副本的一致性(但并不能严格保证)。
  2. 异步复制放松了一致性,而换来了较低的写入延迟和较高的可用性。

在实践中,会根据对一致性和可用性的要求,进行取舍。针对所有从副本来说,可以有以下选择:

  1. 全同步:所有的从副本都同步写入。如果副本数过多,可能性能较差,当然也可以做并行化、流水线化处理。
  2. 半同步:(semi-synchronous),有一些副本为同步,另一些副本为异步。
  3. 全异步:所有的从副本都异步写入。网络环境比较好的话,可以这么配置。

异步复制可能会造成副本丢失等严重问题,为了能兼顾一致性和性能,学术界也在不断研究新的复制方法。如,链式复制(chain-replication)

多副本的一致性和共识性有诸多联系,本书后面章节会讨论。

新增副本

在很多情况下,需要给现有系统新增副本。

如果原副本是只读(read-only)的,只需要简单拷贝即可。但是如果是可写副本,则问题要复杂很多。因此,比较简单的一种解决方法是:禁止写入,然后拷贝。这在某些情况下很有用,比如夜间没有写入流量,同时一晚上肯定能复制完。

如果要不停机,可以:

  1. 主副本在本地做一致性快照。何谓一致性?
  2. 将快照复制到从副本节点。
  3. 从主副本拉取快照之后的操作日志,应用到从副本。如何知道快照与其后日志的对应关系?序列号。
  4. 当从副本赶上主副本进度后,就可以正常跟随主副本了。

这个过程一般是自动化的,比如 Raft 中;当然也可以手动化,比如写一些脚本。

宕机处理

系统中任何节点都可能在计划内或者计划外宕机。那么如何应对这些宕机情况,保持整个系统的可用性呢?

从副本宕机:追赶恢复

类似于新增从副本。如果落后的多,可以直接向主副本拉取快照+日志;如果落后的少,可以仅拉取缺失日志。

主副本宕机:故障转移。

处理相对麻烦,首先要选出新的主副本,然后要通知所有客户端主副本变更。具体来说,包含下面步骤:

  1. 确认主副本故障。要防止由于网络抖动造成的误判。一般会用心跳探活,并设置合理超时(timeout)阈值,超过阈值后没有收到该节点心跳,则认为该节点故障。
  2. 选择新的主副本。新的主副本可以通过选举(共识问题)或者指定(外部控制程序)来产生。选主时,要保证备选节点数据尽可能的新,以最小化数据损失。
  3. 让系统感知新主副本。系统其他参与方,包括从副本、客户端和旧主副本。前两者不多说,旧主副本在恢复时,需要通过某种手段,让其知道已经失去领导权,避免脑裂

主副本切换时,会遇到很多问题:

  1. 新老主副本数据冲突。新主副本在上位前没有同步完所有日志,旧主副本恢复后,可能会发现和新主副本数据冲突。
  2. 相关外部系统冲突。即新主副本,和使用该副本数据的外部系统冲突。书中举了 github 数据库 MySQL 和缓存系统 redis 冲突的例子。
  3. 新老主副本角色冲突。即新老主副本都以为自己才是主副本,称为脑裂(split brain)。如果他们两个都能接受写入,且没有冲突解决机制,数据会丢失或者损坏。有的系统会在检测到脑裂后,关闭其中一个副本,但设计的不好可能将两个主副本都关闭掉。
  4. 超时阈值选取。如果超时阈值选取的过小,在不稳定的网络环境中(或者主副本负载过高)可能会造成主副本频繁的切换;如果选取过大,则不能及时进行故障切换,且恢复时间也增长,从而造成服务长时间不可用。

所有上述问题,在不同需求、不同环境、不同时间点,都可能会有不同的解决方案。因此在系统上线初期,不少运维团队更愿意手动进行切换;等积累一定经验后,再进行逐步自动化。

节点故障;不可靠网络;在一致性、持久化、可用性和延迟间的取舍;等等问题,都是设计分布式系统时,所面临的的基本问题。根据实际情况,对这些问题进行艺术化的取舍,便是分布式系统之美。

日志复制

在数据库中,基于领导者的多副本是如何实现的?在不同层次有多种方法,包括:

  1. 语句层面的复制。
  2. 预写日志的复制
  3. 逻辑日志的复制
  4. 触发器的复制

对于一个系统来说,多副本同步的是什么?增量修改

具体到一个由数据库构成的数据系统,通常由数据库外部的应用层、数据库内部查询层存储层组成。修改在查询层表现为:语句;在存储层表现为:存储引擎相关的预写日志、存储引擎无关的逻辑日志;修改完成后,在应用层表现为:触发器逻辑。

基于语句的复制

主副本记录下所有更新语句:INSERTUPDATEDELETE 然后发给从库。主副本在这里类似于充当其他从副本的伪客户端

但这种方法有一些问题:

  1. 非确定性函数(nondeterministic)的语句可能会在不同副本造成不同改动。如 NOW()、RAND()
  2. 使用自增列,或依赖于现有数据。则不同用户的语句需要完全按相同顺序执行,当有并发事务时,可能会造成不同的执行顺序,进而导致副本不一致。
  3. 有副作用(触发器、存储过程、UDF)的语句,可能不同副本由于上下文不同,产生的副作用不一样。除非副作用是确定的输出。

当然也有解决办法:

  1. 识别所有产生非确定性结果的语句。
  2. 对于这些语句同步值而非语句。

但是 Corner Case 实在太多,步骤 1 需要考虑的情况太多。

传输预写日志( WAL)

我们发现主流的存储引擎都有预写日志(WAL,为了宕机恢复):

  1. 对于日志流派(LSM-Tree,如 LevelDB),每次修改先写入 log 文件,防止写入 MemTable 中的数据丢失。
  2. 对于原地更新流派(B+ Tree),每次修改先写入 WAL,以进行崩溃恢复。

所有用户层面的改动,最终都要作为状态落到存储引擎里,而存储引擎通常会维护一个:

  1. 追加写入
  2. 可重放

这种结构,天然适合备份同步。本质是因为磁盘的读写特点和网络类似:磁盘是顺序写比较高效,网络是只支持流式写。具体来说,主副本在写入 WAL 时,会同时通过网络发送对应的日志给所有从副本。

书中提到一个数据库版本升级的问题:

  1. 如果允许旧版本代码给新版本代码(应该会自然做到后向兼容)发送日志(前向兼容)。则在升级时可以先升级从库,再切换升级主库。
  2. 否则,只能进行停机升级软件版本。

逻辑日志复制(基于行)

为了和具体的存储引擎物理格式解耦,在做数据同步时,可以使用不同的日志格式:逻辑日志

对于关系型数据库来说,行是一个合适的粒度:

  1. 对于插入行:日志需包含所有列值。
  2. 对于删除行:日志需要包含待删除行标识,可以是主键,也可以是其他任何可以唯一标识行的信息。
  3. 对于更新行:日志需要包含待更新行的标志,以及所有列值(至少是要更新的列值)

对于多行修改来说,比如事务,可以在修改之后增加一条事务提交的记录。MySQL 的 binlog 就是这么干的。

使用逻辑日志的好处有:

  1. 方便新旧版本的代码兼容,更好的进行滚动升级。
  2. 允许不同副本使用不同的存储引擎。
  3. 允许导出变动做各种变换。如导出到数据仓库进行离线分析、建立索引、增加缓存等等。

之前分析过一种基于日志,统一各种数据系统的文章[2],很有意思。

基于触发器的复制

前面所说方法,都是在数据库内部对数据进行多副本同步。

但有些情况下,可能需要用户决策,如何对数据进行复制:

  1. 对需要复制的数据进行过滤,只复制一个子集。
  2. 将数据从一种数据库复制到另外一种数据库。

有些数据库如 Oracle 会提供一些工具。但对于另外一些数据库,可以使用触发器和存储过程。即,将用户代码 hook 到数据库中去执行。

基于触发器的复制,性能较差且更易出错;但是给了用户更多的灵活性。

参考资料

[1]DDIA 读书分享会: https://docs.qq.com/sheet/DWHFzdk5lUWx4UWJq

[2]以日志视角对数据系统大一统: https://zhuanlan.zhihu.com/p/458683164

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
2月前
|
SQL canal 关系型数据库
(二十四)全解MySQL之主从篇:死磕主从复制中数据同步原理与优化
兜兜转转,经过《全解MySQL专栏》前面二十多篇的内容讲解后,基本对MySQL单机模式下的各方面进阶知识做了详细阐述,同时在前面的《分库分表概念篇》、《分库分表隐患篇》两章中也首次提到了数据库的一些高可用方案,但前两章大多属于方法论,并未涵盖真正的实操过程。接下来的内容,会以目前这章作为分割点,开启MySQL高可用方案的落地实践分享的新章程!
781 1
|
5月前
|
SQL 网络协议 关系型数据库
【怒怼大厂面试官】听说你精通MySQL?来说说MySQL主从复制
面试官:MySQL主从复制了解吧?嗯嗯了解的。主要是利用了MySQL的Binary Log二进制文件。那我把二进制文件丢给从库,从库复制整个文件吗。噢噢不是的。
【怒怼大厂面试官】听说你精通MySQL?来说说MySQL主从复制
|
5月前
|
NoSQL Redis
【怒怼大厂面试官】听说你精通Redis?Redis数据同步懂吗
面试官:不用慌尽管说,错了也没关系。。。来说说Redis数据同步。是这样的,Redis有一个叫命令传播的概念,如果像面试官说的这种场景,再使用上面我提到的AOF缓冲区就有点浪费内存空间了。所以Redis会将主服务器的这条Del删除命令
【怒怼大厂面试官】听说你精通Redis?Redis数据同步懂吗
|
5月前
|
存储 SQL 关系型数据库
【怒怼大厂面试官】你先说说知道哪些MySQL的高级特性
面试官:上一期博客问了你MySQL主从复制,现在考考你MySQL高级特性吧。分区的一个主要目的是将数据按照一个较粗的粒度分在不同的区域,这样的话就有很多好处。
|
SQL 存储 关系型数据库
一文讲解MySQL主从复制|手撕MySQL|对线面试官
前言 作为 《手撕MySQL》 系列的第三篇文章,今天讲解使用bin log实现主从复制的功能。主从复制也是MySQL集群实现高可用、数据库读写分离的基石。
160 0
一文讲解MySQL主从复制|手撕MySQL|对线面试官
|
SQL 存储 网络协议
【MySQL技术之旅】(4)这也许是你的知识盲区-[MySQL主从架构]之半同步机制
【MySQL技术之旅】(4)这也许是你的知识盲区-[MySQL主从架构]之半同步机制
120 0
【MySQL技术之旅】(4)这也许是你的知识盲区-[MySQL主从架构]之半同步机制
|
存储 SQL 缓存
为了让你彻底弄懂 MySQL 事务日志,我通宵赶出了这份图解!
在当今社会,充斥着大量的数据。从众多APP上的账户资料到银行信用体系等个人档案,都离不开对大量数据的组织、存储和管理。而这,便是数据库存在的目的和价值。本文将为大家详细讲解 MySQL 事务日志的相关知识。
4412 0
为了让你彻底弄懂 MySQL 事务日志,我通宵赶出了这份图解!
|
存储 程序员 Apache
DDIA 读书分享 第五章:Replication,多主模型
DDIA 读书分享会,会逐章进行分享,结合我在工业界分布式存储和数据库的一些经验,补充一些细节。每两周左右分享一次,欢迎加入,网站在这里: https://ddia.qtmuniao.com/ 。我们有个对应的分布式&数据库讨论群,每次分享前会在群里通知。如想加入,可以加我的微信号:qtmuniao,简单自我介绍下,并注明:分布式系统群。
141 0
DDIA 读书分享 第五章:Replication,多主模型
|
存储 监控 NoSQL
DDIA 读书分享 第五章:Replication,复制滞后问题
DDIA 读书分享 第五章:Replication,复制滞后问题
100 0
DDIA 读书分享 第五章:Replication,复制滞后问题
|
SQL 存储 数据库
《数据库系统概论》十一章汇总--基于《数据库系统概论》第五版王珊一书|第十章--数据库恢复(下)
该系列的博客都是基于《数据库系统概论》第五版王珊一书,进行的知识总结和课后习题汇总,从第一章到第十一章,如果觉得不错记得收藏点个赞吧~你的小小支持,是我的大大动力!
156 0