Design Data Intensive Applications 笔记 (复制)

复制目的:

  • 访问的数据地理位置更接近用户 减小访问延时 (多数据中心)
  • 节点宕机数据仍可用 (高可用)
  • 多副本可读,增长读吞吐量

主从复制:

同步复制 -> 强一致性, 弱可用性 一旦某个从节点宕机则写失败
解决方案:一个从节点同步复制,其余异步复制,一旦同步节点宕机提高一个异步节点为同步节点
新强一致性复制算法: chain replication 算法

异步复制 -> 强可用性,弱一致性 安全

增长从节点:
挑战:主节点数据仍在写入,直接复制会致使数据不一致
方案:
复制数据时锁主节点不容许写 ,太粗暴
更好的方案分3步: 服务器

  1. 对主节点数据作一个一致性快照,并记录对应的复制日志位置
  2. 复制快照到从节点
  3. 将快照后新增的复制日志应用到从节点上,直到追上主节点

从节点宕机恢复:
从宕机前复制日志位置开始追赶 网络

主节点宕机 -> FAILOVER 机制
比较复杂,通常须要3步:并发

  1. 宕机检测 : 通常经过心跳
  2. 从新选主,一致性算法和多数派协议
  3. 请求路由到新主节点,并通知客户端
    问题:
  4. 数据丢失,新选的主节点没有所有原主节点的数据
  5. 脑裂:两个节点同时认为本身是主节点
  6. 合理的心跳超时设置 (网络抖动可能误报)
    手动FAILOVER优于自动?

复制日志实现:less

  • 基于SQL语句: (MYSQL早期版本)
    问题: 特殊函数now(), random() 等带来的不肯定性. 自增主键冲突. trigger, 存储过程等的影响
  • 基于WAL日志: (POSTGRESQL, ORACLE)
    问题:和存储引擎强耦合, 滚动升级可能有问题
  • 逻辑复制: 自定义结构,基于修改的数据值.
    优势:不依赖于存储引擎, 兼容性好
  • 触发器(TRIGGER): (DATABUS FOR ORACLE, BUCARDO)
    优势:灵活性
    问题:OVERHEAD

复制延时的问题:
最终一致性: 以较弱的一致性换取读的可扩展性
一致性多弱取决于复制延时和读取策略dom

  • read-your-own-write-consistency (read-after-write consistency): 确保本身写入的数据必定会被随后的读请求读到
    实现方式:
    若是用户只修改自身的数据并知道自身数据的ID, 自身数据从主节点读,其余数据从随机节点读
    若是用户能够修改许多其余的数据,须要客户端记录 key -> last modify time. 在必定时间范围内只从主节点读, 不然从随机节点读. 或者客户端把 key+last modify time 发给查询节点, 查询节点比较时间戳,等待遇上再返回结果或者将读请求
    多数据中心: 路由读请求到同一数据中心异步

  • monotonic read: 确保第二次读结果不会比第一次旧
    实现方式:
    对同一主键每次读从同一副本(可能出现数据倾斜)分布式

  • consistent prefix read: 确保读写的因果顺序不会错乱 (好比问答顺序)
    实现方式:
    全局的一致性写顺序
    追踪因果关系causual dependency的算法

经过分布式事务解决复制延时问题.ide

多主复制:

支持多点写入
每一个LEADER对于另外一个LEADER是FOLLOWER
主要应用场景是多数据中心
每一个数据中心有一个LEADER, 数据从LEADER复制到本数据中心的FOLLOWER和另外一个数据中心的LEADER.

性能:写请求可由LOCAL 数据中心的LEADER处理. 不用跨广域网
数据中心容错: 一个数据中心宕机后另外一个数据中心仍可独立运行,数据中心恢复后再将数据复制过去
网络容错:跨广域网异步复制,暂时网络不稳定不会影响写入

缺点:必须解决写冲突
设计时必须考虑自增主键冲突. TRIGGER, 数据完整性约束等

