前言:本文仅从事务角度论述,难免一些结论性的东西会有些偏颇,毕竟数据库不仅仅只有事务,但事务却是数据库中最复杂,最难实现,最重要的技术之一。
背景:互联网公司从成立初业务往往会飞速发展,系统高并发、数据量急速膨胀对系统扩展性提出了更高的需求。对于系统应用服务来说很容易进行扩展,但是数据库却并非那么容易,因此数据库层难于扩展往往成为拖累业务发展的瓶颈。
目前主流的关系型数据库如:Oracle 、MySQL、SQL Server、 PostgreSQL 等面对数据量爆发增长,无法线性扩展问题日益凸显。主流的方案还是采用主从读写分离的架构,只能扩展一部分的读计算,对于数据写、数据存储扩容目前主要通过微服务的架构业务上对数据库做垂直拆分再水平拆分来实现。这种架构的优点在于规避了分布式事务,事务处理还在单机数据库上进行。不过换个角度来说同时也是缺点,业务上需要做很多妥协,例如将一个大应用拆分成用户模块,订单模块,商品模块,每个模块都有自己的数据库,在用户购买商品的时候需要扣减商品模块库存,在订单模块添加订单数据,这时候需要保证这两个数据库操作在同一个事务中完成变得异常困难。两种方式来解决此类问题:1、应用层实现分布式事务,这个就有点对微服务架构有点背道而驰了,而且实现难度很大、系统整体性能也没有保障。2、消息事务+最终一致性,牺牲了一致性,换来了性能的大幅度提升,不过业务要承担数据不一致带来的风险,相信大多数互联网场景下的微服务架构还是会采用这种方式。
回到数据库扩展性上,通过数据库做分库分表的方式,扩容实现起来也没那么容易往往需要人工干预,即使说通过外部工具能够做到自动化扩容(如:阿里云的DRDS)也不能避免对业务产生影响,更不要提有些业务场景下还需要做缩容。比如:双11场景。
对于扩展性上有没有比较好的数据库产品呢
有三类:
1、分布式NoSQL
NoSQL大多数是分布式的数据库,原生支持数据扩缩容,又有不错的性能表现。但是大多数NoSQL产品规避了对事务的实现,只能应用于少部分对事务没有需要的业务场景如:Web应用服务器、内容管理器、结构化的事件日志、移动应用程序的服务器端和文件存储的后备存储。
注:(MongoDB在4.0里面支持了多行事务,并打算在下个版本支持分布式事务)
由此可见在分布式场景下实现事务是多么困难。
所以这么多年过去了,即使传统关系型数据库在扩展性上暴露明显不足,NoSQL貌似也没能取代传统关系型数据库的地位。大家发现很多的业务并没有办法直接使用 NoSQL 的模型,应用需要很复杂的开发才能实现 SQL 和事务能很简单实现的功能。
2、云关系型数据库Aurora、PolarDB
此类数据库产品多采用存储和计算分离,所有计算节点共享一份数据属于 share-storage 架构。在计算层读和写被分离到不同的节点上,只有一个写节点,写节点可以处理读写请求,其他的节点都是只读节点,只读节点可以很方便的去做Scale out,写节点也可以很方便的做Scale up。在存储层采用分布式的文件系统,存储集群可以做横向扩展 (但是有上限官方的数据 Aurora 最大支持64TB、PolarDB 100TB)。Aurora和PolarDB基于MySQL和InnoDB,实现的是单点写的一主多从架构,所以在事务处理方面,没有大的变动,事务处理技术得到继承。整体上是依据SS2PL和MVCC技术实现了事务模型。本质上用的单机事务,规避了分布式事务,但MySQL事务处理机制还是有差别的,比如事务提交不用写binlog,这里不在过多介绍。
3、分布式关系型数据库NewSQL
NewSQL多采用多副本的share-nothing架构,这里就要引入分布式系统场景中的CAP理论。有人说NewSQL=NoSQL(AP)+SQL,来挑战CAP理论的正确性。其实鱼与熊掌最难兼得的部分是C和P。 本文论述分布式场景下的OLTP数据库 因此需要有AP这个大前提 ,那么NewSQL怎么保障C呢?分两种情况:1、 分区主节点和副本节点的C主要通过Paxos或者Raft协议做到强一致;2、分区之间数据操作的事务原子性 需要分布式事务来保证。实际上CAP的正确性还是存在的:1、对于分区主副节点强一致共识算法只能做到日志级别,日志Apply到状态机有可能存在不一致,一些NewSQL提供通过直接读日志来构建一致性读,这个实现说实话有点重,对于客户端的响应时延会比较大,其实是在一定程度上牺牲了A,为了规避这个问题有些干脆副本节点不提供读操作。2、通常分区之间实现的分布式事务多采用2PC协议,经典2PC协议实际上是一个CP系统,同样牺牲了A。
论述NewSQL产品的话需要详细介绍一下Google Spanner ,很多NewSQL的后起之秀如开源CockroachDB以及国内的TiDB,包括蚂蚁金服的Oceanbase、阿里集团的XDB都在一定程度上借鉴了Spanner。
Google Spanner是谷歌研发的可横向扩展的、支持多版本的、可在全球范围进行分布式部署的、同步进行数据复制的分布式数据库。Spanner是世界上第一个可以在全球范围内进行数据分布式管理并且支持外部一致性(Linearizability_Consistency+Transaction_Serializable)分布式事务的数据库产品。实现这么多功能有很多技术点,有兴趣可以阅读下Spanner的paper,我们这里只探讨事务相关。
Spanner 对外提供了 read-only transaction 和 read-write transaction 两种事务。这些都需要 TrueTime这个大杀器作为前提,TrueTime 是一种 API,它允许 Google 数据中心内的任何机器以高精度(误差在几毫秒内)获悉准确的全球时间。这使得各种 Spanner 机器能够推断事务操作的顺序(并且该顺序与客户端观察到的顺序相匹配),且通常无需任何通信。Google 不得不为其数据中心配备特殊硬件(原子钟!)以使 TrueTime 发挥作用。由此产生的时间精度和准确度远高于其他协议(如 NTP)所能达到的时间精度和准确度。具体来说,Spanner 会为所有读写操作分配一个时间戳。时间戳T1处的事务可确保反映在T1之前发生的所有写入的结果。如果一台机器想要在T2处满足读取请求,它必须确保其数据视图至少在T2处保持最新状态。得益于 TrueTime 技术,这个解决方法通常成本低廉,其实蛮贵的。这里可能有人会有疑问,用一个全局序列号生成器如:Google 很早的另一篇paper 《Large-scale Incremental Processing Using Distributed Transactions and Notifications》中提到的Percolator 依赖的 全局授时服务TSO(Timestamp Oracle )不是更容易实现吗?实际上国内TiDB 、Oceanbase的 GTS都采用类似的实现。为啥还要这么麻烦的搞一个 TrueTime 出来?全局授时服务TSO虽然实现起来比较简单,但很容易看出来它是一个典型的单点服务,即使通过共识算法构建高可用会做一些 failover 的处理,仍然可能是整个系统的一个瓶颈,同时在多机房部署场景下也不可避免产生网络开销,这对于号称全球化部署的Spanner显然是不能接受的。
这里不再描述实现事务的具体细节,只来说明对比单机数据库会多出哪些时延开销!
单节点写入
这是由 Spanner 完成的写入中成本最低的一种
1、事务提交前需要将写入同步到副本多数派的时延(Paxos延迟)
2、主副本会等到能确定事务的时间戳已实时传递为止;这通常需要几毫秒,以避开 TrueTime 时间戳可能存在的不确定性。这样可以确保高度一致性:一旦客户端获知事务的结果,就可以保证所有其他读取者也能看到事务的效果。这个“提交等待”通常与上述步骤中的副本通信同时发生,因此它的实际延迟成本很低
多分片写入
除了单节点写入的开销外,还需要额外的跨分片协调即分布式事务处理(Paxos延迟+commit延迟)。
Spanner使用的是标准的2PC,虽然在 Paxos 上运行两阶段提交弱化了可用性问题,但依然存在性能问题。
标准2PC中全程至少2次RPC延迟(prepare+commit),和4次持久化数据延迟(prepare写日志+协调者状态持久化2次+commit写日志)
这里额外提一下TiDB 和Oceanbase!
TiDB使用的是 Percolator系统的2PC
Percolator 通过把事务的状态都下沉到存储中,简化了2PC中协调者的角色,只要primary record commit成功就返回客户端事务提交成功。这种实现优点就是commit延迟低,对写入事务友好。
但是也存在着诸多局限:
1、底层KV屏蔽了sharding细节,且不提供交户型的事务上下文机制,对存储引擎的读写只能在一次RPC提交,使得加锁、修改、提交都必须是一次bigtable的提交操作,延迟代价是巨大的。
2、尽管primary record的设计简化了2PC的协调者状态维护,但是commit阶段secondaries仍然要等待primary record持久化事务状态成功后,才能进行commit,这一次延迟不可避免。这就导致secondaries持有锁的时间变长,SI隔离级别下,单机读分布式事务参与者会等待更长的时间(不能读旧版本数据,不然就会出现幻读),单机写的锁冲突也会加剧。
3、commit延迟低带来的代价就是需要一种方案来处理运行中事务发生故障的时候遗留下来的锁,Percolator采用延迟处理来释放锁,这种方式虽然易于实现不过可能会将事务提交延迟数10s。
Oceanbase 在标准的2PC算法上做了优化,引入无状态协调者,参与者预提交,最终只需要1次日志延迟 + 2次 RPC 延迟。详见蚂蚁金服资深技术专家 颜然的演讲《Oceanbase事务引擎介绍》。Oceanbase 还在最新的2.0版本中提出了一个 分区组 的概念,用来规避多分片写入所引入的分布式事务。详见:蚂蚁金服技术专家梅庆的演讲《Oceanbase支撑2135亿成交额背后的技术原理》。有点像把传统的分库分表方案放在数据库内核中实现,但也会存在分库分表中热点数据倾斜的问题,有点伪分布式的感觉。
强读取(多节点)
由于读取操作可能会由给定分片的任意最新副本执行,对最新数据的“强”读取可能会因验证副本是否足够新而产生一些延迟时间(向主副本发出一次RPC);但只需过时数据即可的读取则无需付出这种延迟代价。
这里没有具体的数据做支撑,有兴趣的可以阅读下Spanner paper中的基准测试部分
以上可以看到:
第二种数据库产品有着不错的扩展性的同时也有号称比传统关系型数据库更佳的OLTP事务性能,目前看来对大部分读多写少的应用是个不错的方案。
第三种数据库产品NewSQL保障ACID事务相比传统的关系型数据库存在着非常多的性能开销且无法避免。事务中持有的锁会从事务开始直到多个节点写完日志,经历多次网络延迟、IO延迟,导致整体事务吞吐量降低,而NewSQL在一定程度上是通过扩展性来缓解。
以前都说数据库的性能瓶颈在磁盘IO,那么未来数据库性能瓶颈将是网络IO。
在2017年DTCC大会上阿里巴巴研究员张瑞的演讲《面向未来的数据库体系架构的思考》中提到以前对于OLTP系统数据库来说不允许有很高的时延,未来在数据库架构上很多物理时延不可避免,应用程序需要对相对高的数据库时延来做适配。正所谓英雄所见略同,Pingcap的CTO黄东旭在一篇博文 《十问 TiDB :关于架构设计的一些思考》中也强调了类似的看法“衡量一个分布式系统更有意义的指标是吞吐,这个观点我在很多文章里已经提到过,提高并发度,如果系统的吞吐能够随着集群机器数量线性提升,而且延迟是稳定的才有意义,而且这样才能有无限的提升空间。”
虽然两位大牛都这么说,不过目前应该很少有企业在自己的业务系统中使用分布式数据库处理事务。未来在分布式数据库中使用事务,你是否已经做好准备?
附生产级NewSQL所用的数据库技术
Notes
鉴于作者水平,难免有理解和描述上有疏漏或者错误的地方,欢迎共同交流;部分参考已经在正文和参考文献中列表注明,但仍有可能有疏漏的地方,有任何侵权或者不明确的地方,欢迎指出,必定及时更正或者删除;文章供于学习交流,转载注明出处