Sharding-JDBC分片策略、分布式事务、全局ID

分片策略详解 :

  https://shardingsphere.apache.org/document/current/cn/features/sharding/concept/sharding/

  Sharding-JDBC 中的分片策略有两个维度:分库(数据源分片)策略和分表策略。

  分库策略表示数据路由到的物理目标数据源,分表分片策略表示数据被路由到的目标表。分表策略是依赖于分库策略的,也就是说要先分库再分表,当然也可以不分库只分表。

  跟 Mycat 不一样,Sharding-JDBC 没有提供内置的分片算法,而是通过抽象成接口,让开发者自行实现,这样可以根据业务实际情况灵活地实现分片。

分片策略:

  包含分片键和分片算法,由于分片算法的独立性,将其独立抽离。真正可用于分片操作的是分片键 + 分片算法,也就是分片策略。分片算法是需要自定义的。可以用于分库,也可以用于分

  Sharding-JDBC 提供了 5 种分片策略,这些策略全部继承自 ShardingStrategy。

  1. 标准分片策略:对应 StandardShardingStrategy。提供对 SQL语句中的 =><>=<=IN 和 BETWEEN AND 的分片操作支持。 StandardShardingStrategy 只支持单分片键,提供 PreciseShardingAlgorithm 和 RangeShardingAlgorithm 两个分片算法。 PreciseShardingAlgorithm 是必选的,用于处理 = 和 IN 的分片。 RangeShardingAlgorithm 是可选的,用于处理 BETWEEN AND><>=<=分片,如果不配置 RangeShardingAlgorithm,SQL 中的 BETWEEN AND 将按照全库路由处理。
  2. 复合分片策略:对应 ComplexShardingStrategy。复合分片策略。提供对 SQL 语句中的 =><>=<=IN 和 BETWEEN AND 的分片操作支持。 ComplexShardingStrategy 支持多分片键,由于多分片键之间的关系复杂,因此并未进行过多的封装,而是直接将分片键值组合以及分片操作符透传至分片算法,完全由应用开发者实现,提供最大的灵活度。
  3. 行表达式分片策略:对应 InlineShardingStrategy。使用 Groovy 的表达式,提供对 SQL 语句中的 = 和 IN 的分片操作支持,只支持单分片键。 对于简单的分片算法,可以通过简单的配置使用,从而避免繁琐的 Java 代码开发,如: t_user_$->{u_id % 8} 表示 t_user 表根据 u_id 模 8,而分成 8 张表,表名称为 t_user_0 到 t_user_7。 
  4. Hint分片策略:对应 HintShardingStrategy。通过 Hint 指定分片值而非从 SQL 中提取分片值的方式进行分片的策略。
  5. 不分片策略:对应 NoneShardingStrategy。不分片的策略。

分片算法:

  通过分片算法将数据分片,支持通过 =>=<=><BETWEEN 和 IN 分片。 分片算法需要应用方开发者自行实现,可实现的灵活度非常高。目前提供4种分片算法。 由于分片算法和业务实现紧密相关,因此并未提供内置分片算法,而是通过分片策略将各种场景提炼出来,提供更高层级的抽象,并提供接口让应用开发者自行实现分片算法。

  1. 精确分片算法:对应 PreciseShardingAlgorithm,用于处理使用单一键作为分片键的 = 与 IN 进行分片的场景。需要配合 StandardShardingStrategy 使用。
  2. 范围分片算法:对应 RangeShardingAlgorithm,用于处理使用单一键作为分片键的 BETWEEN AND><>=<=进行分片的场景。需要配合 StandardShardingStrategy 使用。
  3. 复合分片算法:对应 ComplexKeysShardingAlgorithm,用于处理使用多键作为分片键进行分片的场景,包含多个分片键的逻辑较复杂,需要应用开发者自行处理其中的复杂度。需要配合 ComplexShardingStrategy 使用。
  4. Hint分片算法:对应 HintShardingAlgorithm,用于处理使用 Hint 行分片的场景。需要配合 HintShardingStrategy 使用。

  算法实现 :

  实现的话就是实现对应的算法接口,并且把实现类配置进去即可。下列就是这四种算法对应的接口。实现他们的  doSharding 方法即可。

public interface PreciseShardingAlgorithm<T extends Comparable<?>> extends ShardingAlgorithm {
    
    String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<T> shardingValue);
}

public interface RangeShardingAlgorithm<T extends Comparable<?>> extends ShardingAlgorithm {
    
    Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<T> shardingValue);
}

public interface ComplexKeysShardingAlgorithm extends ShardingAlgorithm {
    Collection<String> doSharding(Collection<String> availableTargetNames, Collection<ShardingValue> shardingValues);
}

public interface HintShardingAlgorithm extends ShardingAlgorithm {
    Collection<String> doSharding(Collection<String> availableTargetNames, ShardingValue shardingValue);
}

  然后配置分库分表策略的时候进行注入就行:

userTableRuleConfig.setDatabaseShardingStrategyConfig(new StandardShardingStrategyConfiguration("user_id", DBShardAlgo.class.getName()));
userTableRuleConfig.setTableShardingStrategyConfig(new StandardShardingStrategyConfiguration("user_id",TblPreShardAlgo.class.getName(), TblRangeShardAlgo.class.getName()));

  其中 DBShardAlgo、TblPreShardAlgo、TblRangeShardAlgo都是自定义的策略类。如果 springboot下面 ,则按照如下配置:

sharding.jdbc.config.sharding.tables.user_info.databaseStrategy.standard.shardingColumn=user_id
sharding.jdbc.config.sharding.tables.user_info.databaseStrategy.standard.preciseAlgorithmClassName=com.wuzz.demo.config.DBShardAlgo
sharding.jdbc.config.sharding.tables.user_info.tableStrategy.standard.shardingColumn=user_id
sharding.jdbc.config.sharding.tables.user_info.tableStrategy.standard.preciseAlgorithmClassName=com.wuzz.demo.config.TblPreShardAlgo
sharding.jdbc.config.sharding.tables.user_info.tableStrategy.standard.rangeAlgorithmClassName=com.wuzz.demo.config.TblRangeShardAlgo

分布式事务支持:

  官网:https://shardingsphere.apache.org/document/current/cn/features/transaction/

  本地事务:

  在不开启任何分布式事务管理器的前提下,让每个数据节点各自管理自己的事务。 它们之间没有协调以及通信的能力,也并不互相知晓其他数据节点事务的成功与否。 本地事务在性能方面无任何损耗,但在强一致性以及最终一致性方面则力不从心。

  两阶段提交:

  XA协议最早的分布式事务模型是由 X/Open 国际联盟提出的 X/Open Distributed Transaction Processing (DTP) 模型,简称 XA 协议。

  基于XA协议实现的分布式事务对业务侵入很小。 它最大的优势就是对使用方透明,用户可以像使用本地事务一样使用基于XA协议的分布式事务。 XA协议能够严格保障事务 ACID 特性。

  严格保障事务 ACID 特性是一把双刃剑。 事务执行在过程中需要将所需资源全部锁定,它更加适用于执行时间确定的短事务。 对于长事务来说,整个事务进行期间对数据的独占,将导致对热点数据依赖的业务系统并发性能衰退明显。 因此,在高并发的性能至上场景中,基于XA协议的分布式事务并不是最佳选择。

  柔性事务:

  如果将实现了 ACID 的事务要素的事务称为刚性事务的话,那么基于 BASE 事务要素的事务则称为柔性事务。 BASE 是基本可用、柔性状态和最终一致性这三个要素的缩写。

  • 基本可用(Basically Available)保证分布式事务参与方不一定同时在线。

  • 柔性状态(Soft state)则允许系统状态更新有一定的延时,这个延时对客户来说不一定能够察觉。

  • 而最终一致性(Eventually consistent)通常是通过消息传递的方式保证系统的最终一致性。

  在 ACID 事务中对隔离性的要求很高,在事务执行过程中,必须将所有的资源锁定。 柔性事务的理念则是通过业务逻辑将互斥锁操作从资源层面上移至业务层面。通过放宽对强一致性要求,来换取系统吞吐量的提升。

  基于 ACID 的强一致性事务和基于 BASE 的最终一致性事务都不是银弹,只有在最适合的场景中才能发挥它们的最大长处。 可通过下表详细对比它们之间的区别,以帮助开发者进行技术选型。

  Sharding-JDBC下实现强一致分布式事务需要导入以下依赖:

<!--xa分布式事务-->
<dependency>
  <groupId>io.shardingsphere</groupId>
  <artifactId>sharding-transaction-2pc-xa</artifactId>
  <version>3.1.0</version>
</dependency>

<dependency>
  <groupId>io.shardingsphere</groupId>
  <artifactId>sharding-transaction-spring-boot-starter</artifactId>
  <version>3.1.0</version>
</dependency>

  默认是用 atomikos 实现的。在 Service 类上加上注解:

@ShardingTransactionType(TransactionType.XA)
@Transactional(rollbackFor = Exception.class)

分布式全局 ID:

  传统数据库软件开发中,主键自动生成技术是基本需求。而各个数据库对于该需求也提供了相应的支持,比如 MySQL 的自增键,Oracle 的自增序列等。 数据分片后,不同数据节点生成全局唯一主键是非常棘手的问题。同一个逻辑表内的不同实际表之间的自增键由于无法互相感知而产生重复主键。 虽然可通过约束自增主键初始值和步长的方式避免碰撞,但需引入额外的运维规则,使解决方案缺乏完整性和可扩展性。

  目前有许多第三方解决方案可以完美解决这个问题,如 UUID 等依靠特定算法自生成不重复键,或者通过引入主键生成服务等。为了方便用户使用、满足不同用户不同使用场景的需求, Apache ShardingSphere 不仅提供了内置的分布式主键生成器,例如 UUID、SNOWFLAKE,还抽离出分布式主键生成器的接口 KeyGenerator,方便用户自行实现自定义的自增主键生成器。

  内置的主键生成器:

  • UUID:采用 UUID.randomUUID() 的方式产生分布式主键。
  • SNOWFLAKE:在分片规则配置模块可配置每个表的主键生成策略,默认使用雪花算法(snowflake)生成 64bit 的长整型数据。雪花算法是由 Twitter 公布的分布式主键生成算法,它能够保证不同进程主键的不重复性,以及相同进程主键的有序性。

  Java config 配置:

