OB君:作为一款100%自研的分布式数据库,OceanBase历经了近十年的发展历程。近十年来,OceanBase的存储架构经历了多次演进,以解决业务越来越复杂苛刻的存储需求。本文整理自赵裕众(花名陈群)在2019 SACC中国系统架构师大会中的演讲。
随着用户数据量的不断增长,基于传统共享存储的纵向扩展能力渐渐变得力不从心,分布式存储成为应对用户海量数据的标配。
作为一位架构师,在设计系统的分布式存储架构时,需要关注哪些方面呢?或者我们换句话说,对于客户来说,一个理想中的分布式存储产品应该具有哪些特性呢?
我们认为完善的分布式存储架构应该关注这五个方面:
- 扩展性:扩展性可谓是分布式存储有别于单机存储的重要特征,分布式存储的扩展性远远好于单机存储,但是不同分布式存储系统的扩展性之间仍然有着非常大的差异。某些分布式系统在几十个节点的量级上可能运行的很好,但是节点如果再多下去,到了上百个乃至上千个的时候可能就会面临很大的问题。用户数据的增长没有止境,如果不能做到线性扩展,那么今天能够支撑业务的系统到了明天可能就会成为业务进一步发展的阻碍。
- 高可用:在分布式系统中出现节点故障是很常见的事情,分布式系统中的节点越多,出现节点故障的频率也就会越高。在发生节点故障时是否能够保证系统仍然可用,对于很多业务来说至关重要。根据故障类型和恢复时间的不同,高可用也分有不同的级别,系统是否可以容单点故障、多点故障、单机房故障、多机房故障、单城市故障、多城市故障?系统恢复是否可以做到天级恢复、小时级恢复、分钟级恢复、秒级恢复?不同业务场景可能会对高可用有不同的要求。
- 一致性:一致性其实是一个被滥用的概念,很多同学会将其和数据库事务特性中ACID中的C相混淆,在这里我们特指分布式系统的一致性。那么分布式系统的一致性指的是什么?如果用一句话进行概括,我们认为一致性指的是在一个分布式的读写系统中,用户是否总能读到最新写入的数据。如果总是能读到,那么这个系统就是强一致的,否则则是弱一致的。最终一致性则是弱一致的一种特例,指的是尽管不能总是读到最新的数据,但随着写入操作的终止,最终还是可以读到最新的数据。尽管很多分布式系统都宣称提供一致性,但很多时候它们只是提供弱一致或者最终一致。强一致对于一些业务特别是和交易相关的金融业务来说至关重要,如果不能保证总是读到最新的数据,那么就会有发生资损的可能。
- 低成本:分布式存储系统可以使用更加廉价的PC服务器来代替高端的小机及大机,在成本上具有显著的优势。但低成本并不意味着低性能,事实上由于分布式系统下有着非常多的节点,同时使用这些节点的能力可以为我们带来相比大型服务器更高的性能。低成本高性能可以为我们的用户节省更多的系统成本。
- 易用性:低成本特性通常关注于硬件成本,易用性则关系到人力成本。对于开发同学来说,易用性意味着简单易上手的使用接口,最好学习和迁移成本为零,同时功能强大,能够满足各式各样的需求;对于运维同学来说,易用性则意味着系统稳定健壮,系统监控及运维手段完善,学习及使用门槛低。
架构演进历程
架构设计服务于业务,再完美的系统架构都需要有业务进行使用才能创造价值。对于业务来说,当然希望我们的产品能够同时具备高扩展、高可用、强一致、低成本以及零门槛的易用性,但对于系统架构师和开发者来说,这五个特性之间存在相互矛盾的不少,同时在实现这些特性时也会面临巨大的复杂性,这要求我们在做系统设计及实现时需要有所权衡。
下面我们就OceanBase创立九年多以来存储架构的演进历程,来回顾每一次架构变更背后的权衡与思考。
1)OceanBase 0.1版本(2010年)
OceanBase由阳振坤于2010年在淘宝创立,当时淘宝大多数业务都已经按照用户维度做了分库分表,一个全新的分布式存储系统似乎很难有用武之地。最终我们找到了OceanBase的第一个业务:淘宝收藏夹,也就是我们今天打开手淘看到喜欢的商品点收藏时用到的收藏夹,直到今天它仍然跑在OceanBase数据库上面。
当时收藏夹面临了一个分库分表难以解决的问题,它的核心业务主要包括两张表,一张是用户表,记录一个用户收藏的商品条目,数量从几条到几千条不等;另一张是商品表,记录一件商品的描述、价格等明细信息。如果一个用户增加/删除收藏,那么相应的就向用户表中插入/删除数据就可以了;同时如果一个商家需要修改商品描述,例如修改商品价格等信息,那么相应的更新商品表就可以了。
当用户打开收藏夹时,通过用户表和商品表的连接查询,就可以展现给用户最新的商品信息。最开始的时候,这两张表是在一个数据库里面,也一直运行地很好,但随着用户数据量的增长,单个数据库放不下了,一般常用的做法是将表按照用户维度进行拆分,用户表是可以这样拆,但是商品表中没有用户字段,如果按照商品条目进行拆分,那么在用户打开收藏夹时,就需要对多个不同的库进行查询并做连接,当时的数据库中间件并没有这样的能力,即使可以这么做,一次查询也会耗费非常长的时间,会极大的影响用户体验,业务遇到了很大的困难。
OceanBase接下了用户的这个难题,如果我们分析扩展性、高可用、一致性、低成本和易用性这五个特性,那么什么是业务的刚需,什么是业务可以放弃的呢?业务最强的刚需是扩展性,因为传统的单机模式已经走到了尽头;最可以放弃的其实是易用性,因为业务对写入查询的使用非常简单,提供简单的读写接口就可以满足业务需求,业务甚至不需要在表上构建索引。同时我们也注意到业务对一致性也有一定的需求,业务可以容忍一定的弱一致读,但不能容忍数据出错。这些特性决定了OceanBase从诞生的第一天起,就是一个支持在线事务处理的关系型分布式数据库。
我们注意到收藏夹这个业务的特性,它的存量数据比较大,但是每天的增量并不大,毕竟每天新增收藏的用户并不是特别多。它更关心数据存储的扩展性,而对写入的扩展性要求并不是很高。我们将数据分为两部分:基线数据和增量数据。基线数据是静态的,分布式地存储在ChunkServer上。增量数据写在UpdateServer上,通常是存储在内存里面,通过Redo Log支持在线事务,在每天的业务低峰期,UpdateServer上的数据会与ChunkServer上的数据做合并,我们称之为“每日合并”。MergeServer是一个无状态的Server,提供数据写入的路由与数据查询的归并;RootServer负责整个集群的调度与负载均衡。这是一个类似于LSM Tree的存储架构,这也决定了今后OceanBase的存储引擎都是基于LSM Tree的。
我们回过头来看OceanBase0.1的架构,它实际上具有很强的一致性,因为写入是个单点,读到的数据一定是最新写入的数据,同时成本也并不高,也具有一定的扩展性,存储空间可以很容易地做扩展,很好满足了当时业务的需求。
2)OceanBase 0.2-0.3版本(2011年)
很快OceanBase 0.1版本上线了,并为收藏夹业务提供了读服务,但业务不能把所有流量都切到OceanBase上面来,因为OceanBase 0.1版本的架构有着一个很大的缺陷:它不是高可用的。任何一台服务器的宕机都会造成数据的不可访问,这对于收藏夹这样的业务是无法接受的。很快我们带来了OceanBase 0.2版本的架构,补上了高可用的短板。
在OceanBase 0.2版本中我们引入了主备库模式,这也是当时传统数据库常用的容灾模式,数据通过redo log从主库同步到备库,当主库发生问题时,可以把备库切换为主库继续提供服务。redo log的同步是异步的,这意味着主备的切换是有损的,可能会丢失若干秒的数据。
我们将OceanBase 0.2版本和OceanBase 0.1版本的架构进行对比,会发现OceanBase 0.2版本终于有了高可用这一重要特性,但高可用的获得不是没有代价的,首先系统不再是强一致的了,我们不能够保证业务总是能够读到最新的数据,在宕机场景下,数据可能会有部分丢失;其次主备库的引入极大地增加了成本,我们使用的机器数量翻番了。之后的OceanBase 0.3版本基于OceanBase 0.2版本做了很多代码上的优化,进一步降低了系统成本,但从架构上来说和OceanBase 0.2版本并没有显著的差别。
3)OceanBase 0.4版本(2012年)
随着收藏夹业务的成功,很快我们接到了更多新的业务,淘宝直通车是一个面向商家的业务,也面临着分库分表难以解决的问题。首先淘宝直通车的数据量越来越大,单库难以支撑,同时它又是一个OLAP类型的业务,有很多多表间的关联查询,每张表的维度又各不相同,无法统一按照用户id进行拆分。对于OceanBase的扩展性、高可用以及低成本业务都很满意,但是接口使用确实是太痛苦了。那么问题来了,什么是最好的接口语言?对于编程来说,可能不同的语言都有不同的拥趸,但对于数据操作来说,我们认为SQL一定是最好的语言。对于简单的KV查询,你可能会觉得SQL过于沉重了,但当你的业务慢慢复杂起来后,SQL一定是使用最简单轻便的。
在OceanBase 0.4版本,我们对SQL有了初步的支持,用户可以使用标准SQL来访问OceanBase,支持简单的增删改查以及关联查询,但对SQL的支持并不完整。同时OceanBase 0.4版本也是我们最后一个开源版本。
对比OceanBase 0.4版本和OceanBase 0.2版本的架构,在OceanBase 0.4版本我们最终补上了易用性的白板,开始慢慢有了一个标准分布式数据库的样子。
4)OceanBase 0.5版本(2014年)
2012年底的时候,OceanBase团队来到了支付宝。当时支付宝面临着全面去掉IOE的强烈需求,IOE的成本太高了,但PC服务器的稳定性难以和高端存储相比,如果我们使用MySQL这样的开源数据库基于PC服务器进行替代,业务就面临着可能会丢数据的潜在风险。当时基于MySQL的容灾方案仍然只是主备同步,对于支付宝交易支付这样的核心系统来说,丢失一笔订单造成的损失难以估量。业务对数据库的强一致和高可用提出了更高的要求,也使得我们搭建了OceanBase 0.5版本的新一代架构。
在OceanBase 0.5版本中,我们引入了Paxos一致性协议,通过多数派选举来保障单点故障下的数据一致性。一般情况下,OceanBase 0.5版本的部署模式会是三副本,当有一个副本出现问题时,另外两个副本会补齐日志并重新选出一个主提供服务,我们可以做到单点故障下不丢失任何数据,同时故障恢复时间小于30s。同时为了更好地支撑业务,在OceanBase 0.5版本中,我们全面兼容了MySQL协议,支持了二级索引并有了基于规则的执行计划,用户可以用MySQL的客户端来无缝连接OceanBase,可以像使用MySQL一样来使用OceanBase。
对比OceanBase 0.4版本和OceanBase 0.5版本的架构,我们发现OceanBase 0.5版本基于Paxos,有了更强的高可用以及强一致,基于SQL有了更好的易用性,但代价是从两副本变成三副本,系统成本进一步增加了50%。
5)OceanBase 1.0版本(2016年)
在OceanBase 0.5版本的架构下,业务对强一致、高可用以及易用性的需求都得到了很好的支持,痛点慢慢集中在扩展性和成本上。随着用户写入量的不断增长,UpdateServer的写入单点总是会成为瓶颈,同时三副本也带来了过高的成本消耗。OceanBase 1.0版本带来了全新的架构,重点解决了扩展性和成本的痛点。
在OceanBase 1.0版本中,我们支持了多点写入,从架构上将UpdateServer、ChunkServer、MergeServer和RootServer都合并为一个OBServer,每一个OBServer都可以承担读写,整体架构更加优雅,运维部署也更加简单。一张表可以被划分为多个分区,不同分区可以散布在不同的OBServer上,用户的读写请求通过一层代理OBProxy路由到具体的OBServer上进行执行。对于每个分区都仍然通过Paxos协议做三副本高可用,当有一台OBServer出现故障时,这台OBServer上的分区会自动切到其他包含对应分区的OBServer上提供服务。
在成本方面,我们注意到在Paxos协议中,需要三副本同步的只是日志,日志需要写三份,但是数据并不是,和数据相比日志量总是小的。如果将日志和数据分开,我们就可以使用两副本的存储开销实现三副本高可用。在OceanBase 1.0版本中,我们将副本分为两种类型:全功能副本和日志副本,其中全功能副本既包含数据也包含日志,提供完整的用户读写;日志副本只包含日志,只进行Paxos投票。
同时在OceanBase 1.0版本中,我们引入了多租户的概念,在同一个OceanBase集群中,可以支持多个不同的租户,这些租户共享整个集群资源,OceanBase会对不同租户的CPU、内存、IO及磁盘使用进行资源隔离。租户可以根据自己的需要配置不同的资源容量,集群会根据不同OBServer的负载做动态的负载均衡。这使得我们可以把很多个小租户部署到同一个大集群中来,降低整体的系统成本。
和OceanBase 0.5版本相比,OceanBase 1.0版本的扩展性有了大幅提升,而且由于引入了日志副本和多租户技术,在成本上有了大幅降低;但扩展性的提升不是没有代价的,多点写入带来了巨大的复杂性。首先用户的写入并不一定会只写单个分区,对于多个分区的写入不可避免会带来分布式事务,我们使用两阶段提交协议来完成分布式事务。其次在一个分布式系统中获取一个全局单调递增的时间戳是极其困难的,由于没有全局时钟,我们基于局部时间戳做单分区内的读写并发控制,这使得系统的一致性有了一定的限制,虽然单分区的查询仍然是强一致的,但跨分区的查询无法保证读的强一致,这对于用户而言会是一个不小的限制。同时由于分区的关系,二级索引成为了分区内的局部索引,这要求索引键中一定需要包含分区键,无法支持全局的唯一索引,这对于用户使用也造成了一定的不便。
6)OceanBase 2.0版本(2018年)
OceanBase 2.0版本的外部整体架构与OceanBase 1.0版本没有太大差别,仍然是一个share nothing的三副本架构,但在内部我们对扩展性、高可用、一致性、低成本和易用性都做了极大的提升。
在扩展性方面,我们实现了分区分裂的功能。在建表的时候,用户对合适的分区数可能没有很好的估计,当分区过大时,可以通过分区分裂来使得分区数变多。尽管分区分裂是一个比较重的DDL操作,在OceanBase 2.0版本中,分区分裂是可以在线进行的,对用户的正常业务读写并不会造成太大影响。
在高可用方面,我们支持了主备库功能,对于某些只有双机房的用户,可以在机房内通过三副本做机房内的无损容灾,通过主备库做跨机房的有损容灾。
在一致性方面,我们支持了全局快照,真正意义上实现了分布式读写下的强一致。基于全局快照,我们也完成了对全局索引以及外键的支持。
在低成本方面,我们在事务层支持了TableGroup,允许把一组相近的表“绑定”在一起,减少分布式事务的开销。在存储层引入了数据编码,通过字典、RLE、Const、差值、列间等值、列间前缀等算法进一步压缩存储空间的占用,并且对于数据的编码是自适应的,会根据数据特征来自动选择合适的编码算法。
在易用性方面,我们支持了Oracle租户,允许用户在同一套ObServer集群中同时使用MySQL租户与Oracle租户,并且支持存储过程、窗口函数、层次查询、表达式索引、全文索引、ACS、SPM、回收站等功能。
总结
尽管今天的OceanBase 2.0版本在扩展性、高可用、一致性、低成本以及易用性方面做到了更好的平衡,但这样的架构并不是一蹴而就的,从OceanBase 0.1版本到OceanBase 2.0版本的发展历程来看,OceanBase的架构总是在一直进化,为了能够更好地服务于业务,很多事物总是面临着许多权衡取舍,一项特性的提升会以其他特性的降低为代价。架构的优化演进没有终点,未来为了更好满足业务的需求,OceanBase的架构还会不断进行演化。
作者介绍
赵裕众(花名陈群),蚂蚁金服高级技术专家,目前在OceanBase团队负责存储相关的开发工作。2010年于中国科学技术大学获得计算机博士学位,同年加入支付宝从事分布式事务框架的研发,2013年加入OceanBase团队。
OceanBase 技术交流群
— 想了解更多OceanBase背后的技术秘密?
— 想与蚂蚁金服OceanBase的技术专家深入交流?
— 只需扫码关注OceanBase微信公众号并回复“加群”,快速加入OceanBase技术交流群!
— 有钉钉的小伙伴也可以加入OceanBase钉钉互动群:搜索群号21949783