当前位置:   article > 正文

shardingsphere 实战_getcolumnnameandshardingvaluesmap

getcolumnnameandshardingvaluesmap

前言

shardingsphere是一款分库分表的框架,可以简化我们写分库分表的逻辑

需求

  • 存5000万订单数据数据
  • 支持按订单ID,买家ID查询订单
  • 可支持通用配置

技术设计

  1. mysql单表存储超过2000W性能会明显下降,把数据拆分到4张表中,分别在2个库里。库的数量,是需要根据QPS来定,每个库的用户链接数是有上限的,如果QPS很高,需要增加分库数量
  2. 订单号的设计,最后几位采用买家ID,按买家ID分库分表,这样能同时支持按订单ID和卖家ID查询
  3. 每个库都放一个global_config表,作为字典表,存放相同数据

表结构设计

  1. create database oms1;
  2. create database oms2;
  3. CREATE TABLE torder_1 (
  4. order_id BIGINT(20) PRIMARY KEY,
  5. status varchar(20) NOT NULL,
  6. gmt_create datetime NOT NULL,
  7. sku_code VARCHAR(50) NOT NULL,
  8. sku_name VARCHAR(50) NOT NULL,
  9. price DECIMAL(10, 2) NOT NULL,
  10. buyer_id BIGINT(20) NOT NULL
  11. );
  12. CREATE TABLE torder_2 (
  13. order_id BIGINT(20) PRIMARY KEY,
  14. status varchar(20) NOT NULL,
  15. gmt_create datetime NOT NULL,
  16. sku_code VARCHAR(50) NOT NULL,
  17. sku_name VARCHAR(50) NOT NULL,
  18. price DECIMAL(10, 2) NOT NULL,
  19. buyer_id BIGINT(20) NOT NULL
  20. );
  21. CREATE TABLE global_config (
  22. id BIGINT(20) PRIMARY KEY,
  23. status varchar(20) NOT NULL,
  24. gmt_create datetime NOT NULL,
  25. config_key VARCHAR(50) NOT NULL,
  26. config_value VARCHAR(50) NOT NULL,
  27. config_desc VARCHAR(100) NOT NULL
  28. );

二方包

  1. <!-- shardingJDBC核心依赖 -->
  2. <dependency>
  3. <groupId>org.apache.shardingsphere</groupId>
  4. <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
  5. <version>4.1.1</version>
  6. </dependency>
  7. <!--XA 分布式事务 -->
  8. <dependency>
  9. <groupId>org.apache.shardingsphere</groupId>
  10. <artifactId>sharding-transaction-xa-core</artifactId>
  11. <version>4.1.1</version>
  12. </dependency>
  13. <dependency>
  14. <groupId>org.springframework.boot</groupId>
  15. <artifactId>spring-boot-starter</artifactId>
  16. </dependency>
  17. <dependency>
  18. <groupId>org.springframework.boot</groupId>
  19. <artifactId>spring-boot-starter-test</artifactId>
  20. </dependency>
  21. <!-- 数据源连接池 -->
  22. <!--注意不要用这个依赖,他会创建数据源,跟上面ShardingJDBC的SpringBoot集成依赖有冲突 -->
  23. <!-- <dependency>-->
  24. <!-- <groupId>com.alibaba</groupId>-->
  25. <!-- <artifactId>druid-spring-boot-starter</artifactId>-->
  26. <!-- <version>1.1.20</version>-->
  27. <!-- </dependency>-->
  28. <dependency>
  29. <groupId>com.alibaba</groupId>
  30. <artifactId>druid</artifactId>
  31. <version>1.1.20</version>
  32. </dependency>
  33. <!-- mysql连接驱动 -->
  34. <dependency>
  35. <groupId>mysql</groupId>
  36. <artifactId>mysql-connector-java</artifactId>
  37. </dependency>
  38. <!-- mybatisplus依赖 -->
  39. <dependency>
  40. <groupId>com.baomidou</groupId>
  41. <artifactId>mybatis-plus-boot-starter</artifactId>
  42. </dependency>

