从大致的非功能需求角度来说,作为一般的分布式持久化存储系统,这样三个需求从重要性依次排列:
Durability > Availability > Performance
即最重要的是,数据绝对不能丢失,其次是要一直提供服务,最后才是要保持一定的性能。当然,有了上述基础以后,我们还可以谈论任何分布式存储系统都涉及的重要特性,比如一致性。最后,作为特定的存储系统——“数据库”,我们还常常谈论一些特定的特性,比如权限管理和事务控制等等。
下面拿的是 Bigtable 来举例的,它建立在 GFS 这样的分布式文件系统上面,有一定代表性。
- 图中展示的是一个简单的写数据的流程,虚线是控制流,实线是数据流。
- 如果需要加锁,客户端先去 Lock Service 获取写锁。
- 客户端先去 Tablet Master 询问,具体数据的读写应该访问哪一个 Tablet Server,原始 key 需要转换为怎样的一个 row key,如果获取到了,就和那个 Tablet Server 通信读写数据。如果使用锁,这一步和上一步可以合二为一,即在获取到 row key 的同时也一并加锁,这样可以减少一步访问。
- 数据的持久化和冗余,是通过 GFS 的 Chunk Server 来实现的,也就是说,Tablet Server 持有 GFS 的客户端来实现对文件系统的读写。客户端不合 GFS 直接打交道,而 GFS 并不关心 Bigtable 层面的概念,只关心文件 block 的读写。图中我省掉了 GFS 的 Master 结点。
- 在数据写入完成后,客户端告诉 Lock Service 释放锁。
- Bigtable 的数据分成这样几种存放形式:
- 一种是稳定的、有序的数据,sharding 后存放在 SSTable(Sorted String Table),每一个 SSTable 有自己的 index 和 bloom filter,前者用来加快数据定位,后者用来快速存在性判断(尤其是它判断得到不存在的结果的话,它的正确率是 100%)。Tablet Server 的内存中也会有部分 index 和全部 bloom filter 的拷贝,以提高数据定位的效率。
- 第二种是由于数据增加、修改等原因,未和上述一起排序的 “额外” 的数据,这些数据一开始是存放在 Tablet Server 的内存中的,使用一个 Skip List 来维护,它们定期归并到上述的 SSTable 中去。为了防止这些数据丢失,需要使用 WAL(Write Ahead Log)持久化到 GFS 中,这个过程是很快的。这样这些后来的新增和修改操作既保证了效率,又不丢失持久性。
- 由于 GFS 的特性,对数据追加的操作可以高效完成,但是数据修改却往往不是(比如某个值的修改导致其占用的空间增大,原地修改将导致整个文件的数据从该值的位置开始向后平移)。这个限制非常关键,Bigtable 的数据修改一般都是通过内存中的追加写操作来完成(使用一个 Skip List 来维护),内存中的追加操作达到一定大小阈值以后,它们将被磁盘序列化为 SSTable,而它的内部是有序的。于是,一条数据记录可能存在多个版本,在内存(无序)中和多于一个的 SSTable(有序)中都存在,但只有最新的一条才作数。
- 因此对于某条数据的读取,是有可能从多个地方读取的,即 Tablet Server 的内存和多个 SSTable。在查询获取某一 key 的数据的时候,需要先访问内存中的 Skip List;然后对于硬盘中有序的 SSTable,过一遍 Bloom Filter(它可以长期待在内存里以减少磁盘读取),再加载它部分的 index 到内存中,从而定位到数据位置。
- 每隔一定时间,所有这些 SSTable 可以做一次归并整理(有 k 个 SSTable 那就是 k 路归并),顺便剔除过期数据。
这是《常见分布式系统设计图解》系列文章中的一篇,如果你感兴趣,请参阅汇总(目录)寻找你其它感兴趣的内容。
文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《四火的唠叨》