写冲突检测和解决

  • 同步检测:等数据复制到每一个节点再检测冲突并通知用户. 延迟大,彻底牺牲了多主复制的优点
  • 冲突避免: 保证相同的键写入到同一个数据中心, 数据HASH,路由等. 数据中心若是宕机或者用户位置改变则会出现问题
  • 每一个写操做一个UUID,对同一数据的最后一次写获胜 (基于时间戳等), 可能丢失数据
  • 每一个REPLICA有一个UUID, 对同一数据写来自高位REPLICA获胜, 可能丢失数据
  • 保存写冲突数据,以后自动解决或者提示用户解决

解决冲突的时机:
写时:一旦冲突发生,只能后台解决没法让用户干预 (Bucardo)
读时: 保存冲突数据,在下次读的时候自动解决或提示用户(CouchDB)
事务中的每一个写冲突会单独解决,没法保持事务边界

自动冲突解决:
Conflict free replicated data types
Mergeable persistent data structures (GIT)
Operational transformation

多主复制拓扑:(超过两个MASTER)
星状
环状
全部对全部
须要防止循环复制(写请求记录通过的节点编号)
星状或者环状复制某个节点宕机会致使不可写
全部对全部 容易因延迟致使数据一致性问题或写失败

无主复制:

没有副本概念,客户端直接写多个节点
多数节点响应写请求则成功
读请求也发送到多个节点,版本号保证读取最新的数据
保证: 写成功节点数(W) + 读节点数(R) > 集群节点数(N)
读写并行发往全部节点, W, R 为响应的节点数量
若是 W + R < N 牺牲一致性换取低延时和高可用性
通常选择 W > N/2, R > N/2,允许最多N/2 节点宕机
W + R > N 依然有可能读到旧数据:

  • Sloppy Quorum
  • 并发写冲突
  • 读写冲突, 读的节点可能没写入最新数据
  • 写某些节点失败,写成功节点未回滚
  • 写成功节点宕机,数据从旧节点恢复

监控数据陈旧程度很困难, 没法像主从复制同样监控REPLICATION LAG. 写顺序不肯定

Sloppy quorum: 部分节点宕机致使没法写入一般写入的节点(读写迁移)
Hinted handoff: 节点恢复后读写迁移的节点将临时写入数据写回一般写入节点
Sloppy quorum 提升了可用性可是下降了读到最新数据的可能
无主复制也适合多数据中心的场景: 写请求发送多数据中心但只等待本数据中心确认

保证宕机节点最终一致性:
Read repair: 客户端发现读版本冲突自动更新版本比较旧的节点
Anti-entropy process: 后台进程自动比较数据版本并修复 (不一致时间长)

写冲突可能发生在如下状况:

  • 节点因网络故障丢失写请求
  • 不一样节点收到写顺序不一致

写冲突处理:

  • 最后写获胜:为写请求分配版本号,冲突只保留版本最大的数据. 可能丢失数据,适用于每一个KEY写一次再也不更新的场景 (时序数据?)
  • 并发写定义: 若是两次写操做有强时序关系或互相依赖 (如A为插入key1,B为key + 1),则A,B称为causually dependent. 任意写操做A, B, 必有A在B以前,A在B以后,或者AB为并发
  • 并发写检测算法:
    每一个KEY维护一个版本,每次写版本+1
    客户端写KEY以前必须先从SERVER读一次KEY的最新版本
    写操做带上写以前读到的KEY版本,能够覆盖同一个KEY更低或者相同版本,可是更高版本必须保留 (并发写)
    购物车举例:
    A, B 两个客户端同时向购物车添加产品,每次写提交,服务器端生成一个购物车新版本,并将最新版本的购物车内容及相应版本号返回给提交写请求的客户端
    客户端提交写请求时要将提交的内容和以前的购物车版本合并,并附带以前的版本
    服务端针对同一个客户端,把新的版本覆盖以前的版本,可是不一样客户端提交的最新版本认为是concurrent write,不会互相覆盖,须要冲突解决
  • 处理并发写:
    由客户端合并多个concurrent write的版本.
    删除操做不能直接物理删除,而要标记(tombstone)
    自动冲突解决, 见上文
  • 向量钟 (version vector): 适用于leaderless replication, 没有主节点而是多个对等的replica版本号针对于每一个replica的每一个KEY写以前先从replica读取最新的version vector, 合并写数据再发回保证从replica A读,再写到replica B是安全的
相关文章
相关标签/搜索