Skip to content

Instantly share code, notes, and snippets.

@ideawu
Last active May 21, 2021 04:12
Show Gist options
  • Save ideawu/b3810a2fe34cdba9128a775a31d122e5 to your computer and use it in GitHub Desktop.
Save ideawu/b3810a2fe34cdba9128a775a31d122e5 to your computer and use it in GitHub Desktop.
数据库最常用的特性

根据我的从业经验, 用户对一个(分布式)数据库系统, 最看重这些特性:

  1. 多副本, 数据安全性
    • 能提供强一致性更好, 但基于性能考虑, 最终一致性(异步复制)有广泛的用户需求
  2. 节点容灾, 部分服务器故障, 不影响整体系统的运行
    • 方案选择, 倾向于增加无状态 proxy 层
  3. "伪装"成一个单机数据库实例, 底层细节对用户隐藏
    • 平滑扩容, 加机器不影响业务, 不需要业务在代码和架构上做改变
  4. 数据全球同步, 最终一致
    • 一个大区的日志序列是顺序 apply, 但多个大区的日志序列是乱序 apply
  5. TTL, 能设置数据的过期时间, 定期清理数据释放空间, 主要是运维需求, 节省用户的运维工作量
    • 不提供一致性, 及时性, 只有运维性
    • TTL是一种外部运维特性, 所以在存储系统之外向存储系统发送删除指令, 是运维操作, 不是内在特性
  6. 分页操作, 例如 MySQL 的 limit, Redis 的 zrange, 虽然技术上性能局限性比较突出, 但好用, 用户喜欢用
  7. 批量操作(性能), 例如 mget, mset, mdel, 主要是方便用户使用, 性能也有优势
    • Pipeline 也算批量操作
    • 批量操作如果不承诺原子性, 可以在客户端实现
  8. 可选持久化策略
    • auto: 只需要 write(), 不要求 fsync()
    • strong: 共识必须在磁盘 fsync() 之后
  9. 可选写一致性级别
    • 默认是单大区内(raft组)强一致性
    • 默认大区间是最终一致性(跨地域同步)
    • 提供 sync_write() 让用户等待同步进度, 在写数据之后调用
  10. 可选读一致性级别
    • 默认是单大区内(raft组)强一致性
    • 默认大区间是最终一致性(跨地域同步)
    • 用户可选 stale read: 读单机
    • 提供 sync_read() 记用户等待同步进度, 在读之前调用
  11. 提供事务能力(锁+快照), 多个写操作之间有关联和依赖
    • 悲观事务(读写, Transaction), 交互式 2PC, 有 commit point
      • begin() -> commit()
    • 原子写入/乐观事务(只写, AtomicWrite), 非交互式 2PC, 写操作之间无因果依赖关系
      • atomic() -> commit()
      • 若有参与者 prepare 失败(包括 CAS 冲突)则全部回滚
      • 相比悲观事务, 可以并发 prepare
    • 批量写入(只写, MultiWrite), 1PC, 忽略 CAS 冲突, 最终全部会被执行, LWW
      • multi() -> exec()
      • 真正执行时, 如果对象当操作的时间戳更新, 则直接丢弃操作(认为操作已经完成, 然后被覆盖)
      • hdel 操作的 CAS 比对的是元素的 mtime
      • hclear 操作的 CAS 比对的是容器的 ctime, 异步地遍历每一个元素检查 mtime 决定是否删除
    • 提供 lock 指令
    • 以事务启动时的时间戳作为所有参与者的 mtime
    • 读操作忽略未决资源(最终原子性, read committed, 不是 monotonic atomic view)
  12. 提供 binlog 和 redo log 订阅接口
  13. CAS 操作, CAS+原子性, 可以取代锁, 提供事务一致性保证
  14. MVCC 底层数据保留多个版本, 但带来的问题非常多, 例如容器计数也要保留多个版本

相关文章:

@ideawu
Copy link
Author

ideawu commented Apr 20, 2021

其实, apply 的过程可以理解为 redo log 的创建过程. redo log 中记录 apply 进度, 不把 redo log 看作是 db 的一部分, 就像我们不把 binlog 看作是 db 的一部分.

redo log 持久化之后, 再通知 db 持久化(仅通知 db 可以持久化, 由 db 自己决定是否持久化).

所以, db 对外暴露 AddCheckPoint() 接口

binlog 和 redolog 是一体两面.

@ideawu
Copy link
Author

ideawu commented Apr 21, 2021

节点结构图

a

外部请求产生 binlog, binlog 经过共识后, apply 到 storage, apply 的同时, 产生 redolog

progress 记录 last_index, last_seq, progress 是独立的模块

storage 记录 last_seq

Recover 流程

节点重启时, 执行 recover 流程:

  1. 读取 binlog.last_index, progress.last_index + progress.last_seq, storage.last_seq
  2. 取 progress.last_seq 和 storage.last_seq 的最小者作为 last_seq, 从 progress 中查找对应的 last_index
  3. 根据 last_seq 回滚 progress 或者 storage
  4. 从 last_index 位置, 继续 apply binlog

关于 Raft Apply Exactly Once 的讨论

为了保证 binlog apply 是 exactly once 的, 有两种解决方法:

  1. 提供操作原子性保证, 在 apply 的同时, 保存 apply 进度
  2. 单独保存 apply 进度, 重启时回滚

如果 apply 操作和保证进度两个操作有先后顺序, 那么先执行者需要提供回滚接口, 如果是并发的(不区分先后), 那么两者都要提供回滚接口.

代码上的先后, 不代表持久化的先后, 因为模块内部可能有缓冲, 会异步进行持久化.

@ideawu
Copy link
Author

ideawu commented Apr 28, 2021

Overview
    Cluster
    Machine
    Zone
    Node
    Proxy
Cluster
    Shard
    Machine
Machine
    CPU
    Memory
    Disk
    Cluster
    Node
    Proxy
Zone
    Machine
    Cluster

@ideawu
Copy link
Author

ideawu commented May 17, 2021

全球分布式数据库通过同步原语可以在各个区域提供强一致性读, 但是, 强一致性写操作(也即锁)则必须回源.

@ideawu
Copy link
Author

ideawu commented May 20, 2021

快照, 回滚.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment