经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » 编程经验 » 查看文章
DIY 3 种分库分表分片算法,自己写的轮子才吊!
来源:cnblogs  作者:程序员小富  时间:2024/4/3 9:28:20  对本文有异议

大家好,我是小富~

前言

本文是《ShardingSphere5.x分库分表原理与实战》系列的第六篇,书接上文实现三种自定义分片算法。通过自定义算法,可以根据特定业务需求定制分片策略,以满足不同场景下的性能、扩展性或数据处理需求。同时,可以优化分片算法以提升系统性能,规避数据倾斜等问题。

在这里,自定义分片算法的类型(Type)统一为CLASS_BASED,包含两个属性:strategy 表示分片策略类型,目前支持三种:STANDARDCOMPLEXHINTalgorithmClassName 表示自定义分片算法的实现类路径。此外,还可以向算法类内传入自定义属性。

自定义 STANDARD 算法

要实现自定义 STANDARD 标准算法,需要实现StandardShardingAlgorithm<T>接口( T 代表接收的分片健值类型),并重写接口中的四个方法。其中,有两个 doSharding() 方法为处理分片的核心逻辑;getProps() 方法用于获取分片算法的配置信息;init() 方法则用于初始化分片算法的配置信息,支持动态修改。

5.X 以后的版本,实现自定义标准算法的精准分片和范围分片,不在需要实现多个接口。只用实现 StandardShardingAlgorithm 标准算法接口,重写两个 doSharding() 方法。 doSharding(availableTargetNames,rangeShardingValue) 处理含有 >、<、between and 等操作符的 SQL,doSharding(availableTargetNames,preciseShardingValue) 处理含有 = 、in 等操作符的 SQL。

精准分片

精准分片用于SQL中包含 in、= 等操作符的场景,支持单一分片健。

重写方法 doSharding(Collection availableTargetNames, PreciseShardingValue preciseShardingValue),该方法返回单一的分片数据源或分片表数据。有两个参数:一个是可用目标分库、分表的集合,另一个是精准分片属性对象。

PreciseShardingValue 对象属性数据格式如下:

  1. {
  2. "columnName": "order_id", // 分片健
  3. "dataNodeInfo": {
  4. "paddingChar": "0",
  5. "prefix": "db", // 数据节点信息前缀,例如:分库时为db,分表时为分片表t_order_
  6. "suffixMinLength": 1
  7. },
  8. "logicTableName": "t_order", // 逻辑表
  9. "value": 1 // 分片健值
  10. }

范围分片

范围分片用于 SQL中包含 >、< 等范围操作符的场景,支持单一分片健。

重写方法 doSharding(Collection availableTargetNames, RangeShardingValue rangeShardingValue),该方法可以返回多个分片数据源或分片表数据。有两个参数:一个是可用目标分库、分表的集合,另一个是精准分片属性对象。

RangeShardingValue 对象属性数据格式如下:

  1. {
  2. "columnName": "order_id", // 分片健
  3. "dataNodeInfo": {
  4. "paddingChar": "0",
  5. "prefix": "db", // 数据节点前缀,分库时为数据源,分表时为分片表t_order_
  6. "suffixMinLength": 1
  7. },
  8. "logicTableName": "t_order", // 逻辑表
  9. "valueRange": [0,∞] // 分片健值的范围数据
  10. }

精准分片算法的 doSharding() 执行流程:从PreciseShardingValue.getValue()中获取分片键值,然后经过计算得出相应编号,最终在availableTargetNames可用目标分库、分片表集合中选择以一个符合的返回。

范围分片算法的 doSharding() 执行流程:从RangeShardingValue.getValueRange()方法获取分片键的数值范围,然后经过计算得出相应编号,最终在availableTargetNames可用目标分库、分片表集合中选择多个符合的返回。

下面是具体实现分片的逻辑:

  1. /**
  2. * 自定义标准分片算法
  3. *
  4. * @author 公众号:程序员小富
  5. * @date 2024/03/22 11:02
  6. */
  7. @Slf4j
  8. public class OrderStandardCustomAlgorithm implements StandardShardingAlgorithm<Long> {
  9. /**
  10. * 精准分片进入 sql中有 = 和 in 等操作符会执行
  11. *
  12. * @param availableTargetNames 所有分片表的集合
  13. * @param shardingValue 分片健的值,SQL中解析出来的分片值
  14. */
  15. @Override
  16. public String doSharding(Collection<String> availableTargetNames,
  17. PreciseShardingValue<Long> shardingValue) {
  18. /**
  19. * 分库策略使用时:availableTargetNames 参数数据为分片库的集合 ["db0","db1"]
  20. * 分表策略使用时:availableTargetNames 参数数据为分片库的集合 ["t_order_0","t_order_1","t_order_2"]
  21. */
  22. log.info("进入精准分片 precise availableTargetNames:{}", JSON.toJSONString(availableTargetNames));
  23. /**
  24. * 分库策略使用时: shardingValue 参数数据:{"columnName":"order_id","dataNodeInfo":{"paddingChar":"0","prefix":"db","suffixMinLength":1},"logicTableName":"t_order","value":1}
  25. * 分表策略使用时: shardingValue 参数数据:{"columnName":"order_id","dataNodeInfo":{"paddingChar":"0","prefix":"t_order_","suffixMinLength":1},"logicTableName":"t_order","value":1}
  26. */
  27. log.info("进入精准分片 preciseShardingValue:{}", JSON.toJSONString(shardingValue));
  28. int tableSize = availableTargetNames.size();
  29. // 真实表的前缀
  30. String tablePrefix = shardingValue.getDataNodeInfo().getPrefix();
  31. // 分片健的值
  32. long orderId = shardingValue.getValue();
  33. // 对分片健取模后确定位置
  34. long mod = orderId % tableSize;
  35. return tablePrefix + mod;
  36. }
  37. /**
  38. * 范围分片进入 sql中有 between 和 < > 等操作符会执行
  39. *
  40. * @param availableTargetNames 所有分片表的集合
  41. * @param shardingValue 分片健的值,SQL中解析出来的分片值
  42. * @return
  43. */
  44. @Override
  45. public Collection<String> doSharding(Collection<String> availableTargetNames,
  46. RangeShardingValue<Long> shardingValue) {
  47. /**
  48. * 分库策略使用时:availableTargetNames 参数数据为分片库的集合 ["db0","db1"]
  49. * 分表策略使用时:availableTargetNames 参数数据为分片库的集合 ["t_order_0","t_order_1","t_order_2"]
  50. */
  51. log.info("进入范围分片:range availableTargetNames:{}", JSON.toJSONString(availableTargetNames));
  52. /**
  53. * 分库策略使用时 shardingValue 参数数据:{"columnName":"order_id","dataNodeInfo":{"paddingChar":"0","prefix":"db","suffixMinLength":1},"logicTableName":"t_order","valueRange":{"empty":false}}
  54. * 分表策略使用时 shardingValue 参数数据:{"columnName":"order_id","dataNodeInfo":{"paddingChar":"0","prefix":"t_order_","suffixMinLength":1},"logicTableName":"t_order","valueRange":{"empty":false}}
  55. */
  56. log.info("进入范围分片:rangeShardingValue:{}", JSON.toJSONString(shardingValue));
  57. // 分片健值的下边界
  58. Range<Long> valueRange = shardingValue.getValueRange();
  59. Long lower = valueRange.lowerEndpoint();
  60. // 分片健值的上边界
  61. Long upper = valueRange.upperEndpoint();
  62. // 真实表的前缀
  63. String tablePrefix = shardingValue.getDataNodeInfo().getPrefix();
  64. if (lower != null && upper != null) {
  65. // 分片健的值
  66. long orderId = upper - lower;
  67. // 对分片健取模后确定位置
  68. long mod = orderId % availableTargetNames.size();
  69. return Arrays.asList(tablePrefix + mod);
  70. }
  71. //
  72. return Collections.singletonList("t_order_0");
  73. }
  74. @Override
  75. public Properties getProps() {
  76. return null;
  77. }
  78. /**
  79. * 初始化配置
  80. *
  81. * @param properties
  82. */
  83. @Override
  84. public void init(Properties properties) {
  85. Object prop = properties.get("prop");
  86. log.info("配置信息:{}", JSON.toJSONString(prop));
  87. }
  88. }

配置算法

在实现了自定义分片算法的两个 doSharding() 核心逻辑之后,接着配置并使用定义的算法。配置属性包括strategy分片策略类型设置成standardalgorithmClassName自定义标准算法的实现类全路径。需要注意的是:策略和算法类型必须保持一致,否则会导致错误

  1. spring:
  2. shardingsphere:
  3. rules:
  4. sharding:
  5. # 分片算法定义
  6. sharding-algorithms:
  7. t_order_database_mod:
  8. type: MOD
  9. props:
  10. sharding-count: 2 # 指定分片数量
  11. # 12、自定义 STANDARD 标准算法
  12. t_order_standard_custom_algorithm:
  13. type: CLASS_BASED
  14. props:
  15. # 分片策略
  16. strategy: standard
  17. # 分片算法类
  18. algorithmClassName: com.shardingsphere_101.algorithm.OrderStandardCustomAlgorithm
  19. # 自定义属性
  20. prop:
  21. aaaaaa: 123456
  22. bbbbbb: 654321
  23. tables:
  24. # 逻辑表名称
  25. t_order:
  26. # 数据节点:数据库.分片表
  27. actual-data-nodes: db$->{0..1}.t_order_${0..2}
  28. # 分库策略
  29. database-strategy:
  30. standard:
  31. sharding-column: order_id
  32. sharding-algorithm-name: t_order_database_mod
  33. # 分表策略
  34. table-strategy:
  35. standard:
  36. sharding-column: order_id
  37. sharding-algorithm-name: t_order_standard_custom_algorithm

测试算法

在插入测试数据时,默认会自动进入精确分片的 doSharding() 方法内,看到该方法会获取分片键的数值,根据我们的计算规则确定返回一个目标分片表用于路由。

接着执行一个范围查询的 SQL,此时将进入范围分片的 doSharding() 方法。通过观察 shardingValue.getValueRange() 方法中分片键的数值范围,可以发现这些数值范围是从SQL查询中解析得到的。

  1. select * from t_order where order_id > 1 and order_id < 10

自定义 COMPLEX 算法

复合分片算法支持包含 >,>=, <=,<,=,IN 和 BETWEEN AND 等操作符的SQL,支持多分片健。

自定义COMPLEX复合分片算法,需要我们实现 ComplexKeysShardingAlgorithm<T> 接口(其中 T 代表接收的分片键值类型),并重写该接口内部的 3 个方法。其中,主要关注用于处理核心分片逻辑的 doSharding()方法,可以返回多个分片数据源或分片表数据;其他两个配置方法与上述类似,这里不再赘述。

重写复合分片方法 doSharding(Collection availableTargetNames, ComplexKeysShardingValue shardingValues) 实现定制的多分片健逻辑,该方法有两个参数:一个是可用目标分库、分表的集合;另一个是多分片健属性对象。

logicTableName为逻辑表名,columnNameAndShardingValuesMap用于存储多个分片键和对应的键值,columnNameAndRangeValuesMap用于存储多个分片键和对应的键值范围。

ComplexKeysShardingValue数据结构如下:

  1. public final class ComplexKeysShardingValue<T extends Comparable<?>> implements ShardingValue {
  2. // 逻辑表
  3. private final String logicTableName;
  4. // 多分片健及其数值
  5. private final Map<String, Collection<T>> columnNameAndShardingValuesMap;
  6. // 多分片健及其范围数值
  7. private final Map<String, Range<T>> columnNameAndRangeValuesMap;
  8. }

