分布式事务与数据分区(二)|学习笔记

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云原生数据库 PolarDB 分布式版,标准版 2核8GB
简介: 快速学习分布式事务与数据分区(二)

开发者学堂课程【PolarDB-X 开源系列课程:分布式事务与数据分区(二)】学习笔记与课程紧密联系,让用户快速学习知识

课程地址https://developer.aliyun.com/learning/course/1032/detail/15162


分布式事务与数据分区(二)


三、数据分区

1.分区键

在分布式事务之后,进入到数据分区这一块的内容,关于数据分区其实想说,当采用了 shared-nothing 架构,那么将数据全部按照分区键进行分区之后,其实对数据的查询是有一定影响的,接下来要讨论的就是这个影响是什么以及解决方案是什么?考虑有这样一张按照主件 ID 作为分区件的这样的一张表,就是图中的例子,那么当提供查询是 ID 上的等值条件的时候。例如 ID = 14,计算出 ID = 14的 Hash 值,就可以知道数据落在P3这个分区上。

 image.png

2.二级索引

但问题是如果要按照内部的条件来查该怎么办?粗略看起来,好像只能把所有的分区都扫描一遍,这样其实肉眼可见的导致代价变高。为了找到答案,先看一下单击数据库是如何解决问题,在 MYSQL 同样的表结构,实际上是一棵 B+树,那么这棵 B+树是按照主建 ID 有序的,当按照 ID 进行查询时,实际上数据会在 B+树上做二分查找,从而快速的定位到数据所在的一个子节点。同样当以内部为条件进行查询时,肯定法做二分查找了只能从头到尾将整棵 B+树都变一变,这个代价是比较高的,其专门的名字叫做全表扫描

那么在 MYSQL 中,为了避免全表扫描,通常会在内创建一个索引,那么索引其实就是创建了另外一棵 B+数,这棵 B+树是按照 name 进行排序的。那么在它的所有的业务节点当中,包含了 name 对应的组件 ID 的值。对于 name 上的等值查询,数据库会先在二级索引上进行二维码,进行在这棵 B+树上进行一个二分查找找到对应的 ID 之后,再用这个 ID,回到主键的 B+树上的进行查找,这就是常说的回绕操作二级索引的思路属于计算机里面比较常见的思路。它就是一个典型的空间换时间的思路,在 MYSQL 中设计主键时,由于有索引的存在,设计主键时,就通常只需要考虑它在业务上是唯一的并且不存在浪值

并不用太多的在意,主键是不是包含在所有的查询条件当中其实就是因为 MYSQL 上创建索引的成本非常低,就是无论查询里面有什么条件,都可以按照条件去创建索引。所以这个可能也成为了使用单机数据库的固定思路

3.全局二级索引

但是回到分布式数据库,在按照主键拆分之后,如何按照 name 列进行查询,还能避免全表扫描?那参考前面空间换时间的思路,以 name 为分区间,再将数据做一份冗余,然后将 name 映射到它的主键 ID

采用这种思路也可以达到前面索引的同样的效果这块一个数据,就称它为全局二级索引,这里的全局的意思其实是说,索引的数据和主表的数据并没有保存在同一个分片上,它通常是因为拆分维度不同,一个使用 name 拆,个使用 ID,它通常会保存在不同的分区上有了这个全区二级索引之后,当按照 name = Megan 进行查询的时候,也是先通过全二级索引,定位到 Megan 所在的分片,然后找到 Megan  ID 值,拿着 ID 的值到主表上,查到整条记录所在的分区这样的过程,其实和单机上是一样的,也称为回表。

那这时候发现如果把分布式数据库上创建全局二级索引的代价,使用体验做的和单机数据库做成比较接近的,那其实也可以像使用单机数据库的时候一样去不太关心分区间这样的内容,所以如何去实现全局索引也是一个就是非常关键的点,那在介绍全局索引实现的一些细节之前,首先通过一个全局索引来优化查询性能的例子,来实际感受一下,现在的 PolarDB-X 上的全局二级索引大概做成了什么样子。

4.通过全局索引优化查询性能

