18f4d2af19792da723b35e8122da29ed
漫谈分布式系统(7) -- 扩展性?切就完了

这是《漫谈分布式系统》系列的第 7 篇,预计会写 30 篇左右。欢迎订阅,听我娓娓道来。也欢迎转发朋友圈分享给更多人。

再谈分治

前面几篇文章,我反复提到一个词 -- 「分治」。

分治,分而治之,是为了解决数据太大存不下和数据太大算的慢的问题。这也是基础分布式系统要解决的核心问题。

或者,我们换到程序和服务的角度去理解,是要解决(横向)扩展性(Scalability)问题。

  • 以 HDFS 为代表的分布式存储框架,通过把数据切分成固定大小的 block,再搭配记录 block 位置的元数据,就解决了数据存储扩展性的问题。只要有足够多的硬盘,就能一直存下去。

  • 以 MapReduce 为代表的分布式计算框架,通过把计算逻辑切分成一个个 Mapper 和 Reducer,就解决了数据计算扩展性的问题。只要有足够的 CPU 和内存,就能一直算下去。

所以,分布式系统最基础的难题 -- 扩展性,就被 partitioning 这个通用的方法解决掉了。

不同的系统里,partition 可能会有其他名字,比如 block、region、bucket、shard、stage、task 等等。换汤不换药,讲的都是同一个故事。

而我们在系列第 5 篇提到过,计算逻辑的切分等价于数据的切分。所以,我们聚焦在数据的扩展性上,来了解下常见的 partitioning 方案。

典型的,我们可以把数据分为三种类型来研究:

  • 文件数据,文件系统上任意格式的文件,如 HDFS 上的文本文件。
  • key-value 数据,带主键的数据,如 MySQL 里的数据。
  • 文档数据,类似 json 格式的数据,和 key-value 的区别是没有业务意义上的主键,如 ElasticSearch 上的数据。

具体来说,对于扩展性,我们关注这么几个点:

  • parititioning 方式,即数据在逻辑上怎么切分
  • localization 方式,即数据在物理上怎么分布
  • rebalance 方式,即在节点数变化后,数据在物理上怎么重新分布

文件数据

Partitioning

文件数据,是最底层也是最灵活的数据。其他类型的数据,底层最终也一定是以文件的形式存在(不去纠结不落地的纯内存数据)。

也正式因为这个特点,文件数据的 partition 也只能做得更底层和应用无关。

所以,无论是 Linux 支持的各种文件系统,还是分布式领域的 HDFS,都采用了类似固定大小的 block 的做法来切分数据。

对分布式系统而言,还要再搭配一个元数据,记录文件和 block 的对应关系,以及 block 和机器的对应关系。

Localization & Rebalance

由于 block 是没有应用层含义的,所以文件数据的分布(Localization)接近随机,只是更多考虑从存储空间大小的角度去做。

这样是很好理解的,好处有几个:

  • 需要保证足够的剩余空间,否则可能导致服务和机器故障。
  • 需要保证数据分布的均衡,因为计算跟着数据跑,数据分布的均衡,能更高效的利用计算资源和 IO 资源。

而增删节点之后的 Rebalance,实现起来也非常简单。

本质上,只需要复制数据到目标机器,然后修改元数据上的映射关系,就好了。

元数据的修改很轻量级。但数据的挪动会带来大量的 IO,所以可以考虑错开业务高峰时间,或者限制挪动速度来降低对业务程序的资源竞争。

像 HDFS 还提供了触发阈值的设置,以及自动 rebalance 的功能,就更大程度的方便了扩缩容。

Key-Value 数据

Partitioning

key-value 数据是我们经常处理的数据类型。和文件类型数据的最大区别是,key-value 的结构赋予了应用层的含义,使得我们可以摆脱底层的束缚,在应用层做很多事情。

具体到 partitioning,我们就不用在 block 级别去做了。key-value 以 key 为核心,所以我们以 key 为单位做 partitioning。

第一个很容易想到的办法,是按范围(key range)来切分

比如一个以手机号为 key 的数据,就很容易这么来切分:

  • ........
  • 13100000000 - 13199999999
  • 13200000000 - 13299999999
  • 13300000000 - 13399999999
  • ........

HBase 就是采用的这种方法做 partitioning。

但是这样很容易导致数据分布不均衡。比如 135 这类号段会有非常多的数据,而 101 这类号段可能根本没有数据。

根本原因,还是 partitioning 的 key 带上了业务含义,而业务本身可能就是不均衡的。

好办,那就让 partiton key 变得业务无关。

有些场景下,通过一些简单的变换就能达到效果。

比如上面手机号的例子,把手机号翻转再作为 partitioning key 就能很好的解决数据倾斜(不均衡)了。

更普遍的场景下,想要打散数据,以下两个办法更通用:

  • 加随机数,这个效果肯定是最彻底的,但这样就不能精确定位 key 来访问数据了,只能范围扫描。
  • hash 处理,hash 算法的摘要提供了一定程度的打散,而输出的确定性又保证了事后访问能精确定位。

所以,一般选择**对 key 做 hash **的方式,然后,仍旧可以继续按 range 来做切分。

广义来看,手机号翻转其实也可以看做一个 hash 函数,更加常用的标准 hash 算法有 md5、sha 等。

hash 确实能解决分布不均的问题,但也丢掉了 range 的一大好处:按范围查询。

做完 hash 之后,范围查询不得不扩散到多个 partition 上,查询性能会收到比较大的影响,并且丢掉了数据之间的顺序。

于是可以有一种折中的办法,所谓的组合主键(compound primary key)。类似 key1_key2,只有 key1 用来作为 hash partition,而 key2 就用来满足范围查询的需求。

top Created with Sketch.