数据库系统经过几十年的演进,近几年分布式数据库发展迅速。国内外涌现出许多分布式数据库初创公司。为什么分布式数据库会流行起来?计算机历史上出现了数百种数据库系统,为什么我们需要分布式数据库?一、为什么要走向分布式数据库让我们追溯一下数据库的发展史,看看为什么会出现分布式数据库。1.1960年代:第一个数据库1961年,CharlesBachman等人设计了第一个计算机数据库管理系统(DBMS)。这个网络模型(Networkmodel)数据库叫做IDS(IntegratedDataStore)。不久之后,IBM于1968年开发了分层模型(hierarchicalmodel)数据库IMS(InformationManagementSystem)。这两种数据库都是实验先驱。无论是网络模型还是层次模型,最初的数据库都是非常难用的,没有很多我们今天已经习惯的东西:没有表,没有SQL;数据存储粗放,整个数据结构要通过指针查询;逻辑层和物理层不分离,没有独立的模式(schema),并且要添加属性,必须重新加载所有数据,然后转储;原始数据库没有独立存储数据,没有任何抽象,需要开发人员花费大量精力才能使用。2.1970年代:关系型数据库70年代,IBM研究员EdgarFrankCodd看到他身边的程序员花费大量时间处理查询、更改模式以及思考如何存储数据,因此他创建了今天被称为关系模型的东西.关系模型建立后,IBM专门研究推出了著名的SystemR,这是第一个实现SQL和事务的DBMS。SystemR的设计对后来的各类数据库产生了积极的影响。关系模型摆脱了查询和数据存储之间的紧耦合。查询独立于存储,数据库可以在后台自由优化。程序员无需了解其背后的存储方式,只需要通过SQL与数据库进行交互即可,这对开发者非常友好。1978年甲骨文的发布,点燃了商业数据库的导火索。3.20世纪末:走向成熟在接下来的几十年里,数据库将进入成长期,逐渐走向成熟。早期的层次模型和网络模型消失了,关系型数据库成为主流。SQL成为我们今天仍在使用的数据库的标准查询语言。数据库的商业化越来越完善,PostgreSQL、MySQL等开源数据库开始出现。由于大型商业数据库非常昂贵,一些互联网公司开始使用MySQL等开源数据库作为替代。4.2000年代:NoSQL21世纪初,互联网蓬勃发展。突然之间,许多公司需要支持越来越多的用户,并且不得不24*7不间断地运行服务。为此,互联网公司不得不在多台电脑上进行复制。和分片来存储他们的数据。分片存储就是把表按照某个关键字拆分成多个分片,比如按照年份拆分。2000年的数据存放在第一台机器上,2001年的数据存放在第二台机器上。等等。这通常由数据库管理员完成。同时,为了让应用能够在不修改代码的情况下读写分片数据,必须在这些分片前放置一个中间件,将应用原有的SQL转换为支持分片的SQL。如下所示。当然,这种方案也有一些缺点,比如:不支持跨分片交易;重分片难度大,将成为数据库管理员的噩梦;可扩展性,因为他们需要构建越来越大的应用程序来服务越来越多的用户。这些东西都是为了追求可扩展性。为此,这些公司也开发了NoSQL,代价是放弃了关系模型、事务和数据一致性保证(有些NoSQL只保证最终一致性)。前面提到,在1970年代,EdgarFrankCodd为了减轻开发人员的精神负担设计了关系型数据库,NoSQL解决了应用程序需要的可扩展性,但似乎又回到了过去,程序员不得不面对NoSQL功能不足的问题——也就是JimGray所说的:“所有的存储系统最终都会演变成数据库系统。”5.2010s:分布式数据库为什么要做分布式数据库?从历史发展分析中应该相当清楚,现有的数据库解决方案给开发人员和管理员带来了过多的负担。当你开始一个新的大项目时,选择单点数据库会牺牲未来的可扩展性,而选择NoSQL会给开发人员解决问题带来额外的负担,并且可能不支持事务等优秀的功能。分布式数据库试图结合两者的优点来构建一个两全其美的系统:它可以支持完整的关系模型并提供高可扩展性和可用性。分布式数据库通常称为NewSQL或DistributedSQL——不管你怎么称呼它们,它们都是指在多台机器上运行的数据库。这并不是说NoSQL完全没用,事实上人们已经在NoSQL上构建了很多成功的系统,只是难度要大得多。Google的分布式数据库Spanner论文中有一句话:我们认为,最好让应用程序程序员在出现瓶颈时处理由于过度使用事务而导致的性能问题,而不是总是围绕缺少事务进行编码。翻译过来就是:“我们认为最好让应用程序开发人员来解决过度使用事务导致的性能问题,而不是让开发人员总是围绕缺少事务来编写代码。”即事务是否会对性能造成影响应该留给业务开发者去考虑,而作为数据库必须提供事务机制来满足各种应用的共同需求。Spanner论文发表后,许多优秀的开源分布式数据库开始涌现,包括:CockroachDB、TiDB、YugabyteDB,以及最近开源的OceanBase。通过回顾数据库的历史进程,我们知道了为什么会出现分布式数据库,现在我们需要关注的是如何实现分布式数据库。2.如何实现分布式数据库分布式数据库我们关注的重点是:数据如何分布在机器上;数据副本如何保持一致性;如何支持SQL;如何实现分布式事务;1.数据分布NewSQL和NoSQL的数据分布类似,都认为所有的数据不适合存储在一台机器上,必须分段存储。因此,我们需要考虑:1)如何划分碎片?2)如何定位具体数据?①分片的方式主要有两种:hash或range。哈希分片是通过哈希函数计算一个关键词,得到一个哈希值,根据哈希值判断数据应该存储在哪里。这样做的好处是很容易定位到数据,只需要跑hash函数就知道数据存放在哪台机器上;但是缺点也很明显,因为哈希函数是随机的,数据不会支持范围查询。范围分片是指按照一定的范围划分数据存储的位置。举个最简单的例子,按照首字母从A-Z分成26个分区。这种分片对于范围查询非常有用;查询知道数据在哪个节点后,似乎会有一些性能损失,但由于范围很少变化,所以很容易缓存范围信息。比如下图,我们按照关键字将其分为三个范围:[以a开头,以h开头),[以h开头,以p开头),[以p开头,无穷大)。如下图所示,通过这种方式进行范围查询效率更高。最后一个我们关心的问题是,当一个分片的数据量太大,超过了我们设置的阈值时,如何扩容分片?由于有一个中间层来做转换,这也很容易做到,只需在现有范围内选择一个点并将范围分成两半即可得到两个分区。如下图所示,当p-z的数据量超过阈值时,为了避免负载压力,我们对范围进行了拆分。显然,这里需要权衡取舍。如果范围阈值设置得太高,机器之间移动数据会很慢,并且很难从故障机器上快速恢复数据;但是如果范围阈值设置得很小,中间的转换层可能会增长很快,增加查询的开销,数据会同时被频繁的拆分。一般范围阈值为64MB到128MB,Cockroachdb使用64MB,而TiDB默认为96MB。2、数据一致性一个带有“分布式”二字的系统,当然需要容错。为了避免一台机器挂掉后数据全部丢失,通常会将数据复制到多台机器上进行冗余存储。但是在分布式系统中,请求会丢失,机器会宕机,网络会延迟,所以我们需要一些方法来知道冗余副本中哪些数据是最新的。最常见的数据复制方式是主从同步(或直接复制冷备数据),主节点将更新操作同步到从节点。但是这种方式存在潜在的数据不一致,如果同步更新操作丢失了怎么办?从节点恰好写入失败怎么办?有时这些错误甚至会永久损坏数据,需要数据库管理员的干预。保持一致性往往是以牺牲性能为代价的(后面会讨论),所以大多数NoSQL只是保证最终一致性,通过一些冲突处理方案来解决数据不一致。现有比较知名的数据复制算法就是我们经常听到的Paxos、Raft、Zab或者ViewstampedReplication等算法。其中,谷歌花了数年时间才实现了满足生产需求的Paxos算法。Raft是后起之秀,是斯坦福大学博士生OngaroDiego在Paxos的基础上设计的一种比较容易理解的共识算法。Raft诞生后,席卷了分布式共识算法领域。现在你可以在Github中找到许多Raft开源实现,将它们克隆到你的应用程序中以实现可靠的数据复制(不要真的这样做!)。Raft可能不是真的好用,但是它让编写一个一致的系统比以往任何时候都容易,具体的算法细节这里不再赘述。简而言之,Raft算法只需要一半以上的节点写入成功,即认为写入操作成功,并将结果返回给客户端。当故障发生时,Raft算法可以重新选举leader,只要少于一半的节点故障,Raft就会正常工作。Raft算法可以可靠地复制数据,系统可以容忍不超过一半的节点故障。在分布式数据库中,分片使用共识组来复制数据。具体的Raft共识组称为Raft组,Paxos共识组称为Paxos组。我从TiDB的官网上找到了一张图片。TiDB将一个分片称为一个区域。图中有3个Raftgroup,用来复制三个Region的数据。图像版权侵权和删除软件工程没有灵丹妙药。使用共识算法仍然需要面对很多生产问题,比如成员变化、范围分区变化、线性一致性等。只是现在我们有了坚实的学术后盾,这是正确的做法。3、SQL表数据基于KV存储解决了KV存储之后,我们还需要想办法使用KV结构来存储表结构。通常,增删改查可以抽象为以下5个KV操作(可能更多,但基本就这些)。我们说的是类似OLTP的分布式数据库都是行存储的。我们以CockroachDB为例。表格通常包含行和列。表格可以转换为以下结构:/