登录 PolarDB-X 数据库实例,创建测试使用的数据库,然后使用 sysbench 脚本创建测试使用的表和填充数据。测试脚本经过修改会显示使用的建表语句,可以看到是采用 MYSQL 原生语法的建表语句。

查看建表结果,默认以主键 ID 作为分区键,表包含16个分区表中包含1万条数据,均匀分布在16个分片上。接下来进行查询测试,首先是主键上的点查查询当中包含了主键上的等值条件可以看到查询性能非常不错,通过 explain 语法查看实行计划,由于包含了分区件上的等值条件,什么查询可以路由到具体的分辨上所以查询性能非常好,如果不包含分区件上的等值条件会是一个什么情况?第二个测试使用 k 的条件作为查询条件,这里将 QPS 日志导入到一个文件当中,可以在网页上查看,可以看到 QPS 只有2500,再来看执行计划可以看到由于没有包含分区键上的条件,必须要进行全表扫描,也就是需要扫描全部16个分件才能得到查询结果。那么如何优化这样的一条语句?

可以通过 PolarDB-X 提供的查询建议工具来看一下查询建议,这里得到的优化建议是通过增加索引来提升查询性能,同时给出了增加索引的语句,增加索引过程是 online schema change,不会阻塞查询的执行,可以看到索引加上之后,整个查询的性能得到了很大的提升。再看此时的查询计划,可以发现这里的 BKAJoin 代表了回操作,整个查询的执行过程是首先通过索引上的拆分条件K确定位到索引的具体分片,然后从中查到需要返回的行,然后再回到主表上,根据主键得到所有的拆分结果

那么对于这样的回操作,还可以通过高 DBS 提供的聚簇索引来进一步优化。聚簇索引可以保证索引表和主表始终保持相同的结构,也就是可以通过索引扫描来完全代替主表扫描。可以看到增加聚簇索引之后,整个查询性能又得到了很大的提升,基本上已经达到了最初的2500的十倍。再看一下此时的查询计划,可以发现直接通过索引扫描就能达到最终结果并且整个查询只需要扫描一个索引上的经过两次增加索引之后,再看一下现在的表结构,缺失之前的操作包含一个聚簇索引和一个二级索引,那么通过详细模式再查看实际上聚簇索引和二级索引都是全局索引它们各自包含16个分片。以上是通过二级索引和聚簇索引,优化查询的方法

如果用户可以确定自己的业务场景主要是以 K 作为查询条件的话,还可以通过变更分区间的方法来提升查询性能。具体操作如下:首先删除之前创建的全局索引可以看到查询性能立即恢复到了最初的2500,然后通过分区变更语法,将分区键调整K ,可以看到调整分区之后,查询性能又再次得到了一个很大的提升,查看一下此时的结构。表中只包含了一个分区K 上的局部索引,并且包含了16个分片再看一下时的查询计划,和一开始主键拆分的情况比较类似,也是直接将查询下发到一个具体分片获得比较好的性能

image.png

上面是通过全局索引来优化查询性能的一个例,大家在看例的时候,注意左上角的这个曲线,它通过首先增加要回表的全局索引,然后提升了一定的查询性能,接下来又增加了一个全局的聚簇索引,全剧聚簇索引的区别,就是聚簇索引不需要回表然后进一步的提升到更高的性能,接下来把两个索引都删除掉,这个查询系统又回到了一开始的情况,然后再通过 online schema change 调整了这张表的拆分键,使得它的拆分键和查询条件能够对上,那么这个查询键又再次回到了比较高的过程

通过这个演示,基本上可以确定,通过全局索引来优化查询性能,其实满足的几个特点第一数据是准确的。第二整个键索引和调整索引的过程都是 online 的,因为整个键索引产品,通过跑的 schema change 流量,然后去操作索引看到了这一点

接下来会具体展开看看,在设计全局索引以及设计分区方式的时候,还有考虑了哪些细节

这里先回答几个简单简单的问题:

一个分片是一台 ECS 吗?这个不一定,因为分片本身是相对独立的概念,可以多个分片落在同一台 DN 上,也可以一个分片落在一个 DN 上,这个没有强制绑定

