根据我的从业经验, 用户对一个(分布式)数据库系统, 最看重这些特性:
- 多副本, 数据安全性
- 能提供强一致性更好, 但基于性能考虑, 最终一致性(异步复制)有广泛的用户需求
- 节点容灾, 部分服务器故障, 不影响整体系统的运行
- 方案选择, 倾向于增加无状态 proxy 层
- "伪装"成一个单机数据库实例, 底层细节对用户隐藏
- 平滑扩容, 加机器不影响业务, 不需要业务在代码和架构上做改变
- 数据全球同步, 最终一致
- 一个大区的日志序列是顺序 apply, 但多个大区的日志序列是乱序 apply
- TTL, 能设置数据的过期时间, 定期清理数据释放空间, 主要是运维需求, 节省用户的运维工作量
- 不提供一致性, 及时性, 只有运维性
- TTL是一种外部运维特性, 所以在存储系统之外向存储系统发送删除指令, 是运维操作, 不是内在特性
- 分页操作, 例如 MySQL 的 limit, Redis 的 zrange, 虽然技术上性能局限性比较突出, 但好用, 用户喜欢用
- 批量操作(性能), 例如 mget, mset, mdel, 主要是方便用户使用, 性能也有优势
- Pipeline 也算批量操作
- 批量操作如果不承诺原子性, 可以在客户端实现
- 可选持久化策略
- auto: 只需要 write(), 不要求 fsync()
- strong: 共识必须在磁盘 fsync() 之后
- 可选写一致性级别
- 默认是单大区内(raft组)强一致性
- 默认大区间是最终一致性(跨地域同步)
- 提供 sync_write() 让用户等待同步进度, 在写数据之后调用
- 可选读一致性级别
- 默认是单大区内(raft组)强一致性
- 默认大区间是最终一致性(跨地域同步)
- 用户可选 stale read: 读单机
- 提供 sync_read() 记用户等待同步进度, 在读之前调用
- 提供事务能力(锁+快照), 多个写操作之间有关联和依赖
- 悲观事务(读写, 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)
- 悲观事务(读写, Transaction), 交互式 2PC, 有 commit point
- 提供 binlog 和 redo log 订阅接口
- CAS 操作, CAS+原子性, 可以取代锁, 提供事务一致性保证
- MVCC 底层数据保留多个版本, 但带来的问题非常多, 例如容器计数也要保留多个版本
相关文章:
强一致性是手段, 目的是为了高可用
有人认为, MySQL 异步复制的目的是为了高可用, 这是错误的. 异步复制的目的是为了高性能: 减少响应时间, 负载均衡. 异步复制根本无法达到高可用的目的. 当 Master 节点宕机之后, 整个系统立即不可用, 所谓的自动主从切换是严重错误的, 众所周知会"丢数据", 负责任的系统不可能去自动切换主从. 支付宝所谓的异地多活, 出现故障时照样必须停止分区的服务, 只有人工确认之后, 公司层层签字之后才能切流量. 一个分区完全宕机, 谈何高可用?
可用性的定义是有争论的. 通常, 所谓的"高可用"是指整体高可用, 某些分区可能完全不可用, 但任意时刻大部分分区是可用的. 给定某个对象, 除非使用同步复制, 否则永远无法做到高可用
还有人认为同步复制不能高可用, 因为 Slave 宕机之后系统不可用. 这也是错误的. Raft 就是同步复制, 也是高可用的.
强一致性的系统, 高性能是附带的, 因为实现强一致性的手段, 或者说一致性所要解决的问题, 就是数据复制. 正因为有数据复制, 我们才需要强一致性.
基于同步复制而实现的一致性, 是实现高可用的最有效手段. 同时, 也能达到"高性能". 因为同步复制, 数据能扩散到多个节点. 因为有一致性策略协调节点如何处理请求, 所以能放心地切换主从. 异步复制当然也能实现强一致性, 例如全部回源的 Master 节点, 例如 Read Index.
同步复制必然受到光速(也即地理距离)的限制, 所以, 距离较远的两地, 不可能采用同步复制的强一致性策略, 只可能是异步复制的最终一致性.
但一个应用, 只有少部分功能需要涉及到资源抢占(也即锁), 这时才需要强一致性. 大部分情况下, 我们只需要最终一致性.
总结: