数据系统与分布式(二) 分布式数据系统(复制与分片)

分布式数据系统

基础知识

  • 为什么要引入分布式

    • 拓展性

      • 负载过大,超出了单台机器的处理上限
    • 容错与高可用性

      • 单台机器出现故障, 其他机器可以正常工作
      • 组件失效, 冗余组件可以继续接管
    • 延迟考虑

      • 服务遍布全球各地, 希望就近服务
  • 系统拓展

    • 每个机器称为结点

    • 垂直拓展

      • 加强单台机器
    • 水平拓展

      • 加机器数量, 加小机器
  • 复制与分区

    • 当把数据分布在多节点的时候有两种常见的方式

    • 复制

      • 在多个节点上保存冗余的副本
    • 分区

      • 将一个大的数据库拆分成多个较小的子集

数据复制

  • 目的

    • 1.数据在地理位置上更接近用户,降低延迟
    • 2.高可用, 系统出现故障, 仍然可以继续工作
    • 3.拓展多台机器, 提高读吞吐量
  • 主节点与从节点

    • 基本原理

      • 1.选一个作为主节点, client的所有写请求都发给主节点
      • 2.其他节点都是从节点, 主节点把更改写入本地后, 把更改通过复制的日志或者更改流发送给所有从副本, 从副本应用这些更改
      • 3.client可以向主节点发read or write, 对从节点只能read
    • 同步复制和异步复制

      • 同步复制的优点

        • 一旦向用户确认, 从节点可以保证数据的最新版本
        • 主节点发生故障时, 从节点有最新的备份, 可以迅速恢复
      • 同步复制的缺点

        • 一旦有某个响应特别慢, 就要一直阻塞到同步副本确认完成
      • 异步复制的优点

        • 性能特别好, 在处理写请求时, 不管从节点数据多么滞后, 主节点总是可以继续响应写请求, 系统的吞吐性能更好
      • 异步复制的缺点

        • 主节点发生失败,还未同步的写请求会丢失
        • 无法保证数据的持久化
      • trade-off也就是半同步

        • 一个节点是同步模式, 其他节点是异步模式.
        • 万一同步的从节点性能下降或者不可用了, 把另外一个节点提升成同步模式
        • 这样做的好处是保证至少有两个节点拥有最新的数据副本
    • 配置新的从节点

      • 如何在不停机 , 数据服务不中断的前提下完成从节点的设置

        • 1.在某个时间点对主节点的数据副本产生一个一致性快照
        • 2.将此快照拷贝到新的从节点
        • 3.从节点连接到主节点, 并请求 快照之后的数据更改日志
        • 4.获得日志后, 应用这些变更. 也就是 "追赶"
    • 处理节点失效

      • 从节点失效

        • 追赶式恢复
      • 主节点失效

        • 切换的具体表现

          • 选择某个从节点提升为主节点, 客户端也要把write发送给新的主节点, 从节点也要接受来自新的主节点的变更数据
        • 手动切换

          • 管理员手动切换
        • 自动切换

          • 1.确认主节点失效

            • 基于超时的机制, 如果30s都没有回复, 则认为失效
          • 2.选举新的主节点

            • 选举的方式, 最好数据差异最小
          • 3.重新配置系统让 新主节点生效

            • 很多小问题

              • 脑裂
              • 原主节点仍然认为自己是主节点
    • 复制日志的实现

      • 基于语句的复制

        • 把诸如insert和update这类语句发送给从节点

        • 问题就是很多非确定性的语句

          • 比如now()获取时间

          • 多个并发执行的事务

            • 等等
          • MySQL会自动切换到基于行d

      • 基于预写日志(WAL)传输

        • PostgreSQL和Oracle支持
        • 缺点是此方案过于底层, 与存储引擎紧密耦合, 和软件版本有关
      • 基于行的逻辑日志复制

        • 复制和存储引擎采用不一样的日志格式, 解耦合
        • MYSQL的binlog
      • 基于触发器的复制

        • 高度可定制, 复杂度高
  • 复制滞后问题

    • 主从复制所有写都要经过主节点, 可以通过水平拓展来增强 系统的读能力

    • "最终一致性"问题

      • 一个应用从一个异步的从节点读取数据, 该副本落后于主节点
      • 如果同时对主节点和从节点发送相同的查询, 可能会得到不一样的效果
      • 但是这种不一致只是暂时的, 一段时间后, 从节点会赶上主节点
      • 这种效应称为最终一致性
    • "写后读一致性"问题

      • 读自己的写

        • 用户在写入不久后查看数据

          • 但是还没同步,看起来就像数据丢失了一样
          • 用户会不高兴不高兴
        • 我们需要"写后读一致性"

          • 方案一

            • 如果用户读可能被修改的内容, 从主节点读
            • 否则 ,在从节点读
            • 比如社交网站, 读自己的朋友圈, 从主节点读, 读别人的朋友圈, 在从节点读
          • 方案二

            • 跟踪最近更新的时间,

              • 如果在1min之内, 从master读
            • 同时监控从节点的复制滞后程度

              • 避免从滞后超过1min的节点读取
          • 方案三

            • 通过client端发送读请求时的时间戳, 确保提供读服务时 时间上的合理
          • Tips

            • 如果副本分布在多数据中心, 必须先把请求路由到主节点在的数据中心
    • "单调读"

      • 由于两次路由的从节点不同的原因, 用户第一次看到了数据, 第二次却看不到了

      • 我们需要"单调读"一致性

        • 方案一

          • 根据用户ID哈希到固定的一台机器
      • 是比"强一致性"弱, , 比"最终一致性"强的

      • 保证绝对不会看到回滚

    • "前缀一致读"

      • 对于一系列按照某个顺序发生的写请求 ,读这些内容时也会按照当时写入的顺序

      • 方案

        • Happened-before
    • 复制滞后的解决方案

      • 如果采用最终一致性系统, 用户的体验能接受的话,

        • 那就采用"最终一致性"吧
      • 如果用户体验不好

        • 提供更强的一致性

          • 比如写后读
  • 多主节点复制

    • 单主节点的明显缺点

      • 如果主节点网络中断了,就会影响所有的 写入操作
    • 适用场景

      • 在单数据中心引入 多节点 只是徒增复杂度

      • 适用于多数据中心, 离线客户端, 协作编辑 这三个场景

      • 多数据中心场景

        • 在每个数据中心内, 采用常规的主从复制方案

        • 在数据中心之间, 有每个的主节点来负责 数据交换和更新

        • 与单主节点的对比

          • 性能方面

            • 肯定是多主节点性能更好,
            • 因为就近访问
          • 容忍数据中心失效

            • 也是多主节点更好
            • 每个数据中心可以独立于其他数据中心继续运行
          • 容忍网络问题

            • 也是多主更好
        • 缺点

          • 冲突问题
      • 协作编辑

        • 石墨文档, 腾讯文档
        • 如果解决冲突
      • 离线客户端操作

        • 比如手机上的日历工作, 每部手机都是一个主节点
        • 这种时候采用 多主节点场景比较合适
    • 处理写冲突

      • 场景

        • A修改标题为20201005是美好的一天, B修改标题为 20201006是美好的一天
      • 同步与异步冲突检测

      • 避免冲突

        • 最理想的策略就是避免冲突
        • 不同用户对应到不同的主数据中心
        • 但是还是有可能冲突,
      • 收敛于一致性状态

        • 通过分配唯一的ID

          • 比如基于时间戳
          • 最后写入者获胜
        • 拼接这些值

          • 20201005是美好的一天20201006是美好的一天
        • 靠应用层的逻辑, 事后解决, 比如告诉用户

      • 自定义冲突解决逻辑

        • 最合适的方式还是得应用层来
        • 在写入时执行
        • 在读取时执行
      • 什么是冲突

        • 刚才的那种对同一个字段写

        • 订票时把唯一的票给了两个人

        • 自动冲突解决的可能方案

          • 1.无冲突的数据类型(CRDT)

            • conflict-free Replicated DataType
            • 可以由多个用户编辑的数据结构
          • 2.可合并的持久数据结构

            • 类似git
            • 二向合并 三向合并
          • 3.操作转换

        • 没现成的答案

    • 拓扑结构

      • 指的是多个主节点之间传播路径

      • 环形拓扑

      • 星形拓扑

        • 拓展到树形结构
      • all to all拓扑

        • 容错性更好
        • 问题: 可能由于网络 原因, 出现类似"前缀一致读"的问题
        • 使用版本向量的技术来
  • 无主节点复制

    • 思路清奇, 放弃主节点

      • 亚马孙Dynamo系统 不开放, DynamoDB不是Dynamo
    • 节点失效时写入数据库

      • 读修复与反熵

        • 读修复

          • 并行读多个副本时, 可以检测到过期的返回值
        • 反熵

          • 有后台进程不断查找副本之间的数据差异, 有明显的同步滞后
      • 读写quorum

        • quorum是法定人数的意思

        • 3个副本的例子里, 成功的写操作至少2个, 那么失败的至多一个, 我们读的时候至少读两个, 就可以保证至少有一个是最新的副本

        • W+R>n

        • 常见的设计

          • n为奇数
          • w = r = (n+1)/2
        • 问题

          • 即使满足W+R>n , 我们还是不能保证读取最新值
        • 所以我们最好把w和r看成灵活可调的读取新值的概率

    • Quorum一致性的局限性

      • 可以把w和r<=n

        • 这样虽然可能读的是旧值, 但是提高了性能
      • 监控旧值

        • 我们监控复制的运行状态

        • 如果差距过大, 可能就是网络问题或者节点超负荷

        • 原理大概是

          • 由于主节点和从节点上写入遵循相同的顺序, 每个节点维护了复制日志执行的当前偏移量. 通过比较偏移量的差值, 来衡量从节点落后于主节点的程度
      • 宽松的quorum与回传

        • 配置适当的quorum是有好处的

          • 1.可以容忍一些节点慢, 因为请求不用等所有节点的响应, 只需要w或者r个
        • sloppy quorum

          • 写入和读取仍然需要w和r个成功的响应, 但包含了不在先前指定的n个节点

          • 原先指定的n个节点暂时不可用

          • 把写请求写入了一些暂时可访问的节点中, 一旦网络问题解决, 这些临时节点会把这些写入全部发送到原始主节点上,

            • 这就是数据回传,(或者说暗示移交)
      • 多数据中心操作

        • 无主节点复制 aim at 更好容忍并发写入冲突, 网络中断 和 延迟尖峰
    • 检测并发写

      • Dynamo风格的数据库在并发写入时缺乏顺序保证

      • 最后写入者获胜( 丢弃并发写入)

        • 每个写请求附加一个时间戳

        • Last Write Wins(LWW)

        • 可以实现最终收敛的目标, 但是牺牲了数据的持久性

        • 如果覆盖, 丢失数据可以接受,就用

        • 确保LWW安全无副作用的唯一办法

          • 只写入一次然后写入值视为不可变
          • 比如UUID作为主键
      • Happens-before关系和并发

        • 并发性, 时间, 相对性
        • 确定前后关系
        • 合并同时写入的值
        • 版本矢量