一个表可以有多个聚簇索引嘛?可以的,通过 DPS 的设计是可以有多个聚簇索引的。

5.全局索引与单机索引的兼容性

然后进入正题,对于全局索引来说,对它的最高评价其实就是如果能够使用体验和MYSQL当中的索引一样,其实已经是一个非常好的场景了。那么具体体现在哪些点上?就是这里列出了六个点:

第一个是一致性,MYSQL 当中的索引肯定是强一致的,主表上数据索与索引表数据相一致,不会有延迟不一致。对于设计全局索引的话,如果采用了最终一致弱一致,或者的一致,那其实都不是非常透明的数据,就是用户在使用索引时,需要思考很多问题,其实不是好的选择,那么 PolarDB-X 的全局索引采取的就是强一致的方案,因为 PolarDB-X 支持强一致的分布式事物,可以通过分布式事物来保证主表与所引导的数据强一致。那么接下来的话,接着看创建方式和使用方式,以及对其他 DD L的兼容性,还有对前缀查询以及 Big Key 的兼容性。

6.索引创建-Online Schema Change

首先是创业方式,这里看看如何来创建全局索引?从刚刚的演示上就知道创建过程中是不会阻塞用户的读写的,那么在分布式数据中,要实现Online Schema Change的一个主要的挑战是,Schema 变更过程中,不同的 CN 节点上的事务看到原数据的版本可能是不一致的,这个点怎么理解?回想在一开始说过 PolarDB-X 采取了存储计算分离的这样架构,那么 CN 节点和 DN 节点是独立的。CN 节点同时因为它是状态可以水平扩展,有很多的 CN 节点,那么当要让 CN 节点去加载原数据的话,由于网络延迟或者说通知延迟先后顺序,不同CN 加载新版本原数据多少是存在时间差的。

 image.png

这个时间可能很细微,但是可能带来数据不一致的风险。这里举一个索引的例子,比如现在图上列了两个 CN,就管左侧的 CN 叫做 CN0右侧的 CN 叫做 CN1考虑现在再添加一个全局索引,然后这个索引,基本的物理结构已经准备完毕,要去通知这个 CN 节点,在新来的读写请求,都需要考虑维护索引的数据一致,这时候因为通知的先后区别,可能 CN0已经知道全局索引的存在,但是 CN1还不知道全局索引的存在那此时比如 CN0上收到了 inter 的请求,那么由于知道全局索引的存在,会把数据在主表上写一条,在索引表上也写一条,这是没问题的

问题出现在 CN1并不知道有全局索引,如果这时候又收到了对数据的 delete 操作,这个时候就有问题了,因为不知道全局索引存在,所以只会删主表上的数据,不会去删索引表这个时候就相当于索引表上比主表多一条数,这其实是就导致数据不一致这其实就不符合对索引表的强一致这样的要求。所以同样的问题其实对单机数据库也存在单机数据库,解决这个问题的办法是通过对原数据加锁来保证任意时刻所有事物都只能看到一个原数据版本,套在 MYSQL 上,就是比较熟悉的 MDL 

当要去加索引时,在加索引换 meta过程中会使用 MDL 锁使得变更原数据操作是在使用老版本原数据后,所有事物结束之后才去进行的那分布式系统能不能采用这样的方式?其实是不可以的,原因是由于网络延迟存在不确定性,要实现一个跨节点的 CN锁,要在每个 CN 上都取一把 MDL 锁,有可能会由于网络延迟导致非常明显的读写卡顿,甚至有可能这个锁由于部分 CN 没有响应,迟迟不能完成枷锁的动作有可能因为在10个 CN1个响应卡顿,导致其他9个都已经持有锁了,但是就是不能进入下一步的切换和解锁过程所以这个在分布式系统当中,不能采取一把大锁的方式来实现,因为它不太符合 online 的定义

PolarDB-X 实现这种 Online Schema Change 是参考了 Google f1的实现。其出过一篇叫做 online,asynchronous schema change in F1这样一篇介绍自己在 F1中实现论文,具体的做法会稍微复杂一点,可以参考之后的公众号。