实体

  1. package com.wgp.oms.dal.entity;
  2. import java.math.BigDecimal;
  3. import java.util.Date;
  4. import com.baomidou.mybatisplus.annotation.TableId;
  5. import lombok.Data;
  6. /**
  7. * @author gangpeng.wgp
  8. * @date 2023/6/27 下午9:58
  9. */
  10. @Data
  11. //特别注意,不能踩坑:表名不能叫order,是mysql关键字。否则shardingsphere会启动失败
  12. public class Torder {
  13. @TableId
  14. private Long orderId;
  15. private String status;
  16. private Date gmtCreate;
  17. private String skuCode;
  18. private String skuName;
  19. private BigDecimal price;
  20. private Long buyerId;
  21. }

数据源

在application.properties中定义2个数据源m1、m2,分别对应2个库oms1、oms2

  1. #各种分库分表策略
  2. #配置多个数据源
  3. spring.shardingsphere.datasource.names=m1,m2
  4. spring.shardingsphere.datasource.m1.type=com.alibaba.druid.pool.DruidDataSource
  5. spring.shardingsphere.datasource.m1.driver-class-name=com.mysql.cj.jdbc.Driver
  6. spring.shardingsphere.datasource.m1.url=jdbc:mysql://localhost:3306/oms1?serverTimezone=GMT%2B8
  7. spring.shardingsphere.datasource.m1.username=root
  8. spring.shardingsphere.datasource.m1.password=12345678
  9. spring.shardingsphere.datasource.m2.type=com.alibaba.druid.pool.DruidDataSource
  10. spring.shardingsphere.datasource.m2.driver-class-name=com.mysql.cj.jdbc.Driver
  11. spring.shardingsphere.datasource.m2.url=jdbc:mysql://localhost:3306/oms2?serverTimezone=GMT%2B8
  12. spring.shardingsphere.datasource.m2.username=root
  13. spring.shardingsphere.datasource.m2.password=12345678
  14. #真实表分布,分库,分表
  15. spring.shardingsphere.sharding.tables.torder.actual-data-nodes=m$->{1..2}.torder_$->{1..2}

分片策略

分片策略总共有4种:

1、inline分片策略:支持按单列分片
2、standard标准分片策略:支持按单列分片,SQL语句中有>,>=, <=,<,=,IN 和 BETWEEN AND 操作符
3、complex复杂分片策略:支持多key,SQL语句中有>,>=, <=,<,=,IN 和 BETWEEN AND 操作符4、hint强制路由策略:不按SQL来路由,由代码强制指定

inline分片策略

这里的分库分表表达式,就是把数据能均衡的离散到2个库4张表里

  1. #inline分片策略,支持单单key,
  2. spring.shardingsphere.sharding.tables.torder.database-strategy.inline.sharding-column=order_id
  3. spring.shardingsphere.sharding.tables.torder.database-strategy.inline.algorithm-expression=m$->{((order_id%4+1).intdiv(3))+1}
  4. spring.shardingsphere.sharding.tables.torder.table-strategy.inline.sharding-column=order_id
  5. spring.shardingsphere.sharding.tables.torder.table-strategy.inline.algorithm-expression=torder_$->{((order_id%4+1)%2)+1}

