数据库领域图灵奖获得者 Jim Gray 说过:“所有的存储系统最终都会演变成数据库系统。”
数据库系统经过几十年演进后,分布式数据库在近几年发展如火如荼。于是大家开始思考一个问题:
为什么分布式数据库开始流行?
在计算机历史上出现过数百个数据库系统,
为什么需要分布式数据库?
一、为何走向分布式数据库
追溯数据库发展历史,看看分布式数据库为何出现:
图 | 数据库发展历程
1960 年代:第一个数据库
1961 年,Charles Bachman 等人设计了第一个计算机数据库管理系统,这个网状模型的数据库被称为 IDS。随后不久,IBM 在1968 年开发了层次模型的数据库 IMS。这两个数据库都是实验性的先行者。
无论是网状模型还是层次模型,最开始的数据库没有很多我们如今习惯的东西:
没有表,更没有 SQL;
数据粗暴存储,不得不通过指针遍历整个数据结构来进行查询;
逻辑层和物理层并不分离,没有独立的模式,要增加属性,必须重新加载全部的数据然后转存;
最初的数据库没有独立存储数据,没有任何抽象,这导致开发者需要耗费大量精力来使用。
1970 年代:关系型数据库
到了20世纪70年代,IBM的研究员 Edgar Frank Codd 看到他周围的程序员每天花费大量时间处理查询、改变模式和思考如何存储数据,于是他创造了今天众所周知的关系模型。
关系模型摆脱了查询和数据存储之间的紧密耦合,查询独立于存储,数据库可以自由地在幕后进行优化,程序员无需知道背后的存储方式,只需要通过 SQL 与数据库进行交互,这对于开发者非常友好。
20世纪末:走向成熟
接下来的几十年里,数据库进入成长期,一步步走向成熟。早期的层次模型和网状模型消失了,关系型数据库成为主流。SQL 成为数据库标准查询语言,直到今天我们仍然在使用。
数据库商业化也越来越完善,同时开始出现 MySQL 等开源数据库。由于大型商业数据库非常昂贵,一些互联网企业开始使用 MySQL 等开源数据库作为替代方案。
2000 年代:NoSQL
21 世纪伊始,互联网走向繁荣,突然间许多公司需要支持越来越多的用户,并且必须7*24小时不间断运行服务,为此互联网公司不得不在多台计算机上复制和分片存储他们的数据。
分片存储即将表按照某个关键字拆分成多个分片,例如按照年进行拆分,2000 年的数据存储在第一台机器上,2001 年的数据存储在第二台机器上,以此类推。这通常由数据库管理员来完成。同时为了让应用程序不修改代码、无感知地读写分片数据,必须要将一个中间件放到这些分片前面,将应用程序原本的SQL转换为支持分片的SQL。如下图所示:
图 | 分片存储架构图
当然,这类方案也有一些缺点,例如:
不支持跨分片事务;
重新分片是困难的,会成为数据库管理员的噩梦;
Google 等公司如此分片存储数据库,目的是不惜一切代价来获得可扩展性,因为他们需要构建越来越大的应用,服务越来越多的用户。这些事情都是为了追求可扩展性。
为此,这些公司还开发了
NoSQL,不惜放弃了关系模型,放弃了事务,放弃了数据一致性保证。
2010 年代:分布式数据库
为什么要构建分布式数据库呢?通过历史发展分析应该相当清楚了,现有的数据库解决方案给开发者和管理员带来了过重的负担。当你开始一个新的大项目,选择一个单点数据库会牺牲掉未来的可扩展性,选择一个 NoSQL 又会让开发者承受额外的负担来解决问题,并且可能不支持事务等优秀的功能。
分布式数据库试图结合两者优点,构建成为两全其美的系统:
既能支持完整的关系模型,又能提供高可扩展性和可用性。分布式数据库常被称为 NewSQL 或 Distributed SQL——无论怎么称呼,都指那些在多台机器运行的数据库。
Spanner 论文中有句话翻译过来是:“
我们认为最好让应用程序开发者来解决因过度使用事务而导致的性能问题,而不是让开发者总是围绕着缺少事务编写代码。”
也就是说,事务是否会造成性能影响应该由业务开发者来考虑,而
作为一个数据库必须提供事务机制,来满足各种应用常见的需求。
Spanner 论文发表后,开始涌现许多优秀的分布式数据库,如
CockroachDB、YugabyteDB 和国产的
Oceanbase、GreatDB等。
二、如何实现分布式数据库
分布式数据库我们关注:
数据如何在机器上分布;
数据副本如何保持一致性;
如何支持 SQL;
分布式事务如何实现;
1 、数据分布
万里的GreatDB分布式数据库支持多层次不同粒度的数据分布式分布:
库级别分布式分布、表级别分布式分布以及行级别分布式分布。
对于行级别数据分布式分布,GreatDB提供了Hash、Range、List、Mod四种数据分片方式。
GreatDB支持多种类型的表分布策略:
1)Normal:分布在某个shard上;
2)Global:分布在所有的shard上,每个shard上拥有全量的表数据;
3)Partition:分布在所有的shard上,每个shard上拥有部分表数据;
Hash:数据按照分区字段的hash值,分布在不同的shard上,每个shard上存储部分表数据,不同分片数据之间没有交集;
Range: 数据按照分区字段的范围,分布在不同的shard上,每个shard上存储部分分片数据,不同分片数据之间没有交集;
List:数据按照给定的值,分布在不同的shard上,每个shard上存储部分分片数据,不同分片数据之间没有交集。
GreatDB支持创建分片表,为分片表指定预先创建的分片策略。
集群调度节点负责根据分片表选定的分片策略在数据节点之间对数据进行sharding操作。
GreatDB分片策略
查看表分布策略:可以通过查询information_schema.greatdb_table_distribution 查询表的分布策略,如下示例所示:
图 | 查询表分布策略
普通表:普通表只在后端个shard中存储数据,后端 shard 中的表名,和户创建的表名保持致。
普通表的分布策略为:
1.创建表时,对表名计算 hash 值,结果为 h1
2. h1 % N 求余操作,得出结果 m
3. 那么表将分布在 sd_m 中表创建后,其分布策略不会随着 shard 数量的增加和减少发改变。除户主动对表进迁移操作。
全局表:全局表在后端所有的 shard 中存储数据,后端 shard 中的表名,和户创建的表名保持致。
全局表的分布策略:
1. 建表时,假设共有 N 个 shard
2. 则该全局表会分布到所有 N 个 shard 中
需要注意的是,当添加新的 shard 后,新 shard 中不会存储该表数据。除户主动执表的迁移操作。
分区表:分区表不同的分,将存储在后端不同的 shard 中,每个分的数据只存储在个 shard 中。后端 shard 中的表名和户创建的表名不致。在后端实际存储节点中,会在表名后添加 #part_id 后缀,如表 test.pt1,共 4 个分,则每个分的表名为 test.pt1#0 , test.pt1#1 , test.pt1#2 , test.pt1#3 。
分区表的分布策略较为复杂,内部采的是线性哈希的算法。其中,shard 的数量表 hash 桶的数量,分 id 表 hash 值。采线性哈希算法的的便于在扩缩容情况下,保持分规则的稳定性,减少分布式事务。
2、 数据一致性
一个带有“分布式”三个字的系统需要容忍错误,为了避免一台机器挂掉后数据彻底丢失,通常会将数据复制到多台机器上冗余存储。但分布式系统中请求会丢失、机器会宕机、网络会延迟,因此我们需要某种方式知道冗余的副本中哪些数据是最新的,最常见的复制数据方式是主从同步,主节点将更新操作同步到从节点。
但这样存在潜在的数据不一致问题:
同步更新操作丢失了怎么办?
从节点恰好写入失败了怎么办?
有时这些错误甚至会永久损坏数据,需要数据库管理员介入。
保持一致性常常会以性能为代价,因此,一些分布式数据库只保证最终一致性,并通过一些冲突处理方案来解决数据不一致。
现有著名的复制数据的算法包括
Paxos、Raft、Zab 或
Viewstamped Replication 等算法。其中,Paxos基本成为了分布式领域内一致性协议的代名词,是几乎所有相关课程必讲内容以及很多其它一致性协议的起点。
Paxos又可以细分为两种:
单Paxos和
多Paxos。
对照上节的副本状态机模型,直观上可以如此理解两者的差异:所谓
单Paxos,即副本状态机中各个服务器针对Log中固定某个位置的操作命令通过协议达成一致,因为可能某一时刻不同服务器中Log中相同位置的操作命令是不一样的,通过执行协议后使得各个服务器对应某个固定位置的操作命令达成一致。
而
多Paxos则是指这些服务器对应的Log内容中多个位置的操作命令序列通过协议保持一致。多Paxos往往是同时运行的多个单Paxos协议共同执行的结果。
Paxos算法的目的是为了解决分布式环境下一致性的问题。
Paxos的两个组件:
Proposer:处理客户端请求,将客户端的请求发送到集群中,以便决定这个值是否可被批准;
Acceptor:负责处理接收到的提议,他们的回复就是一次投票,会存储一些状态来决定是否接收一个值。
Paxos有两个原则:
1、安全原则——保证不能做错的事
a) 针对某个实例的表决只能有一个值被批准,不能出现一个被批准的值被另一个值覆盖的情况;
b) 每个节点只能学习到已经被批准的值,不能学习没有被批准的值。
2、存活原则——只要有多数服务器存活并且彼此间可以通信,最终都要做到下列事情:
a)最终会批准某个被提议的值;
b)一个值被批准了,其他服务器最终会学习到这个值。
总结
paxos协议可以归纳为几个原则:
不接受旧值;
为了更快达成一致,proposer会把值修改为最有可能达成一致的值;
只有多数派接受了值,值才达成一致;
一旦一个值达成一致,不可更改。
paxos是一致性协议的基础,其他的协议都是paxos的改进版本。paxos侧重理论,实现paxos非常困难,因此后来出现了许多基于paxos的改进算法。
在分布式数据库中,一个分片使用一个共识组复制数据,具体的 Raft 共识组称为 Raft 组,Paxos 共识组称为 Paxos 组。
图 | Paxos 组架构图
软件工程没有银弹,使用共识算法仍然需要面临许多生产问题,例如
成员变更、范围分区变更、实现线性一致性等等问题都要去克服。只不过现在我们有了坚实的学术支撑,这样进行复制是正确的。
3、 SQL支持
GreatDB支持完整的SQL语法标准,完美兼容MySQL通信协议、SQL语法及各种数据库对象。GreatDB SQL标准支持如下:
DML语句:如INSERT、UPDATE、DELETE及各类复杂查询SQL;
DDL语句:如ALTER TABLE、CREATE DATAbase、DROP FUNCTION等;
DCL语句:如GRANT、SHOW、SET、KILL等管理命令,同时提供了集群管理命令扩展;
视图:提供视图对象的创建、修改、删除,允许SQL查询使用视图来简化业务逻辑的编写;
存储过程:提供存储过程的创建、修改、删除,允许将业务逻辑以SQL语言的方式编写到存储过程中,简化应用开发维护,同时通过减少网络交互实现高效的执行性能;
用户自定义函数:提供用户自定义函数的创建、修改、删除。除了支持数据库系统函数,用户还可以通过SQL创建业务逻辑函数,简化SQL的开发过程;
触发器:提供触发器的创建、修改、删除,允许DBA配置行级触发器进行数据维护操作,支持针对插入、修改和删除的事前触发器和事后触发器;
定时任务:提供定时任务的创建、修改和删除,允许DBA配置定时任务来简化日常运维调度过程;
4、 分布式事务
当我们谈论事务时,永远离不开
ACID。分布式事务中最难保证的是
原子性和
隔离性。在分布式系统中,原子性需要原子提交协议来实现,例如两阶段提交;而隔离性可以通过两阶段锁或多版本并发控制来实现不同的隔离级别。
GreatDB提供分布式事务支持,通过
全局事务管理器、两阶段提交、MVCC多版本控制、行级锁等技术实现完整的分布式事务的能力,确保集群事务操作ACID完整性。支持读已提交、可重复读两种隔离级别和MVCC多版本并发控制,实现
高效的读写访问性能。同时具备
高可用特性,可以在计算层中进行快速故障转移。
如下是分布式事务一致性说明:
基于
两阶段提交协议实现分布式事务;
针对第2阶段提交部分失败场景,基于分布式事务日志确保事务肯定会提交成功;
确保分布式事务强一致,故障failover不会出现事务丢失或事务部分提交;
基于分布式一致性算法确保数据冗余副本强一致;
分布式事务日志以日志即数据的方式存储在数据节点的事物日志表中;
基于分布式事务日志对新主副本进行recover。
图 | GreatDB 确保集群事务操作ACID强
同时,GreatDB是一个分布式的集群系统。根据业务的需求,运行于GreatDB上的事务可能需要分布式的事务系统来支持
跨节点的事务一致性、全局一致的数据可见性等。
三、结语
最终,GreatDB分布式数据库简要架构如下图所示:
图 | GreatDB分布式数据库简要架构图
技术改变世界。以GreatDB为代表的分布式数据库是未来数据库演进的方向。相信在未来应用场景越发丰富的情况下,在海量数据、高并发的业务场景下,分布式+数据库将会发挥出自己更大的优势和价值,为千行百业数字化转型做出自己的应有贡献。