当前位置: 首页 > 科技观察

数据库为何以及如何变得分布式?

时间:2023-03-16 15:17:15 科技观察

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

///->Value以斜杠分隔,以便于阅读。///这部分表示每个表必须有一个主键。这不是很直观。例如,对于下面的建表语句:CREATETABLEtest(idINTEGERPRIMARYKEY,nameVARCHAR,priceFLOAT,);转换为KV存储如图:当然这种存储方式会将float等所有类型都转换为string类型。另外,数据库通常会创建一些非主键索引,主要分为两类:UniqueindexesNon-uniqueindexes唯一索引比较简单,因为值是唯一的,我们可以使用如下映射:/
//->Value如图:非唯一索引类似于主键,只不过它的值为空。如图:上表数据的KV规则有些过时。CockroachDB最新的映射规则请参考《Structured data encoding in CockroachDB SQL》。但是里面的想法是相似的。当然,表格数据转KV的方式并不是只有这种方式。TiDB按照以下规则进行映射:这种方式不对每一列进行分离存储。方法类似,不再展开。见《三篇文章了解 TiDB 技术内幕 - 说计算》。分布式事务当我们谈论事务时,我们永远都离不开ACID。分布式事务中最难保证的就是原子性和隔离性。在分布式系统中,原子性需要通过原子提交协议来实现,比如两阶段提交;而隔离可以通过两阶段锁或者多版本并发控制(MVCC)来实现不同的隔离级别。分布式数据库都实现了MVCC,GoogleSpanner设计了TrueTime来实现,但是TrueTime不开源;TiDB基于GooglePercolator实现。Cockroach的分布式事务实现比较复杂,涉及到的新东西也比较多,后面会讲到。由于篇幅原因,分布式事务将是我们后面重点讨论的内容,这里不再展开。结论最后,下图给出了一个分布式数据库的简要架构。开源造福于人类。现在有很多优秀的开源分布式数据库。都是很好的学习资料。笔者将在后续文章中继续分享CockroachDB、TiDB、YugabyteDB和OceanBase的技术细节。感谢这些开源。值得一提的是,在数据库领域获得图灵奖的学者并不多。有四位大师,CharlesBachman、EdgarFrankCodd、JimGray和MichaelStonebraker。本文提到了其中的前三个。2020年图灵奖获得者JeffreyUllman虽然在数据库领域也有所建树,但他获奖的是编程语言领域(《龙书》),而非数据库领域。无论是在学术领域还是在工业领域,真心希望分布式+数据库能够更加努力!本文转载自微信公众号“多糖”,可通过以下二维码关注。转载本文请联系多歌堂公众号。