stardard分片策略

  1. #standard标准分片策略, 支持单key,SQL 语句中有>,>=, <=,<,=,IN 和 BETWEEN AND 操作符
  2. spring.shardingsphere.sharding.tables.torder.table-strategy.standard.sharding-column=order_id
  3. spring.shardingsphere.sharding.tables.torder.table-strategy.standard.precise-algorithm-class-name=com.wgp.oms.dal.algorithem.MyPreciseTableShardingAlgorithm
  4. spring.shardingsphere.sharding.tables.torder.table-strategy.standard.range-algorithm-class-name=com.wgp.oms.dal.algorithem.MyRangeTableShardingAlgorithm
  5. spring.shardingsphere.sharding.tables.torder.database-strategy.standard.sharding-column=order_id
  6. spring.shardingsphere.sharding.tables.torder.database-strategy.standard.precise-algorithm-class-name=com.wgp.oms.dal.algorithem.MyPreciseDBShardingAlgorithm
  7. spring.shardingsphere.sharding.tables.torder.database-strategy.standard.range-algorithm-class-name=com.wgp.oms.dal.algorithem.MyRangeDBShardingAlgorithm
  1. package com.wgp.oms.dal.algorithem;
  2. import java.util.Collection;
  3. import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
  4. import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
  5. /**
  6. * @author : gangpeng.wgp
  7. * @date : 2023/6/28
  8. */
  9. public class MyPreciseDBShardingAlgorithm implements PreciseShardingAlgorithm<Long> {
  10. @Override
  11. public String doSharding(Collection<String> databaseNames, PreciseShardingValue<Long> shardingValue) {
  12. for (String databaseName : databaseNames) {
  13. //m$->{((order_id%4+1).intdiv(3))+1}
  14. String value = "m" + (((shardingValue.getValue() % 4 + 1)/3) +1);
  15. if (databaseName.equalsIgnoreCase(value)) {
  16. return databaseName;
  17. }
  18. }
  19. throw new IllegalArgumentException();
  20. }
  21. }
  22. package com.wgp.oms.dal.algorithem;
  23. import java.util.Collection;
  24. import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
  25. import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
  26. /**
  27. * @author : gangpeng.wgp
  28. * @date : 2023/6/28
  29. */
  30. public class MyPreciseTableShardingAlgorithm implements PreciseShardingAlgorithm<Long> {
  31. @Override
  32. public String doSharding(Collection<String> tableNames, PreciseShardingValue<Long> shardingValue) {
  33. for (String tableName : tableNames) {
  34. //torder_$->{((order_id%4+1)%2)+1}
  35. String value = "torder_" + (((shardingValue.getValue() % 4 + 1) % 2) + 1);
  36. if (tableName.equalsIgnoreCase(value)) {
  37. return tableName;
  38. }
  39. }
  40. throw new IllegalArgumentException();
  41. }
  42. }

complex分片策略

  1. spring.shardingsphere.sharding.tables.torder.table-strategy.complex.sharding-columns= order_id, buyer_id
  2. spring.shardingsphere.sharding.tables.torder.table-strategy.complex.algorithm-class-name=com.wgp.oms.dal.algorithem.MyComplexTableShardingAlgorithm
  3. spring.shardingsphere.sharding.tables.torder.database-strategy.complex.sharding-columns=order_id, buyer_id
  4. spring.shardingsphere.sharding.tables.torder.database-strategy.complex.algorithm-class-name=com.wgp.oms.dal.algorithem.MyComplexDSShardingAlgorithm
  1. package com.wgp.oms.dal.algorithem;
  2. import java.util.Collection;
  3. import org.apache.shardingsphere.api.sharding.complex.ComplexKeysShardingAlgorithm;
  4. import org.apache.shardingsphere.api.sharding.complex.ComplexKeysShardingValue;
  5. public class MyComplexDSShardingAlgorithm implements ComplexKeysShardingAlgorithm<Long> {
  6. /**
  7. * @param availableTargetNames 目标数据源 或者 表 的值。
  8. * @param shardingValue logicTableName逻辑表名 columnNameAndShardingValuesMap 分片列的精确值集合。 columnNameAndRangeValuesMap 分片列的范围值集合
  9. * @return
  10. */
  11. @Override
  12. public Collection<String> doSharding(Collection<String> availableTargetNames, ComplexKeysShardingValue<Long> shardingValue) {
  13. return availableTargetNames;
  14. }
  15. }
  16. package com.wgp.oms.dal.algorithem;
  17. import java.util.ArrayList;
  18. import java.util.Collection;
  19. import java.util.List;
  20. import org.apache.commons.collections4.CollectionUtils;
  21. import org.apache.shardingsphere.api.sharding.complex.ComplexKeysShardingAlgorithm;
  22. import org.apache.shardingsphere.api.sharding.complex.ComplexKeysShardingValue;
  23. /**
  24. * 复合分片
  25. * 使用场景:需要按多个字段进行分表,比如:既可以按order_id分表,也可以按buyer_id分表
  26. * @author : gangpeng.wgp
  27. * @date : 2023/6/28
  28. */
  29. public class MyComplexTableShardingAlgorithm implements ComplexKeysShardingAlgorithm<Long> {
  30. @Override
  31. public Collection<String> doSharding(Collection<String> availableTargetNames, ComplexKeysShardingValue<Long> shardingValue) {
  32. Collection<Long> orderIdList = shardingValue.getColumnNameAndShardingValuesMap().get("order_id");
  33. Collection<Long> buyerIdList = shardingValue.getColumnNameAndShardingValuesMap().get("buyer_id");
  34. List<String> result = new ArrayList<>();
  35. if(CollectionUtils.isNotEmpty(orderIdList)){
  36. //实现自定义分片逻辑
  37. for (Long id : orderIdList) {
  38. //torder_$->{((order_id%4+1)%2)+1}
  39. String value = "torder_" + (((id % 4 + 1) % 2) + 1);
  40. result.add(value);
  41. }
  42. }
  43. if(CollectionUtils.isNotEmpty(buyerIdList)){
  44. //实现自定义分片逻辑
  45. for (Long id : buyerIdList) {
  46. //torder_$->{((buyer_id%4+1)%2)+1}
  47. String value = "torder_" + (((id % 4 + 1) % 2) + 1);
  48. if(!result.contains(value)){
  49. result.add(value);
  50. }
  51. }
  52. }
  53. return result;
  54. }
  55. }

hint分片策略

spring.shardingsphere.sharding.tables.torder.database-strategy.hint.algorithm-class-name=com.wgp.oms.dal.algorithem.MyHintTableShardingAlgorithm
  1. package com.wgp.oms.dal.algorithem;
  2. import java.util.ArrayList;
  3. import java.util.Collection;
  4. import org.apache.shardingsphere.api.sharding.hint.HintShardingAlgorithm;
  5. import org.apache.shardingsphere.api.sharding.hint.HintShardingValue;
  6. public class MyHintTableShardingAlgorithm implements HintShardingAlgorithm<Long> {
  7. @Override
  8. public Collection<String> doSharding(
  9. Collection<String> availableTargetNames,
  10. HintShardingValue<Long> shardingValue) {
  11. // 添加分库或分表路由逻辑
  12. Collection<String> result = new ArrayList<>();
  13. for (String each : availableTargetNames) { //代表:分片目标,对哪些数据库、表分片。如果是对分库路由,表示ds0,ds1;
  14. for (Long value : shardingValue.getValues()) { // 代表:分片值; 可以HintManager设置多个分片值,所以是个集合。
  15. if (each.endsWith(String.valueOf(value ))) { // 分库路由
  16. result.add(each );
  17. }
  18. }
  19. }
  20. return result;
  21. }
  22. }
  1. /**
  2. * 强制指定库路由查询
  3. *
  4. * 注意:需在application.properties中开启hint查询策略,且注释掉其他查询策略
  5. */
  6. @Test
  7. public void queryOrderListWithHit() {
  8. //注意:入参的类型需要和MyHintTableShardingAlgorithm中的泛型类型一致,否则会报错
  9. HintManager.getInstance().setDatabaseShardingValue(1L);
  10. List<Torder> orderList = orderMapper.queryAllOrder();
  11. assertNotNull(orderList);
  12. orderList.forEach(System.out::println);
  13. System.out.println("结果数:" + orderList.size());
  14. }

