01、Sharding-Sphere 实战:介绍
随着版本不断更迭,ShardingSphere的核心功能也变得多元化起来。最开始Sharding-JDBC 1.x版本只有数据分片功能,到Sharding-JDBC 2.x版本开始支持数据库治理,如注册中心、配置中心等,再到3.x版本推出了Proxy产品,还增加了分布式事务,支持Atomikos、Narayana、Bitronix、Seata,4.x为Apache下的第一个版本,支持了更多种类的数据库,如今已经迭代到5.x版本。
现在的ShardingSphere不单是指某个框架,而是一个生态圈,这个生态圈由JDBC、Proxy和Sidecar三款开源的分布式数据库中间件所构成。
一、分库分表
分库分表是所有ShardingSphere产品中最为经典、成熟,也是使用最多的功能。本节我们就先来了解一下分库分表技术。
顾名思义,分库分表的字面意思很好理解:分库就是把单个数据库拆分成多个数据库,将数据分散存储在多个数据库中的过程;分表就是把单张表拆分成多张表,将数据分散存储在多张表内的过程。
1. 为什么需要分库分表
总的来说,分库分表是为了提升性能、增加可用性。
(1)突破性能瓶颈
随着单库中的数据量越来越大、数据库查询的QPS越来越高,数据库读写所需要的时间也越来越多,数据库可能会成为业务发展的瓶颈。对应的就需要做数据库性能方面的优化。当采取了服务器配置、数据库分区、读写分离、数据缓存等常规优化手段后仍不能满足性能要求时,就需要考虑通过分库来分担单个数据库的压力。比如,如果读写QPS为3500,假设单库可以支撑1000个连接,那么就可以考虑分成4个库,分散查询连接和存储磁盘等资源压力,提高总体并发能力。
如果单表数据量过大,当数据量超过一定量级后,无论是查询还是更新,在经过添加索引等纯数据库层面的传统优化手段之后,还是可能存在性能问题。这时候就需要去换个思路来解决问题。比如从数据生产源头、数据处理源头入手,既然数据量很大,那我们就来个分而治之,化整为零。这就产生了分表,把数据按照一定的规则分成多张表,突破单表环境下的数据存取性能瓶颈。
如果表的数据量超过一千万行,即使SQL使用了索引,查询也是会明显变慢。这是因为索引一般是B+树结构,数据千万级别的话,B+树的高度会增高,查询就会变慢。这里顺便复习一下如何计算B+树的高度,以MySQL InnoDB为例。InnoDB存储引擎最小储存单元是页,一页大小固定是16KB,使用该引擎的表为索引组织表。B+树叶子存的是数据,内部节点存的是键值和指针。索引组织表通过非叶子节点的二分查找法以及指针确定数据在哪个页中,进而再去数据页中找到需要的数据。B+树结构如下图所示。
如果B+树的高度为2,即有一个根节点和若干个叶子节点,则这棵B+树的存放总记录数为:根节点指针数 * 单个叶子节点记录行数。
假设一行记录的数据大小为1KB,那么单个叶子节点可以存的记录数 =16KB/1KB =16。非叶子节点内可以存放多少指针呢?假设主键ID为bigint类型,长度为8字节,而指针大小在InnoDB源码中设置为6字节,所以就是一个键值指针占用8+6=14字节,一个内部节点中存储的指针个数为 16KB/14B = 16 * 1024B / 14B = 1170。因此,一棵高度为2的B+树,能存放 1170 * 16 = 18720 条这样的数据记录。同理一棵高度为3的B+树,能存放 1170 *1170 *16 = 21902400,大概两千万左右的记录。
B+树高度一般为1-3层,如果到了4层,查询时会增加查磁盘的次数,数据寻找就会变慢。因此如果单表数据量太大,SQL查询变慢,就需要考虑分表了。
(2)提高可用性
单个数据库如果发生意外,很可能会丢失所有数据。尤其是云时代,很多数据库跑在虚拟机上,如果虚拟机/宿主机发生意外,可能造成无法挽回的损失。因此,除了传统的Master-Slave、Master-Master等部署层面解决可用性问题的方案外,我们也可以考虑从数据分片层面在一定程度上解决此问题。
此处以数据库宕机为例。单库部署情况下,如果数据库宕机,那么故障影响就是100%,而且恢复可能耗时很长。如果拆分成两个库,分别部署在不同的机器上,此时其中一个库宕机,那么故障影响就是50%,还有50%的数据可以继续服务。如果拆分成四个库,分别部署在不同的机器上,此时其中一个库宕机,那么故障影响就是25%,还有75%的数据可以继续服务,恢复耗时也会相对较短。当然也不能无限制的分库,这也是牺牲存储资源来提升性能和可用性的方式,毕竟资源总是有限的。
2. 什么时候考虑分库分表
注意,分库和分表是两件事。可以只分库,在不同的库中有一些相同结构的同名表,即多库单表;也可以只分表,一张表在一个库中有多个不同名的表,即单库多表;当然也可以同时既分库又分表,即多库多表,总之依具体需求而定。通常分库分表后预期达到什么效果是:分库后可承受的并发量增加,服务器得以扩充,磁盘容量等资源不再紧张;分表后单表数据量变少,SQL 执行速度提升。
如果你的系统处于快速发展时期,每天的订单流水都新增几十万或更多记录,并且订单表的查询效率明显变慢时,就需要规划分库分表了。如前所述,一般B+树索引高度为2~3层最佳,如果数据量在几千万级别,当高度变成4层,数据读写操作就会显著变慢。
3. 如何分库分表
根据行业惯例,通常按照水平或垂直两种方式进行数据分片。当然,有些复杂业务场景也可能选择两者结合的方式。
(1)水平拆分与垂直拆分
水平拆分是一种横向按业务维度拆分的方式,比如常见的按用户维度拆分,根据一定的规则把不同用户相关的数据分散在不同库表中。如果业务场景决定都是从用户视角进行数据读写,就可以选择按照水平方式进行用户数据分库分表。
垂直拆分可以简单理解为,把一张表的不同字段拆分到不同的表中。例如,假设有个小型电商业务,把一个订单相关的商品信息、买卖家信息、支付信息都放在一张大表里。可以考虑通过垂直拆分的方式,把商品信息、买家信息、卖家信息、支付信息都单独拆分成独立的表,并通过订单号与订单基本信息关联起来。还有一种情况,如果一张表有10个字段,其中只有3个字段需要频繁修改,那么就可以考虑把这3个字段拆分到子表,避免在更新这3个数据时,影响到其余7个字段的查询行锁定。
如果真的采用分库分表的话,我个人认为还是更专注水平拆分。而垂直拆分,一般在定需求的时候就已经划分好了。
(2)水平分库分表策略
常用的水平分库分表策略一般有range、hash取模、range+hash取模混合三种,分别用于不同场景。
range,即范围分片策略。比如我们可以将表的主键,按照每1000万的划分为一个表。有时候也可以按时间范围来划分,如不同年月的订单放到不同的表,这也是一种range划分策略。范围分片策略有利于扩容,因为不需要数据迁移。假设数据量增加到5千万,只需要水平增加一张表即可,之前0~4000万的数据不需要迁移。但这种方案会有数据热点,例如订单id一直递增,也就是说最近一段时间的数据都存储在一张表里。比如最近一个月的订单都在1000万~2000万之间,平时用户一般查最近一个月的订单比较多,都请求到order_1表,这就导致了数据热点问题。
hash取模分片策略是对指定的分片键hash后再对分表总数进行取模,把数据分散到各个表中。比如把原始订单表分成4张分表,id列作为分片键。对于给定id行的存储表为hash(id)%4,即把数据分存储到t_order_0 - t_order_3四张表中。这种方案的优点是不会存在明显的数据热点,缺点是弹性伸缩(缩扩容)需要做数据迁移。如从4张表扩容到8张表,那之前id=5的数据存储在t_order_1表中(5%4=1),现在应该放到t_order_5(5%8=5),也就是说历史数据要做迁移。
既然range存在热点数据问题,hash取模扩容迁移数据比较困难,我们可以把两种方案综合在一起取长补短,即range+hash取模混合分片策略。比较简单的做法就是,分库时用range范围方案,比如订单id在0~4000万的区间,划分为订单库1;id在4000万~8000万的数据,划分到订单库2。将来扩容时,id在8000万~1.2亿的数据,划分到订单库3。然后在每个订单库内,再用hash取模的策略,把不同订单数据划分到不同的表中。
(3)分成多少库多少表
分库的时候主要考虑业务峰值读写QPS和并发数。根据实际业务场景,可以根据历史QPS等数据进行评估。假设只需要3500个数据库连接,如果单库可以承担最高1000个数据库连接,那么就可以拆分成四个库。
分表数量依据业务数据量进行估算。假设一个表平均每天产生20万条数据,如果系统设计使用年限3年,那么该表总的数据量约为 365 * 20 * 3 = 2.19亿,如果每张表的数据量限定为1000万行,则需要21.9张表,再考虑业务高峰余量,可以按32张表来分片。
(4)数据倾斜问题
一个良好的分库分表方案,它的数据应该是比较均匀地分散在各个库表中。如果设计不当,很容易遇到以下类似问题:
- 某个数据库实例中,部分表的数据很多,而其他表中的数据却寥寥无几,业务上的表现经常是延迟忽高忽低,飘忽不定。
- 数据库集群中,部分集群的磁盘使用增长特别块,而部分集群的磁盘增长却很缓慢,每个库的增长幅度相差很大。这种情况会给后续的扩容带来步调不一致,无法统一操作的问题。
可以定义分库分表最大数据偏斜率为:(数据量最大样本 - 数据量最小样本)/ 数据量最小样本。一般来说,最大数据偏斜率在5%以内是可以接受的,如下图所示。
4. 分库分表导致的复杂性
分库分表的确解决了很多问题,但是也给系统带来了复杂性。
(1)透明路由
虽然数据分片解决了性能、可用性以及单点备份恢复等问题,但分布式的架构在获得收益的同时,也引入了新的问题。面对如此散乱的分片之后的数据,应用开发工程师和数据库管理员对数据库的操作变得异常繁重就是其中的重要挑战之一。
理想情况是用户只要按需求定义好分片规则,而在访问数据时不需要知道从哪个具体的物理库表中获取。这个数据路由的过程应该对用户尽量透明。
(2)跨实例关联查询
在单库未拆分之前,我们可以很方便地使用join操作关联多张表查询数据,但是经过分库分表后,关联表可能不在一个数据库实例中,如何使用join呢?通常有以下几种解决方案。
- 数据复制:将需要关联的表通过数据库提供的复制机制,整合到同一个实例中。
- 字段冗余:把需要关联的字段放入主表中,避免join操作。
- 数据抽象:通过ETL工具将数据汇总聚合,生成新表。
- 全局表:把一些基础表在每个数据库中都放一份。
- 应用层组装:将基础数据查出来(即所谓两次查询),通过应用程序计算组装。
(3)分布式事务
单数据库可以用本地事务,使用多数据库就只能通过分布式事务解决了。常用解决方案有两阶段提交(2PC)和柔性事务(BASE)等。
(4)排序、分页、函数计算问题
在使用SQL时order by、limit等关键字和聚合函数需要特殊处理。一般来说采用分片的思路,先在每个分片上执行相应的排序和函数,然后将各个分片的结果集进行汇总并再次计算,得到最终结果。
(5)分布式ID
数据库被切分后,不能再依赖数据库自身的自增主键生成机制,因为多实例之间不感知彼此的ID,会出现ID重复。常用的分布式ID解决方案有:UUID、基于数据库自增单独维护一张全局ID表、互斥号段模式、Redis单线程自增、雪花算法(Snowflake)等。
(6)多数据源
分库分表之后面临同时从多个数据库实例的库表中获取数据,一般的解决思路有客户端适配或代理层(中间件)适配。
5. 分库分表中间件简介
目前可见的分库分表中间件比较多,如下图所示。其中Sharding Sphere与Mycat两种比较流行。
二、ShardingSphere产品路线
ShardingSphere由JDBC、Proxy和Sidecar(规划中)这三款既能够独立部署,又支持混合部署配合使用的产品组成。它们均提供标准化的基于数据库作为存储节点的增量功能,可适用于Java同构、异构语言、云原生等多样化的应用场景。
关系型数据库当今依然占有巨大市场份额,是企业核心系统的基石,未来也难于撼动。因此ShardingSphere更加注重在原有数据库基础上提供增量,而非颠覆。
1. ShardingSphere-JDBC
ShardingSphere-JDBC定位为轻量级Java框架,在Java的JDBC层提供额外服务。它使用客户端直连数据库,以jar包形式提供服务,无需其他部署和依赖,可理解为增强版的JDBC驱动。ShardingSphere-JDBC使用方式如下图所示。
ShardingSphere-JDBC完全兼容JDBC和各种ORM框架,如JPA、Hibernate、Mybatis、Spring JDBC Template,或直接使用JDBC;支持任何第三方的数据库连接池,如DBCP、C3P0、BoneCP、HikariCP等;支持任意实现JDBC规范的数据库,目前支持MySQL、PostgreSQL、Oracle、SQLServer,以及任何可使用JDBC访问的数据库。
2. ShardingSphere-Proxy
ShardingSphere-Proxy定位为透明化的数据库代理,提供封装了数据库二进制协议的服务端版本,用于完成对异构语言的支持。目前提供MySQL和PostgreSQL(兼容openGauss等基于PostgreSQL的数据库)版本,可以使用任何兼容MySQL/PostgreSQL协议的客户端,如MySQL Command Client、MySQL Workbench、Navicat等操作数据,对DBA更加友好。
ShardingSphere-Proxy对应用程序完全透明,可直接当做MySQL/PostgreSQL使用,如下图所示。
3. ShardingSphere-Sidecar(规划中)
ShardingSphere-Sidecar定位为Kubernetes的云原生数据库代理,以Sidecar的形式代理所有对数据库的访问。通过无中心、零侵入的方案提供与数据库交互的啮合层,即Database Mesh,又可称数据库网格。
Database Mesh的关注重点在于如何将分布式的数据访问应用与数据库有机串联起来,它更加关注的是交互,将杂乱无章的应用与数据库之间的交互进行有效地梳理。使用Database Mesh,访问数据库的应用和数据库终将形成一个巨大的网格体系,应用和数据库只需在网格体系中对号入座即可,它们都是被啮合层所治理的对象,如下图所示。
4. 适用场景
三种产品的特性对比如下表所示。
ShardingSphere-JDBC |
ShardingSphere-Proxy |
ShardingSphere-Sidecar |
|
数据库 |
任意 |
MySQL/PostgreSQL |
MySQL/PostgreSQL |
连接消耗数 |
高 |
低 |
高 |
异构语言 |
仅Java |
任意 |
任意 |
性能 |
损耗低 |
损耗略高 |
损耗低 |
无中心化 |
是 |
否 |
是 |
静态入口 |
无 |
有 |
无 |
ShardingSphere-JDBC采用无中心化架构,与应用程序共享资源,适用于Java开发的高性能轻量级OLTP应用。ShardingSphere-Proxy提供静态入口以及异构语言的支持,独立于应用程序部署,适用于OLAP应用以及对分片数据库进行管理和运维的场景。
ShardingSphere是多接入端共同组成的生态圈。通过混合使用ShardingSphere-JDBC和ShardingSphere-Proxy,并采用同一注册中心统一配置分片策略,能够灵活地搭建适用于各种场景的应用系统,使架构师可以更加自由地调整适合于当前业务的最佳系统架构,如下图所示。
5. 线路规划
三、ShardingSphere核心概念
ShardingSphere有数百个功能模块,但众多模块间的关系却简单明了。大部分模块都是面向几个核心概念的横向扩展,主要包括:面向启动的运行模式、面向使用者操作的 DistSQL 以及面向开发者的可插拔架构。
1. 运行模式
ShardingSphere是一套完善的产品,使用场景非常广泛。除生产环境的集群部署之外,还为工程师在开发和自动化测试等场景提供相应的运行模式。ShardingSphere提供的三种运行模式分别是内存模式、单机模式和集群模式。
(1)内存模式
初始化配置或执行SQL等造成的元数据结果变更的操作,仅在当前进程中生效。适用于集成测试的环境启动,方便开发人员在整合功能测试中集成ShardingSphere而无需清理运行痕迹。
(2)单机模式
能够将数据源和规则等元数据信息持久化,但无法将元数据同步至多个ShardingSphere实例,无法在集群环境中相互感知。通过某一实例更新元数据之后,会导致其他实例由于获取不到最新的元数据而产生元数据不一致的错误。适用于工程师在本地搭建ShardingSphere环境。
(3)集群模式
提供了多个ShardingSphere实例之间的元数据共享和分布式场景下状态协调的能力。在真实部署线上生产环境时,必须使用集群模式。它能够提供计算能力水平扩展和高可用等分布式系统必备的能力。集群环境需要通过独立部署的注册中心(Zookeeper或Etcd)来存储元数据和协调节点状态。
2. DistSQL
DistSQL(Distributed SQL)是ShardingSphere特有的操作语言。它与标准SQL的使用方式完全一致,用于提供增量功能的SQL级别操作能力。
灵活的规则配置和资源管控能力是ShardingSphere的特点之一。 在使用4.x及其之前版本时,开发者虽然可以像使用原生数据库一样操作数据,但却需要通过本地文件(YAML)或注册中心(Zookeeper)配置资源和规则,而操作习惯的变更对于DBA并不友好。
DistSQL让用户可以像操作数据库一样操作ShardingSphere,使其从面向开发人员的框架和中间件转变为面向DBA的数据库产品。DistSQL细分为RDL、RQL和RAL三种类型。
- RDL(Resource & Rule Definition Language)负责资源和规则的创建、修改和删除。
- RQL(Resource & Rule Query Language)负责资源和规则的查询和展现。
- RAL(Resource & Rule Administration Language)负责 Hint、事务类型切换、分片执行计划查询等管理功能。
打破中间件和数据库之间的界限,让开发者像使用数据库一样使用ShardingSphere,是DistSQL的设计目标。注意DistSQL只能用于ShardingSphere-Proxy,ShardingSphere-JDBC暂不提供。
3. 可插拔架构
在ShardingSphere中,很多功能实现类的加载方式是通过SPI(Service Provider Interface)注入的方式完成。SPI是一种为了被第三方实现或扩展的API,可以用于实现框架扩展或组件替换。
可插拔架构对程序架构设计的要求非常高,需要将各个模块相互独立,互不感知,并且可以通过一个可插拔内核,以叠加的方式将各种功能组合使用。设计一套将功能开发完全隔离的架构体系,既可以最大限度的将开源社区的活力激发出来,也能够保障项目的质量。
ShardingSphere 5.x版本开始致力于可插拔架构,项目的功能组件能够灵活地以可插拔的方式进行扩展。目前,数据分片、读写分离、数据库高可用、数据加密、影子库压测等功能,以及对 MySQL、PostgreSQL、SQLServer、Oracle等SQL与协议的支持,均通过插件方式组织进项目。ShardingSphere目前已提供数十个SPI作为系统的扩展点,而且仍在不断增加中。
ShardingSphere可插拔架构如下图所示,其设计目标是让开发者能够像搭积木一样定制属于自己的独特系统。
ShardingSphere的可插拔架构划分为三层,它们是L1内核层、L2功能层、L3生态层。
L1内核层是数据库基本能力的抽象,主要包括查询优化器、分布式事务引擎、分布式执行引擎、权限引擎和调度引擎等组件。所有组件都必须存在,但具体实现可通过可插拔的方式更换。
L2功能层用于提供增量能力,主要包括数据分片、读写分离、数据库高可用、数据加密、影子库等组件。所有组件均是可选的,可以包含零至多个组件。组件之间完全隔离,互无感知,多组件可通过叠加的方式相互配合使用。用户自定义功能可完全面向ShardingSphere定义的顶层接口进行定制化扩展,而无需改动内核代码。
L3生态层用于对接和融入现有数据库生态,包括数据库协议、SQL解析器和存储适配器,分别对应于ShardingSphere以数据库协议提供服务的方式、SQL方言操作数据的方式以及对接存储节点的数据库类型。
四、ShardingSphere功能简介
ShardingSphere 提供了丰富的功能,涵盖范围从数据库内核、数据库分布式到贴近数据库上层的应用,为用户提供了大量的功能池。本节简单介绍一下这些功能,详细描述、实现细节、相关实验将在本专栏后面给出。
1. 数据库兼容
随着技术的进步,新领域的应用层出不穷,数据存储和计算模式无时无刻面临着创新。面向事务、大数据、关联分析、物联网等场景越来越细分,单一数据库再也无法适用于所有的应用场景。与此同时,场景内部也愈加细化,相似场景使用不同数据库已成为常态。
没有统一标准的数据库访问协议和SQL方言,以及各种数据库带来的不同运维方法和监控工具的异同,让开发者的学习成本和DBA的运维成本不断增加。 提升与原有数据库兼容度,是在其之上提供增量服务的前提,而SQL方言和数据库协议的兼容,是数据库兼容度提升的关键点。尽量多的兼容各种数据库,让用户零使用成本,是ShardingSphere数据库兼容希望达成的主要目标。
SQL是与数据库交流的标准语言,SQL解析引擎负责将SQL字符串解析为抽象语法树,供ShardingSphere理解并实现其增量功能。目前支持MySQL、PostgreSQL、SQLServer、Oracle、openGauss以及符合SQL92规范的SQL方言。 由于SQL语法的复杂性,目前仍然存在少量不支持的SQL,具体参见官方文档“数据库兼容特性支持”。
ShardingSphere目前实现了MySQL和PostgreSQL协议。它为数据库提供了分布式协作的能力,同时将一部分数据库特性抽象到了上层,进行统一管理,以降低用户的使用难度。
2. 集群管控
随着数据规模的不断膨胀,使用多节点集群的分布式方式逐渐成为趋势。对集群整体视角的统一管理能力,和针对单独组件细粒度的控制能力,是基于存算分离的现代数据库体系中不可或缺的功能。
集中化管理的挑战体现在将包括数据库存储节点和中间件计算节点的状态统一管理,并且能够实时探测到分布式环境下最新的变动情况,进一步为集群的控制和调度提供依据。ShardingSphere在分布式系统下对单一节点的控制表现为,面对超负荷流量,针对某一节点进行熔断和限流,以保证整个数据库集群得以继续运行,
熔断指阻断ShardingSphere和数据库的连接。 当某个ShardingSphere节点超过负载后,停止该节点对数据库的访问,使数据库能够保证足够的资源为其他节点提供服务。限流指对超负荷请求开启访问限制,以保护部分请求可以得以高质量的响应。
实现从数据库到计算节点打通的一体化管理能力,在故障中为组件提供细粒度的控制能力,并尽可能提供自愈能力,是ShardingSphere管控模块的主要设计目标。
3. 数据分片
前面“分库分表导致的复杂性”小节已经介绍了数据分片所带来的挑战。尽量透明化分库分表的影响,让使用方像使用一个数据库一样使用水平分片之后的数据库集群,是ShardingSphere数据分片模块的主要设计目标,并希望能够优先解决海量数据的OLTP问题。
表是透明化数据分片的关键概念,ShardingSphere通过提供逻辑表、真实表、绑定表、广播表、单表等多种表类型,适配不同场景下的数据分片需求。数据节点是数据分片的最小单元,由数据源名称和对应物理数据库内的真实表组成,如ds_0.t_order_0。
(1)分片键、分片算法、分片策略
分片键指用于将数据库表水平拆分的数据库字段。ShardingSphere分片支持单字段和多字段分片键。分片算法指用于将数据分片的算法,可由开发者自行实现,也可使用内置的分片算法语法糖,灵活度非常高。
自动化分片算法语法糖,用于便捷托管所有数据节点,包括取模、哈希、范围、时间等常用分片算法的实现。自定义分片算法提供接口让应用开发者自行实现与业务实现紧密相关的分片算法,并允许使用者自行管理真实表的物理分布。
分片策略包含分片键和分片算法,由于分片算法的独立性,将其独立抽离。 真正可用于分片操作的是分片键 + 分片算法,也就是分片策略。
(2)分布式主键
传统数据库软件开发中,主键自动生成技术是基本需求。各种数据库对于该需求也提供了相应的支持,比如MySQL的自增键,Oracle的自增序列等。数据分片后,不同真实表之间的自增键由于无法互相感知而会产生重复主键。
ShardingSphere不仅提供了内置的分布式主键生成器,例如UUID、SNOWFLAKE,还抽离出分布式主键生成器的接口,方便用户实现自定义的自增主键生成器。
4. 分布式事务
在单数据节点中,事务仅限于对单一数据库资源的访问控制,称之为本地事务。几乎所有的成熟的关系型数据库都提供了对本地事务的ACID(Atomicity原子性、Consistency一致性、Isolation隔离性、Durability持久性)原生支持,但在分布式场景下,它却成为系统性能的桎梏,分布式事务应运而生。数据库在分布式场景下满足ACID特性或找寻相应的替代方案,是分布式事务的重点工作。ShardingSphere支持两阶段提交和柔性事务两种通用的分布式事务解决方案。
(1)两阶段提交
两阶段提交使用XA协议,最大优势是对使用方透明,用户可以像使用本地事务一样使用基于XA协议的分布式事务,XA协议能够严格保障事务的ACID特性,但这是一把双刃剑。事务在执行过程中需要将所需资源全部锁定,整个事务进行期间对数据的独占,将导致对热点数据依赖的业务系统并发性能衰退,对于长事务尤其明显。因此,在高并发的性能至上场景中,基于XA协议的分布式事务并不是最佳选择。ShardingSphere在整合XA事务时,采用分离XA事务管理和连接池管理的方式,做到对应用程序的零侵入。
(2)柔性事务
如果将实现了ACID要素的事务称为刚性事务的话,那么基于BASE要素的事务则称为柔性事务。 BASE是基本可用、柔性状态和最终一致性这三个要素的缩写。
- 基本可用(Basically Available)保证分布式事务参与方不一定同时在线。
- 柔性状态(Soft state)则允许系统状态更新有一定的延时,这个延时对客户来说不一定能够察觉。
- 最终一致性(Eventually consistent)通常是通过消息传递的方式保证系统的最终一致性。
在ACID事务中对隔离性的要求很高,事务执行过程中必须将所有需要的资源锁定。柔性事务的理念则是通过业务逻辑将互斥锁操作从资源层面上移至业务层面。通过放宽对强一致性要求,来换取系统吞吐量的提升。ShardingSphere集成了SEATA作为柔性事务的使用方案。
(3)方案选择
基于ACID的强一致性事务和基于BASE的最终一致性事务都不是银弹,只有在最适合的场景中才能发挥它们的最大长处。可通过下表详细对比它们之间的区别,以帮助开发者进行技术选型。
本地事务 |
两阶段提交 |
柔性事务 |
|
业务改造 |
无 |
无 |
实现相关接口 |
一致性 |
不支持 |
支持 |
最终一致 |
隔离性 |
不支持 |
支持 |
业务方保证 |
并发性能 |
无影响 |
严重衰退 |
略微衰退 |
适合场景 |
业务方负责处理不一致 |
低并发短事务 |
高并发长事务 |
由于应用场景不同,需要开发者能够合理地在性能与功能之间权衡各种分布式事务。强一致的事务与柔性事务的API和功能并不完全相同,在它们之间并不能做到自由的透明切换。在开发决策阶段,就不得不在强一致的事务和柔性事务之间抉择,使得设计和开发成本被大幅增加。
基于XA的强一致事务使用相对简单,但是无法很好的应对互联网的高并发或复杂系统的长事务场景;柔性事务则需要开发者对应用进行改造,接入成本非常高,并且需要开发者自行实现资源锁定和反向补偿。
整合现有的成熟事务方案,为本地事务、两阶段事务和柔性事务提供统一的分布式事务接口,并弥补当前方案的不足,提供一站式的分布式事务解决方案是ShardingSphere分布式事务模块的主要设计目标。
5. 读写分离
通过一主多从的配置方式,可以将查询请求均匀分散到多个数据副本,能够进一步的提升系统的处理能力。使用多主多从的方式,不但能够提升系统的吞吐量,还能够提升系统可用性,可以达到在任何一个数据库宕机,甚至磁盘物理损坏的情况下仍然不影响系统的正常运行。
与将数据根据分片键打散至各个数据节点的水平分片不同,读写分离则是根据对SQL语义的分析,将读操作和写操作分别路由至主库与从库。透明化读写分离所带来的影响,让使用方尽量像使用一个数据库一样使用主从数据库集群,是ShardingSphere读写分离模块的主要设计目标。
读写分离模块支持以下特性:
- 提供一主多从的读写分离配置,可独立使用,也可配合数据分片使用。
- 事务中的数据读写均用主库。
- 通过负载均衡策略(轮询、随机、权重)将查询请求疏导至不同从库。
- 基于Hint的强制主库路由。
6. 弹性伸缩
ShardingSphere-Scaling是一个提供给用户的通用数据接入迁移及弹性伸缩的解决方案。它不是一个独立的产品,而是以ShardingSphere-Proxy中的一个配置项提供相应功能,工作流程如下图所示。
支持自定义分片算法,减少数据伸缩及迁移时的业务影响,提供一站式的通用弹性伸缩解决方案,是ShardingSphere弹性伸缩的主要设计目标。ShardingSphere-Scaling从4.1.0版本开始向用户提供,当前处于alpha开发阶段。
ShardingSphere-Scaling支持将外围数据迁移至ShardingSphere所管理的数据库,或对 ShardingSphere的数据节点进行扩容或缩容。它不支持无主键表扩缩容、复合主键表扩缩容,如在当前存储节点之上做迁移,需要准备一个全新的数据库集群作为迁移目标库。
ShardingSphere-Scaling为了避免因分片算法带来的数据迁移复杂性,目前的实现方式可谓非常简单粗暴。首先它需要利用数据库系统原生的复制机制。其次不分场景全部采用存量 + 增量的全量数据复制。最后,在扩容或数据迁移过程中,需要目标为全新集群,这意味着临时需要至少双倍数量的服务器。
7. 数据加密
根据业界对加密的需求及业务改造痛点,提供了一套完整、安全、透明化、低改造成本的数据加密整合解决方案,是ShardingSphere数据加密模块的主要设计目标。它包含逻辑列、密文列、查询辅助列、明文列等基本概念。
逻辑列用于计算加解密列的逻辑名称,是SQL中列的逻辑标识。逻辑列包含密文列(必须)、查询辅助列(可选)和明文列(可选)。密文列是加密后的数据列。查询辅助列是用于查询的辅助列。对于一些安全级别更高的非幂等加密算法,提供不可逆的幂等列用于查询。明文列是存储明文的列,用于在加密数据迁移过程中仍旧提供服务,在数据清洗结束后可以删除。
数据加密模块支持对数据库表中单个或多个列进行加解密,兼容所有常用SQL。限制为需要用户自行处理数据库中原始的存量数据;加密字段查询必须区分大小写;加密字段不支持大于、小于、ORDER BY、BETWEEN、LIKE等比较操作;加密字段不支持AVG、SUM 以及表达式等计算操作。
8. 影子库压测
生产环境压测需要应对不同流量以及压测标识的透传,并且为了保证生产数据的可靠性与完整性,要在数据库层面做好数据隔离,将压测产生的数据路由到压测环境数据库,防止压测数据对生产数据库中真实数据造成污染。
ShardingSphere关注于全链路压测场景下,数据库层面的解决方案。 将压测数据自动路由至用户指定的数据库,即影子库,是ShardingSphere影子库模块的主要设计目标。
目前提供两种类型的影子算法。基于列的影子算法通过识别SQL中的数据,匹配路由至影子库,适用于由压测数据名单驱动的压测场景。基于Hint的影子算法通过识别SQL中的注释,匹配路由至影子库,适用于由上游系统透传标识驱动的压测场景。Hint影子算法支持全部SQL,基于列的影子算法仅支持部分SQL,如不支持 DDL、不支持范围、分组和子查询,如BETWEEN、GROUP BY … HAVING等。
通过组合ShardingSphere已经实现的以及一些规划中的功能模块,能够提供多元化解决方案,如下表所示。
分布式数据库 |
数据安全 |
数据库网关 |
全链路压测 |
数据分片 |
数据加密 |
异构数据库支持 |
影子库 |
读写分离 |
行级权限(TODO) |
SQL方言转化(TODO) |
可观测性(第三方集成) |
分布式事务 |
SQL审计(TODO) |
||
弹性伸缩 |
SQL防火墙(TODO) |
||
高可用 |