2021-07-01

分布式系统核心三要素

Views: 6314 | Add Comments

曾经有一次, 某个技术人员向我介绍了他们自研的"分布式存储系统", 他提到, 他们使用了开源的 Raft 库做数据同步, 使用了开源的 B+Tree 存储引擎存储数据到硬盘上. 由于使用的都是非常成熟的开源组件, 技术选型非常正确, 系统结构也简单合理, 这样的系统, 不可否认, 能存储几 TB 的数据, 被广泛应用起来完全没有问题.

但是, 对于他自称这是一个"分布式数据库"或者"分布式存储系统", 我无法认同, 我无法认同以如此功能如此结构的系统, 竟能挂以"分布式"之名. 但是, 我又没有理论支持说"你们这个系统不是分布式系统!".

虽然诡辩术广泛存在, 同时每个人的视角不同, 但是, 根据我的实际经验, 和我对分布式系统的理解, 我想总结出分布式系统的核心三要素, 只具备部分要素的系统, 要谨慎挂以"分布式"之名.

分布式系统核心三要素:

  • 要素一: 多副本(Replication), 系统包含多个完全相同(一致)的节点
  • 要素二: 多分区(Sharding), 系统被拆分成多个完全独立的节点组
  • 要素三: 协作(Cooperation), 节点组之间有协作, 共同完成某项工作

多副本

多副本很容易理解, 正如前面提到的那个技术人员的看法, 大部分挂以"分布式"之名的系统, 仅仅是因为其具备了多副本能力. 多副本技术非常重要, 因为它至少能解决分布式系统的两个核心目的: 高可用和可扩展.

事实上, 多副本并非天然地可扩展和高可用.

首先, 多副本遇到一致性的问题, 只有强一致性的多副本, 才可容灾达到高可用. 如果多副本不是强一致性, 那么必须增加一层 Indirection[1] 实现强一致性之后, 才能容灾. 实践中经常遇到的容灾操作, 例如 MySQL 主从切换, 都是增加了运维人员(Indirection)进行确认, 达到强一致性(数据真的一致, 或者即使数据不一致但人认为可接受)之后, 才进行切换.

其次, 只有使用者不强求强一致性, 多副本才是天然可扩展的. 例如, MySQL 主从同步, 使用者很多时候不要求强一致性, 所以读 Slave 副本. 同时, 工程上多副本对于写操作完全没有可扩展性, 因为所有的写操作的压力是同等作用于所有副本的. 但是, 未经工程优化的强一致性读操作, 多副本也没有可扩展性, 因为每一次读操作必须由全体副本共同完成, 压力也是同等作用于所有副本的.

可能有人对强一致性读操作的压力有疑问, 这里再多解释一下什么是工程优化. 被称为"唯一的共识协议"的 Basic Paxos 不区分工程实践上的读写操作, 每一次读操作都需要全部节点真正地做同样计算. 工程实践上, 在概率上能优化成先读取数据的摘要(例如版本号, MD5 值)做判断, 判断后再决定是否需要读取全部的数据, 从而降低压力. Raft 对读操作也做了工程优化, 例如 Leader 在确定自己的身份有效之后, 可以独立地处理读请求, 但 Raft 多副本只能有一个 Leader 可以处理请求(Raft Multi-Group 就是后面要提到的 Sharding), 无法得到扩展.

多副本天然要求强一致性, 所以, 我们提到多副本, 一般默认是指强一致性多副本, 所以, 是高可用的. 但是, 未经工程优化的强一致性多副本, 默认是无扩展性的. 1 台机器能存储 3TB 的数据, 3 台机器组成的多副本也只能存储 3TB 的数据, 而不是 9TB. 1 台机器能处理 10w qps 写请求, 3 台机器组成的多副本也只能处理 10w qps 写请求, 甚至更少.

多分区

多副本不是天然可扩展的, 但是多分区是天然可扩展的. 前面提到, 3 台机器组成的多副本, 只能存储 3TB 的数据, 处理 10w qps 请求. 如果数据对半分成 2 个分区, 再增加 3 台机器, 这样的话, 整个系统的存储容量和处理能力是不是扩容到了 2 倍?

不像多副本必须经过仔细的工程优化, 只要对数据进行了分区(Sharding), 系统就是天然地扩展了. 例如前面提到的 Raft 多副本只能有一个 Leader, 如果我们将数据分成 3 等分, 那么每一个分区都可以拥有一个 Leader, 这样, 在不增加机器的前提下, 3 台机器都是 Leader, 对于读请求的处理能力, 理论上扩容为 3 倍, 从 10w qps 增加为 30w qps.

多分区之间是 shared-nothing 的, 每一个分区并不知道其它分区的存在, 也不依赖其它分区, 可以独立地进行工作. 如果分区之外的 Indirection 依赖两个分区才能完成某项工作, 那就涉及到了下面介绍的"协作".

协作

理想状态下, 系统被拆分成若干个完全独立的分区, 但是, 现实中分区之间必须进行协作, 共同完成某项工作. 例如某个数据库表被拆分分别存储在两个分区, 但需要对这个表格进行排序之后取某个区间时(order by xxx limit offset, size), 这两个分区在逻辑上就被强绑定在一起, 缺一不可.

比如某些分布式数据库, 一个事务跨越多个分区, 每一个分区上面的事务参与者, 都要指向一个单点标记(commit point), 只要其中一个分区宕机, 事务就无法完成.

协作是一种增加 Indirection 之后的结果, 为了解决问题, 我们不得不增加一层 Indirection[1]. 所以, 分布式系统往往要求分区之间进行协作, 完全独立的分区结构, 几乎是不可能存在的. 否则, 中国的某个技术人员部署了自己的 MySQL 数据库运行网站, 另外, 美国的某个程序员也独立地部署了自己的 MySQL 数据库和博客网站, 这两个人部署的独立的 MySQL 数据库, 你会认为它们两者组成了一个分布式系统吗? 你会认为两者是一个集群吗?

总结

只有同时满足: 多副本, 多分区, 协作三个条件的系统, 才是真正意义上的分布式系统(集群).

相关资料

Related posts:

  1. Raft 为什么不能直接 commit 前任的日志?
  2. Raft 选主优化之 PreVote
  3. 分布式数据库如何做到异地多活?
  4. Raft日志复制状态机模型的Apply进度问题
  5. Raft 协议和区块链
Posted by ideawu at 2021-07-01 21:45:53

Leave a Comment