单测

  1. package com.wgp.oms.service;
  2. import java.math.BigDecimal;
  3. import java.util.Arrays;
  4. import java.util.Date;
  5. import java.util.HashMap;
  6. import java.util.List;
  7. import java.util.Map;
  8. import javax.annotation.Resource;
  9. import com.wgp.oms.dal.entity.Torder;
  10. import com.wgp.oms.dal.mapper.OrderMapper;
  11. import org.apache.shardingsphere.api.hint.HintManager;
  12. import org.junit.Test;
  13. import org.junit.runner.RunWith;
  14. import org.springframework.boot.test.context.SpringBootTest;
  15. import org.springframework.test.context.junit4.SpringRunner;
  16. import static org.junit.jupiter.api.Assertions.assertEquals;
  17. import static org.junit.jupiter.api.Assertions.assertNotNull;
  18. /**
  19. * @author gangpeng.wgp
  20. * @date 2023/6/27 下午10:40
  21. */
  22. @RunWith(SpringRunner.class)
  23. @SpringBootTest
  24. public class TorderServiceTest {
  25. @Resource
  26. private OrderService orderService;
  27. @Resource
  28. private OrderMapper orderMapper;
  29. @Test
  30. public void queryOrderById() {
  31. Torder order = orderService.queryOrderById(1L);
  32. assertNotNull(order);
  33. System.out.println(order);
  34. }
  35. @Test
  36. public void queryOrderList() {
  37. List<Torder> orderList = orderMapper.queryAllOrder();
  38. assertNotNull(orderList);
  39. orderList.forEach(System.out::println);
  40. }
  41. @Test
  42. public void queryByOrderIdRange() {
  43. List<Torder> orderList = orderService.queryByOrderIdRange(1L, 2L);
  44. assertNotNull(orderList);
  45. orderList.forEach(System.out::println);
  46. }
  47. @Test
  48. public void queryByIds() {
  49. List<Torder> orderList = orderMapper.queryByIds(Arrays.asList(1L, 5L));
  50. assertNotNull(orderList);
  51. orderList.forEach(System.out::println);
  52. }
  53. /**
  54. * 配合Complex策略
  55. */
  56. @Test
  57. public void queryByBuyerIds() {
  58. List<Torder> orderList = orderMapper.queryByBuyerIds(Arrays.asList(1L, 5L));
  59. assertNotNull(orderList);
  60. orderList.forEach(System.out::println);
  61. }
  62. /**
  63. * 强制指定库路由查询
  64. *
  65. * 注意:需在application.properties中开启hint查询策略,且注释掉其他查询策略
  66. */
  67. @Test
  68. public void queryOrderListWithHit() {
  69. //注意:入参的类型需要和MyHintTableShardingAlgorithm中的泛型类型一致,否则会报错
  70. HintManager.getInstance().setDatabaseShardingValue(1L);
  71. List<Torder> orderList = orderMapper.queryAllOrder();
  72. assertNotNull(orderList);
  73. orderList.forEach(System.out::println);
  74. System.out.println("结果数:" + orderList.size());
  75. }
  76. //插入数据会进行分片
  77. @Test
  78. public void addOrder() {
  79. delete();
  80. for (int i = 1; i < 100; i++) {
  81. Torder c = new Torder();
  82. c.setBuyerId((long)i);
  83. c.setGmtCreate(new Date());
  84. c.setStatus("INIT");
  85. c.setPrice(BigDecimal.valueOf(1000.10));
  86. c.setSkuCode("sku_" + i);
  87. c.setSkuName("商品_" + i);
  88. c.setOrderId((long)i);
  89. orderMapper.insert(c);
  90. }
  91. }
  92. private void delete() {
  93. Map<String, Object> param = new HashMap<>();
  94. //param.putIfAbsent("cname", "java");
  95. orderMapper.deleteByMap(param);
  96. }
  97. @Test
  98. public void update() {
  99. Torder o = new Torder();
  100. o.setOrderId(1L);
  101. o.setPrice(BigDecimal.valueOf(9999.00));
  102. int cnt = orderMapper.updateById(o);
  103. assertEquals(cnt, 1);
  104. Torder order = orderService.queryOrderById(1L);
  105. assertNotNull(order);
  106. assertEquals(9999.00D, order.getPrice().doubleValue());
  107. }
  108. }