这里简单说就是通过增加了两个相互兼容的中间状态,使得系统当中允许同时存在两个原数据版本,那使得 DDL 过程无需加速,不会阻塞读写请求,也就是做法是破坏了要求整个系统当中只有一个原数据版本这样的前提条件它允许有两个这样的话,像刚刚 CN0拿到了新版本,CN1还在上一个版本的这种情况如果允许这种情况出现,那其实就没有问题了有了 Online Schema Change 的支撑,PolarDB-X 中可以非常轻松的使用create index 语句来创建全局索引,不需要依赖任何的第三方组件

image.png

7.索引变更-DDL 兼容性

接下来索引创建出来其实在平时的使用当中,对于整个表的变更,也是可能会涉及到对索引的影响。那么其实在各种 DDL 操作当中,维护索引表的一致也是一项工作量比较大的内容,PolarDB-X 在这块非常细致的对各种 DDL 都设计了无化的 online DDL 实现,根据 DDL 的类型可以自动选择合适的执行方式,并且能够自动的维护全局索引。基本虽然使用了全局索引,但在 DDL 操作方面没有太多的限制。这也是为了向单机数据库使用索引的体验来靠近的努力。

+image.png

8.索引选择-CBO

所以创建完成后,使用索引的时候,还需要让它自动的能应用在查询里这个做法其实有有手动和自动两种,手动的话可能用过 bicycle的可以了解有 false index 或者 user index 这样的方法,但是如果要求每一条查询都手动指定索引,肯定不是一个非常好的体验所以 PolarDB-X 也支持基于 CBO 的自动索引选择,那么在实现上,由于索引本身也是一张表,并且回表操作实际上是可以表达为索引表和主表在主键上去做 Join ,那这样其实在工程实现上可以大部分的复用分区表上的估算执行计划代价的方法来估算索引扫描或者说索引扫描加回表的执行计划代价,这个难点在于如果两个表说作 Join ,那么并且两个表,各自都有自己的全局索引,那么它产生的执行计划空间和两个表并没有任何全局索引是有很大差异的,最直观的差异就是它的执行计划空间会非常大,所以这时候就 cpu 有更合理的枚举算法,更合理的减值算法,以及本身的性能要更好

image.png

那么 PolarDB-X 在这方面也做了多优化,详细的内容可以关注公众号,里面有有一些介绍的文章那么总结成一句话,就是 PolarDB-X 支持基于代价的优化器,并且能够自动完成索引选择。

9.前缀索引

前面说了索引的创建,以及如何选择接下来要看看分区算法本身在设计上有哪些细节的思考,首先是希望索引能够支持前缀查询,那这就要求分区算法本身能够提供这样的支持就是当把结构上创建多列的索引时,其实是把每一列索引的数据按顺序的拼在一起。所以当只有前面几列的数据时,依然可以拼出一个前缀然后在树上查找。那么如果分区算法默认采取的是一次性 Hash的这样的算法,那么如果不做一些特殊处理,依然是把所有数据拼在一起,然后做整个 Hash的话,肯定是没有办法做前缀查询的因为一部分数据的 Hash 和整个所有列的数据 Hash 肯定不一样那这个地方做的设计就是会分别的对每个列做 Hash,这样在匹配到前缀的情况下,一样可以做分区裁减相当于可以做前缀的查询

image.png

10.Big Key

接下来这个可能也是大家会比较容易想到的点,就是每个索引其实相当于对数据换了一个拆分维度,那随着拆分维度越来越多,总可能有那么一两个维度,它会出现一些这个数据的热点,那这情况下该如何去解决?

这里把这个热点定义为一种叫做 Big key 的东西。这个这个 Big key 怎么理解?就是可能有某个拆分维度下某一个键值行数非常多,或者说写入量非常大,这个就称为 Big key,那么通常数据量其实不太会成为系统瓶颈,因为要想成为系统瓶颈,可能是单一的 key 的数据量就超过了一个单分片的容量,那可能导致确实是存不下,但现在单个分片上的容量通常都在 TB 级别,所以就单一 key 超过 TB 的这样的数据量是比较罕见的。