核心流程:通过循环 Map 得到多个分片健值进行计算,从 availableTargetNames 可用目标分库、分片表集合中选择多个符合条件的返回。

  1. /**
  2. * 自定义复合分片算法
  3. *
  4. * @author 公众号:程序员小富
  5. * @date 2024/03/22 11:02
  6. */
  7. @Slf4j
  8. public class OrderComplexCustomAlgorithm implements ComplexKeysShardingAlgorithm<Long> {
  9. /**
  10. * 复合分片算法进入,支持>,>=, <=,<,=,IN 和 BETWEEN AND 等操作符
  11. *
  12. * @param availableTargetNames 所有分片表的集合
  13. * @param complexKeysShardingValue 多个分片健的值,并SQL中解析出来的分片值
  14. */
  15. @Override
  16. public Collection<String> doSharding(Collection<String> availableTargetNames,
  17. ComplexKeysShardingValue<Long> complexKeysShardingValue) {
  18. /**
  19. * 分库策略使用时:availableTargetNames 参数数据为分片库的集合 ["db0","db1"]
  20. * 分表策略使用时:availableTargetNames 参数数据为分片库的集合 ["t_order_0","t_order_1","t_order_2"]
  21. */
  22. log.info("进入复合分片:complex availableTargetNames:{}", JSON.toJSONString(availableTargetNames));
  23. // 多分片健和其对应的分片健范围值
  24. Map<String, Range<Long>> columnNameAndRangeValuesMap = complexKeysShardingValue.getColumnNameAndRangeValuesMap();
  25. log.info("进入复合分片:columnNameAndRangeValuesMap:{}", JSON.toJSONString(columnNameAndRangeValuesMap));
  26. columnNameAndRangeValuesMap.forEach((columnName, range) -> {
  27. // 分片健
  28. log.info("进入复合分片:columnName:{}", columnName);
  29. // 分片健范围值
  30. log.info("进入复合分片:range:{}", JSON.toJSONString(range));
  31. });
  32. // 多分片健和其对应的分片健值
  33. Map<String, Collection<Long>> columnNameAndShardingValuesMap = complexKeysShardingValue.getColumnNameAndShardingValuesMap();
  34. log.info("进入复合分片:columnNameAndShardingValuesMap:{}", JSON.toJSONString(columnNameAndShardingValuesMap));
  35. columnNameAndShardingValuesMap.forEach((columnName, shardingValues) -> {
  36. // 分片健
  37. log.info("进入复合分片:columnName:{}", columnName);
  38. // 分片健值
  39. log.info("进入复合分片:shardingValues:{}", JSON.toJSONString(shardingValues));
  40. });
  41. return null;
  42. }
  43. }

配置算法

处理完复合分片算法的doSharding()核心逻辑,接着配置使用定义的算法,配置属性包括strategy分片策略类型设置成complexalgorithmClassName自定义算法的实现类全路径。

需要注意:配置分片键时,一定要使用 sharding-columns 表示复数形式,很容易出错。

  1. spring:
  2. shardingsphere:
  3. rules:
  4. sharding:
  5. sharding-algorithms:
  6. t_order_database_mod:
  7. type: MOD
  8. props:
  9. sharding-count: 2 # 指定分片数量
  10. # 13、自定义 complex 标准算法
  11. t_order_complex_custom_algorithm:
  12. type: CLASS_BASED
  13. props:
  14. # 分片策略
  15. strategy: complex
  16. # 分片算法类
  17. algorithmClassName: com.shardingsphere_101.algorithm.OrderComplexCustomAlgorithm
  18. # 自定义属性
  19. aaaaaa: aaaaaa
  20. tables:
  21. # 逻辑表名称
  22. t_order:
  23. # 数据节点:数据库.分片表
  24. actual-data-nodes: db$->{0..1}.t_order_${0..2}
  25. # 分库策略
  26. database-strategy:
  27. standard:
  28. sharding-column: order_id
  29. sharding-algorithm-name: t_order_database_mod
  30. # 分表策略
  31. table-strategy:
  32. complex:
  33. sharding-columns: order_id , user_id
  34. sharding-algorithm-name: t_order_complex_custom_algorithm

测试算法

插入测试数据,debug 进入 doSharding() 方法,看到columnNameAndShardingValuesMap内获取到了 user_id
、order_id 两个分片键及健值。

当执行范围查询的SQL,columnNameAndRangeValuesMap属性内获取到了 user_id、order_id 两个分片键及健值范围,通过range.upperEndpoint()、lowerEndpoint()得到上下界值。

  1. select * from t_order where order_id > 1 and user_id > 1;

自定义 HINT 算法

要实现自定义HINT强制路由分片算法,需要实现 HintShardingAlgorithm<T> 接口( T 代表接收的分片键值类型)。在实现过程中,需要重写接口中的3个方法。其中,核心的分片逻辑在 doSharding() 方法中处理,可以支持返回多个分片数据源或分片表数据。另外,其他两个prop配置方法的使用方式与上述相同,这里不赘述。

重写 HINT 核心分片方法 doSharding(Collection availableTargetNames, HintShardingValue shardingValue),以实现我们的定制逻辑。该方法接受两个参数:一个是可用目标分库、分表的集合,另一个是 Hint 分片属性对象。

