《DDIA》读书笔记:复制(1)

本文是第五章Replication中single leader部分的读书笔记。

这部分内容讨论的问题是

  • 以怎样的方式复制(同步 or 异步)
  • 怎么增加新的follower
  • follower或leader故障了怎么办
  • 复制日志该以什么样的格式存储
  • single-leader架构下,读follower有什么问题

single-leader: 只有一个节点(leader)接受写请求

复制的同步与异步

  • 同步复制:等待所有follower复制完成
  • 半同步复制:等待至少一个follower复制完成
  • 异步复制:不等待
  • 一般都是异步(或半同步),因为如果用同步的方式复制,只要有一个follower当机,整个系统就相当于当机了

新增follower

一般情况下,我们可以用如下的流程来概述新增follower的流程

  • 在leader节点上做consistent snapshot。这个流程最好不能锁住leader节点
  • 将快照发给新的follower, follwer加载快照
  • follower向leader请求自己加载快照这段时间内leader上的数据变化。为了让leader知道该发哪些数据给follower,快照本身应该带一个它在leader的复制日志中的位置,follower请求的时候带上这个位置
  • follower处理完这些数据变化后才算追上leader(caught up)。之后就是正常的和leader进行同步

处理节点故障

  • follower故障(Catch-up recovery)。恢复后带上自己已经同步的位置向leader请求这段时间的数据变化。处理完这些数据变化后,follower就追上leader了(caught up)
  • leader故障(Failover)。从follower中选举一个新的leader。failover的流程:1) 判断leader已故障 2)选举新leader 3)通知整个系统新的leader

Failover需要面对的问题:

  • 如果是异步同步,follower和leader之间数据不一致,那么如果这个follower被选为新的leader,就有数据丢失的问题。
  • 脑裂问题 split brain
  • 怎么判断leader已下线。如果这个timeout值太小,就可能频繁的发生leader切换,如果这个值太大,就会增加系统从故障中恢复的时间。

复制日志的格式/实现:

方案1: Statement-based replication。例如MySQL的statement格式的binlog,记录SQL原文。

  • 语句中包含不确定性的函数NOW(), RAND()。这些函数在不同的环境执行结果是不同的
  • 因为依赖于当前数据库中的数据,所以必须按序执行,否则就会出现结果不一样(例如语句依赖自增主键或者依赖现有数据库的数据 UPDATE … WHERE condition)。带来的问题就是无法并行复制。

方案2: WAL

  • WAL本身是很低层次的(low level)。A WAL contains details of which bytes were changed in which disk blocks.
  • 带来的问题就是不同的节点不能运行不同版本的数据库

方案3: 逻辑日志(row-based)。例如MySQL binlog的row格式

  • For an inserted row, the log contains the new values of all columns. 对于插入的行,记录该行的所有值
  • For a deleted row, the log contains enough information to uniquely identify the row that was deleted. Typically this would be the primary key, but if there is no primary key on the table, the old values of all columns need to be logged. 对于删除的行,记录唯一标识该行的信息(主键,没有主键就记录该行的所有值)
  • For an updated row, the log contains enough information to uniquely identify the updated row, and the new values of all columns (or at least the new values of all columns that changed). 对于更新的行,记录行的唯一标识,以及更新的数据

主从延迟对读follower的影响

因为不是同步复制,主从节点之间必然有延迟(lag),该情况下,系统能保证的只是最终一致性。

read-your-writes/read-after-writes:只要求写的人能读到,不保证其它人能读到

  • 对于可能被当前访问者修改的数据,从leader读(例如:用户查自己的个人信息时,从leader读,查其他人的个人信息时,从follower读)
  • 如果大部分数据都能被访问者修改,那么就只能从leader读了。这时候,我们就需要判断什么时候该从leader读,什么时候从follower读。例如我们可以记录数据的最后修改时间,然后修改后1分钟内都从leader读,之后都从follower读(这1分钟后,follower要能同步完leader之前写的数据)
  • 客户端记录最近修改发生的时间戳(或者唯一编号),查询的时候follower根据自己已经执行到的时间戳,需要判断自己是否已经执行了该修改,从而决定是否处理该读请求(因为记录在客户端,当多终端使用时就不能保证该一致性)

Monotonic Reads(单调读)

  • 如果不保证单调读,用户可能会看到事情逆着时间发生(读两次,一次从较新的follower上读,一次从较旧的follower上读)
  • 解决:用户只能从一个固定的follower上读

Consitent Prefix Reads(一致前缀读/因果读)

  • 保证有逻辑关系的事物被读的时候不会逆着逻辑关系出现。例如 A说吃了吗,B说吃了,不会读到B先说吃了,A再说吃了吗。

  • 违背该要求的场景: 数据被写在不同的partition,由于不同partition之间的复制速度不同,读的人先读到了后写的消息,然后才读了先写的消息
    image

  • 解决:对存在因果关系的写操作要写到同一个分区上


参考:
Designing Data-Intensive Applications https://book.douban.com/subject/26197294/

posted @ 2022-02-10 18:13  elimsc  阅读(59)  评论(0编辑  收藏  举报