字典表

  1. #广播表配置(字典表,每个库都有一个,表名一样,数据一样)
  2. spring.shardingsphere.sharding.broadcast-tables=global_config
  3. spring.shardingsphere.sharding.tables.global_config.key-generator.column=id
  4. spring.shardingsphere.sharding.tables.global_config.key-generator.type=SNOWFLAKE
  1. package com.wgp.oms.dal.entity;
  2. import java.util.Date;
  3. import com.baomidou.mybatisplus.annotation.TableId;
  4. import lombok.Data;
  5. /**
  6. * 全局字典表
  7. * @author gangpeng.wgp
  8. * @date 2023/6/27 下午9:58
  9. */
  10. @Data
  11. public class GlobalConfig {
  12. @TableId
  13. private Long id;
  14. private String status;
  15. private String configKey;
  16. private String configValue;
  17. private String configDesc;
  18. private Date gmtCreate;
  19. }

单测

  1. package com.wgp.oms.service;
  2. import java.util.Date;
  3. import java.util.HashMap;
  4. import java.util.Map;
  5. import javax.annotation.Resource;
  6. import com.wgp.oms.dal.entity.GlobalConfig;
  7. import com.wgp.oms.dal.mapper.GlobalConfigMapper;
  8. import org.junit.Test;
  9. import org.junit.runner.RunWith;
  10. import org.springframework.boot.test.context.SpringBootTest;
  11. import org.springframework.test.context.junit4.SpringRunner;
  12. import static org.junit.jupiter.api.Assertions.assertNotNull;
  13. /**
  14. * 注意:这个测试类,不能放到com.wgp.oms.dal.mapper包下,否则会被当做mybatis的mapper
  15. *
  16. * @author gangpeng.wgp
  17. * @date 2023/6/28 3:11 下午
  18. */
  19. @RunWith(SpringRunner.class)
  20. @SpringBootTest
  21. public class GlobalConfigTest {
  22. @Resource
  23. private GlobalConfigMapper globalConfigMapper;
  24. @Test
  25. public void selectById() {
  26. GlobalConfig order = globalConfigMapper.selectById(1L);
  27. assertNotNull(order);
  28. System.out.println(order);
  29. }
  30. //插入数据会进行分片
  31. @Test
  32. public void addOrder() {
  33. delete();
  34. for (int i = 1; i < 100; i++) {
  35. GlobalConfig c = new GlobalConfig();
  36. c.setStatus("INIT");
  37. c.setConfigKey("conf_" + i);
  38. c.setConfigValue("value_" + i);
  39. c.setConfigDesc("这是配置" + i);
  40. c.setGmtCreate(new Date());
  41. globalConfigMapper.insert(c);
  42. }
  43. }
  44. private void delete() {
  45. Map<String, Object> param = new HashMap<>();
  46. //param.putIfAbsent("cname", "java");
  47. globalConfigMapper.deleteByMap(param);
  48. }
  49. }

注意事项

  • 表名一定不要和mysql的关键字冲突!比如:订单表,开始起名为order,后来应用一直报错,浪费了半天时间
  • shardingsphere没有禁止全表扫描的功能,如果没有命中分片规则,可能会全表扫描,这在生产环境是致命的
  • 对于使用自增主键的表,需要注意在分片键上不能使用该字段。因为自增主键在分布式系统下无法保证唯一性。

代码地址

GitHub - gangpwei/wgp-oms

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/空白诗007/article/detail/801595
推荐阅读
相关标签
  

闽ICP备14008679号