那么更常见的是写入方面的热点,在单击数据库当中,这个建索引其实不太需要考虑热点的问题,就比如说在性别的列上去建索引,那这个索引其实区分度很低,因为它只有两个值写入的时候,确实可能会有一定的争抢但不会存在太大的问题但如果是分布式数据库,由于如果索引只有两个值,进行 Hash,其实只落在两个分区上,那其实如果业务写入量比较大的话,相当于所有的写入压力都压入了这两个分区上,那这个其实就导致整个数据库不再有水平扩展能力,因为不管怎么压最后这两个分区肯定会首先成为瓶颈,因此这个是需要解决这种,写入热点的 Big key 问题。PolarDB-X 是通过将主键和所有的 key 一起作为分区线来这解这个问题的。也就是当热点出现的时候,还能够按照主键去对写入热点的分区做进一步的分裂,那从而达到消除热点。

image.png

11.Join 下推

解决完 Big Key 的话,基本索引体使用体验已经和单机数据库非常的接近了,那接下来还有一个具体的场景,是跟 PolarDB-X 本身对查询处理的内容是比较相关的,就是PolarDB-X 这个分布式数据库和别的分布式数据库不太一样的地方,就是能支持 Join 操作下推到存储节点,那下推 Join  的话,其实本身是对于 TP 类的业务是可能是存在很大有相对很大收益的场景,因为 TP 类的业务,通常不像 ap 那样,Join 的数据越 Join 越多,通常是 inner Join 居多,然后 Join 之后的结果肯定是会小到两个数量级这样的情况。那如果能在 DNA 上提前的把 Join 执行掉,那么返回的数据就会少很多,降低了网络的开销而分布数据库里网络开销,Io 开销其实通常都是部分的,所以下推 Join 是有比较大的收益的但是下推 Join 其实是有一定的前提条件要求的就是要求拆分算法和分区分布必须是完全相同的两张表做 Join,一张表一张索引表或者两张索引表,只有当他们的拆分算法和分区分布完全相同的时候,才支持将 Join 下推到存储节点上。那么如果因为 PolarDB-X 是支持分区分裂和迁移的,如果不对分区操作做任何限制,就可能会出现由于分区分裂合并或者分区迁移导致两张表的分布变得不同进而导致 Join 无法下推的情况。

12.table group  partition group

从用户的角度来看,就可能是出现一种,当分区迁移之后反而导致查询性能下降的一个令人困惑的场景。为了解决这个问题?PolarDB-X 是在后台引入了两个新的概念叫做 table group  partition groupTable group 是一组 global index 的合集,就是一组全局索引的合集,系统会保证同一个 table group 中的索引,具备相同的数据分布,当发生这个分区的分类合并时,同一个 table group 中的所有的全局索引会一起进行分类合并

首先保证了数据的分区数量是一致的,接下来还需要有Partition group这样的概念,这也就是在同一个 table group 中的 global index,相同区间的分组就比如都落在一号分区这样的分组,认为它是 partition group,那么当 PolarDB-X 自动的将分区在不同 DN 之间迁移的时候,会保证同一个 partition group 当中的分区始终落在同一个 DN 上,通过这样的情况,就可以保证,同一个 table group 内所有的全区索引的分区数量是一样的。并且这个序号相同的分区都是落在相同的 DN 上,这样的话就可以保证 Join 操作还是可以下推的

image.png

稍微总结这个 table group  partition group,就是通过合理的去划分 table group,首先 table group 是默认有一个划分方法,也可以通过根据实际使用,就是比如能确定哪些表需要去做 Join 下推也可以主攻的去划分 table group,通过合理的规划 table group 可以降低分区迁移操作带来的代价,那靠 PolarDB-X 使用了 table group  partition group 就是用来解决全局缩影的 Join 优化,避免前分区变更操作,导致Join 优化失效。