方法内执行流程:我们首先获取 HintManager API 设置的分库或分表的分片值,经过计算后得到合适的分片数据源或分片表集合,然后直接路由到目标位置,无需再关注SQL本身的条件信息。

  1. /**
  2. * 自定义强制路由分片算法
  3. *
  4. * @author 公众号:程序员小富
  5. * @date 2024/03/22 11:02
  6. */
  7. @Slf4j
  8. public class OrderHintCustomAlgorithm implements HintShardingAlgorithm<Long> {
  9. @Override
  10. public Collection<String> doSharding(Collection<String> availableTargetNames, HintShardingValue<Long> hintShardingValue) {
  11. /**
  12. * 获取到设置的分表或者分库的分片值
  13. * 指定分表时的分片值 hintManager.addTableShardingValue("t_order",2L);
  14. * 指定分库时的分片值 hintManager.addDatabaseShardingValue("t_order", 100L);
  15. */
  16. Collection<Long> values = hintShardingValue.getValues();
  17. Collection<String> result = new ArrayList<>();
  18. // 从所有分片表中得到合适的分片表
  19. for (String each : availableTargetNames) {
  20. for (Long value : values) {
  21. Long mod = value % availableTargetNames.size();
  22. if (each.endsWith(String.valueOf(mod))) {
  23. result.add(each);
  24. }
  25. }
  26. }
  27. return result;
  28. }
  29. }

配置算法

配置自定义Hint算法,配置属性包括strategy分片策略类型设置成hintalgorithmClassName自定义Hint算法的实现类全路径。使用该算法时无需指定分片健!

  1. spring:
  2. shardingsphere:
  3. # 具体规则配置
  4. rules:
  5. sharding:
  6. # 分片算法定义
  7. sharding-algorithms:
  8. t_order_database_mod:
  9. type: MOD
  10. props:
  11. sharding-count: 2 # 指定分片数量
  12. # 14、自定义 hint 标准算法
  13. t_order_hint_custom_algorithm:
  14. type: CLASS_BASED
  15. props:
  16. # 分片策略
  17. strategy: hint
  18. # 分片算法类
  19. algorithmClassName: com.shardingsphere_101.algorithm.OrderHintCustomAlgorithm
  20. # 自定义属性
  21. bbbbbb: bbbbbb
  22. tables:
  23. # 逻辑表名称
  24. t_order:
  25. # 数据节点:数据库.分片表
  26. actual-data-nodes: db$->{0..1}.t_order_${0..2}
  27. # 分库策略
  28. database-strategy:
  29. hint:
  30. sharding-algorithm-name: t_order_database_mod
  31. # 分表策略
  32. table-strategy:
  33. hint:
  34. sharding-algorithm-name: t_order_hint_custom_algorithm

测试算法

在执行SQL操作之前,使用 HintManager APIaddDatabaseShardingValueaddTableShardingValue方法来指定分库或分表的分片值,这样算法内通过 HintShardingValue 可以获取到分片值。注意:如果在执行 SQL 时没有使用 HintManager 指定分片值,那么执行SQL将会执行全库表路由

  1. @DisplayName("Hint 自动义分片算法-范围查询")
  2. @Test
  3. public void queryHintTableTest() {
  4. HintManager hintManager = HintManager.getInstance();
  5. // 指定分表时的分片值
  6. hintManager.addTableShardingValue("t_order",2L);
  7. // 指定分库时的分片值
  8. hintManager.addDatabaseShardingValue("t_order", 100L);
  9. QueryWrapper<OrderPo> queryWrapper = new QueryWrapper<OrderPo>()
  10. .eq("user_id", 20).eq("order_id", 10);
  11. List<OrderPo> orderPos = orderMapper.selectList(queryWrapper);
  12. log.info("查询结果:{}", JSON.toJSONString(orderPos));
  13. }

到这关于 shardingsphere-jdbc 的 3种自定义分片算法实现就全部结束了。

总结

本文介绍了 STANDARD、COMPLEX 和 HINT 三种自定义分片算法的实现,和使用过程中一些要注意的事项。ShardingSphere 内置的十几种算法,其实已经可以满足我们绝大部分的业务场景,不过,如果考虑到后续的性能优化和扩展性,定制分片算法是个不错的选择。

全部demo案例 GitHub 地址:https://github.com/chengxy-nds/Springboot-Notebook/tree/master/shardingsphere101/shardingsphere-algorithms

我是小富~ 下期见

原文链接:https://www.cnblogs.com/chengxy-nds/p/18108596

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号