tableRuleConfig.setKeyGeneratorColumnName("order_id");
tableRuleConfig.setKeyGeneratorClass("io.shardingsphere.core.keygen.DefaultKeyGenerator");

  keyGeneratorColumnName:指定需要生成 ID 的列

  KeyGenerotorClass:指定生成器类,默认是 DefaultKeyGenerator.java,里面使用了雪花算法。

  Properties 配置:

#全局默认算法
sharding.jdbc.config.sharding.default-key-generator-class-name=
#指定表
sharding.jdbc.config.sharding.tables.t_order.keyGeneratorColumnName=
sharding.jdbc.config.sharding.tables.t_order.keyGeneratorClassName=

Sharding-JDBC 工作流程:

  内核剖析https://shardingsphere.apache.org/document/current/cn/features/sharding/principle/

  Sharding-JDBC 的原理总结起来很简单:SQL 解析 => 执行器优化 => SQL 路由 => SQL 改写 => SQL 执行 => 结果归并。

  1. SQL 解析:分为词法解析和语法解析。 先通过词法解析器将 SQL 拆分为一个个不可再分的单词。再使用语法解析器对 SQL 进行理解,并最终提炼出解析上下文。 解析上下文包括表、选择项、排序项、分组项、聚合函数、分页信息、查询条件以及可能需要修改的占位符的标记。SQL 解析主要是词法和语法的解析。目前常见的 SQL 解析器主要有 fdb,jsqlparser和 Druid。Sharding-JDBC1.4.x 之前的版本使用 Druid 作为 SQL 解析器。从 1.5.x 版本开始,Sharding-JDBC 采用完全自研的 SQL 解析引擎。
  2. 执行器优化:合并和优化分片条件,如 OR 等。
  3. SQL 路由:SQL 路由是根据分片规则配置以及解析上下文中的分片条件,将 SQL 定位至真正的数据源。它又分为直接路由、简单路由和笛卡尔积路由。直接路由,使用 Hint 方式。Binding 表是指使用同样的分片键和分片规则的一组表,也就是说任何情况下,Binding 表的分片结果应与主表一致。例如:order 表和 order_item 表,都根据 order_id分片,结果应是 order_1 与 order_item_1 成对出现。这样的关联查询和单表查询复杂度和性能相当。如果分片条件不是等于,而是 BETWEEN 或 IN,则路由结果不一定落入单库(表),因此一条逻辑 SQL 最终可能拆分为多条 SQL 语句。笛卡尔积查询最为复杂,因为无法根据 Binding 关系定位分片规则的一致性,所以非Binding 表的关联查询需要拆解为笛卡尔积组合执行。查询性能较低,而且数据库连接数较高,需谨慎使用。
  4. SQL 改写:将逻辑表名称改成真实表名称,优化分页查询等
  5. SQL 执行:因为可能链接到多个真实数据源, Sharding -JDBC 将采用多线程并发执行 SQL。
  6. 结果归并:例如数据的组装、分页、排序等等。

Sharding-JDBC 实现原理:

  JDBC 的四大核心对象?DataSource、Connection、Statement(PS)、ResulstSet。

  Sharding-JDBC 封装了这四个核心类,在类名前面加上了 Sharding。

  如果说带 Sharding 的类要替换 JDBC 的对象,那么一定要找到创建和调用他们的地方。ShardingDataSource 我们不说了,系统启动的时候就创建好了。问 题 就 在 于 , 我 们 是 什 么 时 候 用 ShardingDataSource 获 取 一 个ShardingConnection 的?我们以整合了 MyBatis 的项目为例。MyBatis 封装了 JDBC 的核心对象,那么在MyBatis 操作 JDBC 四大对象的时候,就要替换成 Sharding-JDBC 的四大对象。 我 们 的 查 询 方 法 最 终 会 走 到SimpleExecutor 的 doQuery()方法,这个是我们的前提知识,那我们直接在 doQuery()打断点。doQuery()方法里面调用了 prepareStatement()创建连接。而返回的就是 一个 ShardingConnection ,所以后续都是采用 Sharding-JDBC自己的实现类去操作,从而替换了四大对象。

  更多内容请参考官网:https://shardingsphere.apache.org/document/legacy/4.x/document/cn/overview/ .可以切换成中文.

原文地址:https://www.cnblogs.com/wuzhenzhao/p/13474022.html