数据分区

  • 面对海量数据集或者非常高的查询压力, 复制技术还不够, 需要引入分区

    • 分区的定义

      • 每条数据, 或者说每条记录,只属于某个特定分区
      • 每个分区可以看成一个完整的小型数据库
  • 数据分区与数据复制

    • 分区通常和复制结合使用, 每个分区在多个节点上保存有副本.

      • 意思是某条记录属于特定的分区, 而这个分区里有许多节点保存着这记录的拷贝
      • 每个分区有自己的主副本
  • 键-值数据的分区

    • 基于关键字区间分区

      • 分区边界要适配数据本身的分布特征
    • 基于关键字哈希值分区

      • MD5, Fowler-Noll-Vo
      • 缺点, 丧失了区间查询特性
    • 组合索引

      • user_id, update_timestamp

        • 可以有效检索某用户在一段时间内的所有更新
    • 负载倾斜与热点

      • 所有的读/写都是针对同一个关键字

        • 比如明星的微博
        • 方法,如果某key是热点, 在这个key的前或后加一个随机数,比如两位的十进制,, 这样可以负载到100台机上
  • 分区与二级索引

    • 二级索引带来的主要挑战是它们不能规整的映射到分区中

    • 基于文档分区的二级索引

      • 每个分区完全独立, 如果我们想查询"红色的汽车"
      • 需要把查询发送到所有的分区, 然后合并所有返回的结果
      • 别名:"分散/聚集"
    • 基于词条的二级索引分区

      • 我们要对所有的数据构建全局索引, 但是不能把所有的全局索引存在一个节点上, 所以全局索引页必须分区

      • 全局索引的缺点:

        • 写入速度较慢而且比较复杂
        • 所以往往是异步的 刚写入之后读可能 不太行
      • 优点:

        • 读取更高效
  • 分区再平衡

    • 随着时间的变化, 数据库可能会变化, 我们需要再平衡

    • 再平衡需要满足

      • 1.平衡之后, 负载, 数据存储, 读写请求等应该均匀分布
      • 2.再平衡过程中, 数据库应该可以正常提供读写服务
      • 3.避免不必要的负载迁移
    • 动态再平衡的策略

      • 为什么不用取模

        • hash (A) = 123456

          • 10台机器时

            • 123456%10 = 6
          • 11台机器时

            • 123456%10 = 3
          • 12台机器时

            • 123456%10 = 0
        • 因为如果节点数N发送了变化, 会导致许多关键字需要迁移

      • 固定数量的分区

        • 比较好

          • 创建远超实际节点数的分区数, 然后为每个节点分配多个分区
        • 比如10个机器, 一开始就创建了1000个分区

          • 每个机子负责100个分区
        • 只需要做好映射关系

      • 动态分区

        • 分区数量大到一定程度, 比如10GB, 就分裂
        • 分区小到一定程度, 就合并
        • 类似B树
        • 一个节点上可能有多个分区
        • 适用于关键字区间分区, 也适用于基于哈希的分区
      • 按节点比例分区

  • 请求路由

    • 我们已经把数据集分布到多个节点上, 但是客户端有一个 悬而未决的问题 应该连接到哪个IP地址 和哪个端口号

      • 也就是服务发现的问题
    • 主要有三种策略

      • 1.客户端随机选, 然后节点看看是不是自己, 不是就转发
        1. 引入一个路由层
      • 3.客户端就知道
    • ZooKeeper是一个独立的协调服务, 它跟踪集群范围内的元数据

      • 每个节点向ZooKeeper注册自己, 路由层或者客户端订阅ZooKeeper
    • 当然还可以通过gossip协议来同步集群状态变化, 虽然增加了节点的复杂性, 但是避免了对ZooKeeper的依赖

posted @ 2020-10-06 10:45  Yan_Hao  阅读(438)  评论(0编辑  收藏  举报