why
随着互联网的进一步普及,互联网应用的用户进一步增长,互联网应用需要存储的数据直接由用户生成,而不再是由应用提供者提供。随着用户量的增长,用户生成的数据量也随之增长,对系统能支撑的数据量提出了更高的要求,进而对可扩展性要求也越来越高。
通过提升硬件进行众向扩展的方式在互联网应用中已经越来越不能满足需求,所以现在的系统更喜欢采用无状态的设计方式来达到横向的扩展,但是存储系统的压力越来越大,特别是数据库,它们无法做到无状态扩展,这样数据库就变成了分布式系统中的单点问题了。即使采用非常昂贵的小型机甚至是大型机,单台关系数据库系统都无法承受。
一种常见的做法是根据业务特点对数据库进行水平拆分,通常的做法是根据某个业务字段进行分片,根据分片的结果将数据分布到不同的数据库服务器上,客户端请求通过数据库中间层路由到不同的分区。这种方式目前还存在一定的弊端:
数据和负载增加后添加机器的操作比较复杂,往往需要人工介入;
有些范围查询需要访问几乎所有的分区,例如,按照user_id分区,查询收藏了一个商品的所有用户需要访问所有的分区;
一个业务需要跨库进行操作时会造成分布式事务问题
跨库的分页,分组,复杂sql支持不够友好
所以一些大厂逐渐开发出了一些分布式存储系统来解决这些问题,最先提出这个参考方案的是google的Spanner,阿里依据Spanner的论文衍生出了OceanBase,开源的有PingCAP的TiDB,Kudu等分布式存储系统。
What
分布式存储系统应该具有哪些特性:
水平线性弹性扩展:包括计算能力和存储能力的线性扩展
分布式事务:支持跨行跨表事务
强一致性保证:强一致性将大大简化数据库的管理,应用程序也会因此而简化
高可用性:尽量避免任何的单点问题
SQL支持:提高易用性,方便系统迁移
易用性:包括数据分布、负载均衡、主备同步、容错、自动增加/减少服务器等特征
How
我们先来看看几个分布式数据库的总体架构图:
1.Google Spanner:
2.Oceanbase:
3.TiDB:
这里一个关键词Tablet概念上相当于数据库一张表里的一些行,物理上是数据文件。而我认为TiDB的Region和Tablet是同样的概率。
我们先来说说存储,分布式系统的存储必须是可扩展、高可用的分布式存储系统。
Google Spanner使用分布式存储系统Colossus(GFS的升级版)作为存储引擎,一个 GFS 集群包含一个单独的 Master 节点3、多台 Chunk 服务器。
Oceanbase的分布式存储引擎层包含三个模块:RootServer、UpdateServer以及ChunkServer。其中,RootServer用于整体控制,实现tablet分布、副本复制、负载均衡、机器管理以及Schema管理。ChunkServer的功能包括:存储多个tablet、提供读取服务、执行定期合并以及数据分发。UpdateServer是集群中唯一能够接受写入的模块,UpdateServer中的更新操作首先写入到内存表,当内存表的数据量超过一定值时,可以生成快照文件并转储到SSD中。
TiDB的存储引擎由PD Server和TiKV Server配合完成,PD Server是管理模块,其主要工作有三个: 一是存储集群的元信息(某个 Key 存储在哪个 TiKV 节点);二是对 TiKV 集群进行调度和负载均衡(如数据的迁移、Raft group leader 的迁移等);三是分配全局唯一且递增的事务ID,TiKV Server 负责存储数据。
分布式的存储系统基本都是采用Key-Value的存储模型,为了查询和插入性能,存储格式一般采用B+树,存储方式是顺序增加的形式。
作为分布式存储系统,一定需要一致性协议来保证系统的高可用性和一致性,一般都是采用Paxos,Raft和一些Paxos的变种方式来实现,而Raft由于其简单化而得到广泛的使用。这就需要采用Paxos/Raft来实现副本的复制,每个Tablet上会有一个Paxos/Raft状态机。Table的元数据和log都存储在上面。Paxos/Raft会选出一个 replica做leader,Leader就相当于复制数据的master,其他replica的 数据都是从他那里复制的。读请求可以走任意的replica,但是写请求只有去leader。这些replica统称为一个paxos group。
接下来说说事务管理,数据库最基础,也是最核心的部分是事务处理。分布式数据的实现机制和传统数据库一致,都是采用多版本并发控制(MVCC)。大部分事务的实现是会将所有的写操作先缓存起来,在Commit的时候一次提交。分布式事务模型本质上是一个两阶段提交的算法,其实本质上来说,在一个分布式系统中实现跨节点事务,只有两阶段提交一种办法。在 Spanner 中同样也是一个 2PC,但是 Google 比较创新的引入了 TrueTime API 来作为事务 ID 生成器从而实现了 Serializable 的隔离级别。由于 TrueTime 引入了专有的硬件(GPS 时钟和原子钟)来实现跨洲际机房的时钟同步方案,大多数业务场景其实并没有这种跨洲际机房数据同步的需求,所以TIDB采用了 Google 的另一套分布式事务方案 Percolator 的模型。原理比较简单,总体来说就是一个经过优化的 2PC 的实现,依赖一个单点的授时服务 TSO 来实现单调递增的事务编号生成,提供 SI 的隔离级别。传统的分布式事务模型中,一般都会有一个中央节点作为事务管理器,Percolator 的模型通过对于锁的优化,去掉了单点的事务管理器的概念,将整个事务模型中的单点局限于授时服务器上,在生产环境中,单点授时是可以接受的,因为 TSO 的逻辑极其简单,只需要保证对于每一个请求返回单调递增的 id 即可,通过一些简单的优化手段(比如 pipeline)性能可以达到每秒生成百万 id 以上,同时 TSO 本身的高可用方案也非常好做,所以整个 Percolator 模型的分布式程度很高。
OceanBase则有些不同,其UpdateServer是集群中唯一能够接受写入的模块,每个集群中只有一个主UpdateServer。UpdateServer中的更新操作首先写入到内存表,当内存表的数据量超过一定值时,可以生成快照文件并转储到SSD中。由于整个集群所有的读写操作都必须经过UpdateServer,UpdateServer的性能至关重要。OceanBase集群通过定期合并和数据分发这两种机制将UpdateServer一段时间之前的增量更新源源不断地分散到ChunkServer,而UpdateServer只需要服务最新一小段时间新增的数据,这些数据往往可以全部存放在内存中。另外,系统实现时也需要对UpdateServer的内存操作、网络框架、磁盘操作做大量的优化。多个分区分布在多台服务器上的操作OceanBase内部通过两阶段提交实现分布式事务。当然,两阶段提交协议性能较差,OceanBase内部做了很多优化。首先,我们提出了一个表格组的概念,也就是说,我们会把多个经常一起访问,或者说访问模式比较类似的表格放到一个表格组里面。与此同时,OceanBase后台会将同一个表格组尽可能调度到一台服务器上,避免分布式事务。接下来的优化手段涉及到两阶段提交协议的内部实现。两阶段提交协议涉及多台服务器,协议中包含协调者、参与者这两种角色,参与者维护了每台服务器的局部状态,协调者维护了分布式事务的全局状态。常见的做法是对协调者记日志来持久化分布式事务的全局状态,而OceanBase没有这么做。如果协调者出现故障,OceanBase通过查询所有参与者的状态来恢复分布式事务。通过这种方式节省了协调者日志,而且只要所有的参与者都预提交成功,整个事务就成功了,不需要等协调者写日志就可以应答客户端。
高可用是分布式数据库的必需的特性,任何组件都能容忍部分失效而不影响整个系统的可用性。所以分布式系统内的任何组件都需要避免单节点故障问题,尽量每个组件都是集群或者最起码主备同步。而集群需要大量使用Paxos协议保证可用性和最终一致性。还有故障的自恢复性也是高可用性的保证。在高可用性的同时也需要兼顾高性能,高效的数据结构,前置缓存机制,异步事务等方式来提高系统性能。
大部分分布式数据库都是和MySQL 兼容的,这样就方便老系统的迁移,同时也可以使用mysql的强大的数据库工具维护系统。
Conclusion
虽然笔者对分布式数据库的研究尚浅,分布式系统在每个细节都是非常的复杂,而且每个细节都具有非常多的优化空间,个人也是非常看好分布式数据库的未来趋势,如果感兴趣的同学可以看看以下的一些参考资料,里面有更多的细节介绍。
references:
https://static.googleusercontent.com/media/research.google.com/zh-CN//archive/spanner-osdi2012.pdf
https://pingcap.com/docs-cn/overview/#tidb-整体架构
https://yq.aliyun.com/articles/69306
https://github.com/alibaba/oceanbase
https://github.com/pingcap/tidb