相关实践学习
快速体验PolarDB开源数据库
本实验环境已内置PostgreSQL数据库以及PolarDB开源数据库:PolarDB PostgreSQL版和PolarDB分布式版,支持一键拉起使用,方便各位开发者学习使用。
相关文章
|
9天前
|
存储 缓存 算法
分布式缓存有哪些常用的数据分片算法?
【10月更文挑战第25天】在实际应用中,需要根据具体的业务需求、数据特征以及系统的可扩展性要求等因素综合考虑,选择合适的数据分片算法,以实现分布式缓存的高效运行和数据的合理分布。
|
16天前
|
JSON 分布式计算 前端开发
前端的全栈之路Meteor篇(七):轻量的NoSql分布式数据协议同步协议DDP深度剖析
本文深入探讨了DDP(Distributed Data Protocol)协议,这是一种在Meteor框架中广泛使用的发布/订阅协议,支持实时数据同步。文章详细介绍了DDP的主要特点、消息类型、协议流程及其在Meteor中的应用,包括实时数据同步、用户界面响应、分布式计算、多客户端协作和离线支持等。通过学习DDP,开发者可以构建响应迅速、适应性强的现代Web应用。
|
3月前
|
数据采集 分布式计算 并行计算
Dask与Pandas:无缝迁移至分布式数据框架
【8月更文第29天】Pandas 是 Python 社区中最受欢迎的数据分析库之一,它提供了高效且易于使用的数据结构,如 DataFrame 和 Series,以及大量的数据分析功能。然而,随着数据集规模的增大,单机上的 Pandas 开始显现出性能瓶颈。这时,Dask 就成为了一个很好的解决方案,它能够利用多核 CPU 和多台机器进行分布式计算,从而有效地处理大规模数据集。
174 1
|
3月前
|
运维 安全 Cloud Native
核心系统转型问题之分布式数据库和数据访问中间件协作如何解决
核心系统转型问题之分布式数据库和数据访问中间件协作如何解决
|
4月前
|
canal 缓存 NoSQL
Redis常见面试题(一):Redis使用场景,缓存、分布式锁;缓存穿透、缓存击穿、缓存雪崩;双写一致,Canal,Redis持久化,数据过期策略,数据淘汰策略
Redis使用场景,缓存、分布式锁;缓存穿透、缓存击穿、缓存雪崩;先删除缓存还是先修改数据库,双写一致,Canal,Redis持久化,数据过期策略,数据淘汰策略
Redis常见面试题(一):Redis使用场景,缓存、分布式锁;缓存穿透、缓存击穿、缓存雪崩;双写一致,Canal,Redis持久化,数据过期策略,数据淘汰策略
EMQ
|
4月前
|
传感器 人工智能 安全
EMQX 与 MQTT: AI 大模型时代的分布式数据中枢
在以数据为核心的 AI 时代,基于 MQTT 协议的消息服务器 EMQX 能帮助企业更好的利用人工智能和机器学习模型,是智能化系统中核心的数据基础软件。
EMQ
224 19
|
3月前
|
Java 数据库连接 微服务
揭秘微服务架构下的数据魔方:Hibernate如何玩转分布式持久化,实现秒级响应的秘密武器?
【8月更文挑战第31天】微服务架构通过将系统拆分成独立服务,提升了可维护性和扩展性,但也带来了数据一致性和事务管理等挑战。Hibernate 作为强大的 ORM 工具,在微服务中发挥关键作用,通过二级缓存和分布式事务支持,简化了对象关系映射,并提供了有效的持久化策略。其二级缓存机制减少数据库访问,提升性能;支持 JTA 保证跨服务事务一致性;乐观锁机制解决并发数据冲突。合理配置 Hibernate 可助力构建高效稳定的分布式系统。
64 0
|
3月前
|
监控 中间件
分布式链路监控系统问题之当某个Segment数据缺失时还原调用树的问题如何解决
分布式链路监控系统问题之当某个Segment数据缺失时还原调用树的问题如何解决
|
3月前
|
监控 Java 中间件
分布式链路监控系统问题之当某个Segment数据缺失的问题如何解决
分布式链路监控系统问题之当某个Segment数据缺失的问题如何解决
|
3月前
|
消息中间件 存储 监控
消息队列在分布式系统中如何保证数据的一致性和顺序?
消息队列在分布式系统中如何保证数据的一致性和顺序?