根据我的从业经验, 用户对一个(分布式)数据库系统, 最看重这些特性:
- 多副本, 数据安全性
- 能提供强一致性更好, 但基于性能考虑, 最终一致性(异步复制)有广泛的用户需求
- 节点容灾, 部分服务器故障, 不影响整体系统的运行
- 方案选择, 倾向于增加无状态 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 底层数据保留多个版本, 但带来的问题非常多, 例如容器计数也要保留多个版本
相关文章:
node-server(ssdb-node)
代码模块
说明
raft 的配置通过 raft 广播, 例如节点的 ip:port. 节点自身的配置, 如内存配置, 不走 raft.
redolog 模块在有订阅者的时候生成 redo log, 没有订阅者则不生成, redo log 不做持久化.(也许binlog和redolog可以合并)
node-server 不需要知道 meta-server, proxy-server 的存在, 它是被动接受控制的.
meta-server(ssdb-meta)
meta server 定期主动地去查询所有 node server, 更新路由总表. 这样可以简化 node server 的设计.
proxy-server(ssdb-proxy)
代码模块