• SpringBoot使用sharding-jdbc分库分表


    一、前言

      一般来说,随着业务的发展数据库的数据量会越来越多,当单表数据超过上千万时执行一些查询sql语句就会遇到性能问题。一开始可以用主从复制读写分离来减轻db压力,但是后面还是要用分库分表把数据进行水平拆分和垂直拆分。
      实现分库分表目前我知道的方式有两种,第一种是使用mycat中间件实现,第二种是使用sharding-jdbc实现。相比较而言,sharding-jdbc引入一个jar包即可使用更轻量级一些,它们之间的优缺点这里也不做比较,有兴趣的可以自己搜索相关资料。
      不清楚分库分表原理的可以参考这篇博客,数据库之分库分表-垂直?水平?

    二、使用当当网的sharding-jdbc分库分表

    2.1新建SpringBoot项目

    新建项目sharding-jdbc-first,并在pom文件添加如下内容:

    1. <parent> 
    2. <groupId>org.springframework.boot</groupId> 
    3. <artifactId>spring-boot-starter-parent</artifactId> 
    4. <version>1.5.16.RELEASE</version> 
    5. <relativePath/> <!-- lookup parent from repository --> 
    6. </parent> 
    7.  
    8. <properties> 
    9. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 
    10. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> 
    11. <java.version>1.8</java.version> 
    12. </properties> 
    13.  
    14. <dependencies> 
    15. <dependency> 
    16. <groupId>org.springframework.boot</groupId> 
    17. <artifactId>spring-boot-starter-data-jpa</artifactId> 
    18. </dependency> 
    19. <dependency> 
    20. <groupId>org.springframework.boot</groupId> 
    21. <artifactId>spring-boot-starter-web</artifactId> 
    22. </dependency> 
    23.  
    24. <dependency> 
    25. <groupId>mysql</groupId> 
    26. <artifactId>mysql-connector-java</artifactId> 
    27. <scope>runtime</scope> 
    28. </dependency> 
    29. <dependency> 
    30. <groupId>org.springframework.boot</groupId> 
    31. <artifactId>spring-boot-starter-test</artifactId> 
    32. <scope>test</scope> 
    33. </dependency> 
    34. <dependency> 
    35. <groupId>com.dangdang</groupId> 
    36. <artifactId>sharding-jdbc-core</artifactId> 
    37. <version>1.4.2</version> 
    38. </dependency> 
    39. <dependency> 
    40. <groupId>com.alibaba</groupId> 
    41. <artifactId>druid</artifactId> 
    42. <version>1.0.12</version> 
    43. </dependency> 
    44.  
    45. <dependency> 
    46. <groupId>com.dangdang</groupId> 
    47. <artifactId>sharding-jdbc-self-id-generator</artifactId> 
    48. <version>1.4.2</version> 
    49. </dependency> 
    50.  
    51.  
    52. </dependencies> 

    目前好像不支持SpringBoot2.0以上的版本。

    2.2编写实体类及建库建表

    目标:
    db0
    ├── t_order_0 user_id为偶数 order_id为偶数
    ├── t_order_1 user_id为偶数 order_id为奇数
    db1
    ├── t_order_0 user_id为奇数 order_id为偶数
    ├── t_order_1 user_id为奇数 order_id为奇数


    1. 创建两个数据库 ds_0 和 ds_1,编码类型UTF-8。
    2. 每个库分表创建两个表t_order_0和t_order_1,sql语句如下:

      DROP TABLE IF EXISTS t_order_0;
      CREATE TABLE t_order_0 (
      order_id bigint(20) NOT NULL,
      user_id bigint(20) NOT NULL,
      PRIMARY KEY (order_id)
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
    3. 新建类Order,代码如下
    1. package cn.sp.bean; 
    2.  
    3. import javax.persistence.Entity; 
    4. import javax.persistence.Id; 
    5. import javax.persistence.Table; 
    6.  
    7. /** 
    8. * Created by 2YSP on 2018/9/23. 
    9. */ 
    10. @Entity 
    11. @Table(name="t_order"
    12. public class Order
    13. @Id 
    14. private Long orderId; 
    15.  
    16. private Long userId; 
    17.  
    18. public Long getOrderId()
    19. return orderId; 
    20.  
    21. public void setOrderId(Long orderId)
    22. this.orderId = orderId; 
    23.  
    24. public Long getUserId()
    25. return userId; 
    26.  
    27. public void setUserId(Long userId)
    28. this.userId = userId; 
    29.  

    这里需要注意 @Id注解不要导错包,之前我就遇到过这个问题。
    4.配置文件application.yml

    1. server: 
    2. port: 8000 
    3. spring: 
    4. jpa: 
    5. database: mysql 
    6. show-sql: true 
    7. hibernate: 
    8. ## 自己建表 
    9. ddl-auto: none 
    10. application: 
    11. name: sharding-jdbc-first 

    这里要注意的是spring-data-jpa默认会自己建表,这里我们要手动建立,所以需要将ddl-auto属性设置为none

    2.3自定义分库分表算法

    1.分库算法类需要实现SingleKeyDatabaseShardingAlgorithm<T>接口,这是一个泛型接口,T代表分库依据的字段的类型,比如我们根据userId%2来分库,userId是Long型的,这里的T就是Long。

    1. public class ModuloDatabaseShardingAlgorithm implements SingleKeyDatabaseShardingAlgorithm<Long>
    2. @Override 
    3. public String doEqualSharding(Collection<String> availableDatabaseNames, ShardingValue<Long> shardingValue)
    4. for(String databaseName : availableDatabaseNames){ 
    5. if (databaseName.endsWith(shardingValue.getValue() % 2 + "")){ 
    6.  
    7. return databaseName; 
    8. throw new IllegalArgumentException(); 
    9.  
    10. @Override 
    11. public Collection<String> doInSharding(Collection<String> availableDatabaseNames, ShardingValue<Long> shardingValue)
    12. Collection<String> result = new LinkedHashSet<>(availableDatabaseNames.size()); 
    13. for(Long value : shardingValue.getValues()){ 
    14. for(String name : availableDatabaseNames){ 
    15. if (name.endsWith(value%2 + "")){ 
    16. result.add(name); 
    17. return result; 
    18.  
    19. @Override 
    20. public Collection<String> doBetweenSharding(Collection<String> availableDatabaseNames, ShardingValue<Long> shardingValue)
    21. Collection<String> result = new LinkedHashSet<>(availableDatabaseNames.size()); 
    22. Range<Long> range = shardingValue.getValueRange(); 
    23. for(Long i = range.lowerEndpoint() ; i < range.upperEndpoint();i++){ 
    24. for(String each : availableDatabaseNames){ 
    25. if (each.endsWith( i % 2+"")){ 
    26. result.add(each); 
    27.  
    28. return result; 
    29.  

    2.分表算法类需要实现SingleKeyTableShardingAlgorithm<T>接口。

    1. /** 
    2. * 表分片算法 
    3. * Created by 2YSP on 2018/9/23. 
    4. */ 
    5. public class ModuloTableShardingAlgorithm implements SingleKeyTableShardingAlgorithm<Long>
    6.  
    7. /** 
    8. * select * from t_order from t_order where order_id = 11 
    9. * └── SELECT * FROM t_order_1 WHERE order_id = 11 
    10. * select * from t_order from t_order where order_id = 44 
    11. * └── SELECT * FROM t_order_0 WHERE order_id = 44 
    12. */ 
    13. @Override 
    14. public String doEqualSharding(Collection<String> tableNames, ShardingValue<Long> shardingValue)
    15. for (String tableName : tableNames) { 
    16. if (tableName.endsWith(shardingValue.getValue() % 2 + "")) { 
    17. return tableName; 
    18.  
    19. throw new IllegalArgumentException(); 
    20.  
    21. /** 
    22. * select * from t_order from t_order where order_id in (11,44) 
    23. * ├── SELECT * FROM t_order_0 WHERE order_id IN (11,44) 
    24. * └── SELECT * FROM t_order_1 WHERE order_id IN (11,44) 
    25. * select * from t_order from t_order where order_id in (11,13,15) 
    26. * └── SELECT * FROM t_order_1 WHERE order_id IN (11,13,15) 
    27. * select * from t_order from t_order where order_id in (22,24,26) 
    28. * └──SELECT * FROM t_order_0 WHERE order_id IN (22,24,26) 
    29. */ 
    30. @Override 
    31. public Collection<String> doInSharding(Collection<String> tableNames, ShardingValue<Long> shardingValue)
    32. Collection<String> result = new LinkedHashSet<>(tableNames.size()); 
    33. for (Long value : shardingValue.getValues()) { 
    34. for (String table : tableNames) { 
    35. if (table.endsWith(value % 2 + "")) { 
    36. result.add(table); 
    37. return result; 
    38.  
    39. /** 
    40. * select * from t_order from t_order where order_id between 10 and 20 
    41. * ├── SELECT * FROM t_order_0 WHERE order_id BETWEEN 10 AND 20 
    42. * └── SELECT * FROM t_order_1 WHERE order_id BETWEEN 10 AND 20 
    43. */ 
    44. @Override 
    45. public Collection<String> doBetweenSharding(Collection<String> tableNames, ShardingValue<Long> shardingValue)
    46. Collection<String> result = new LinkedHashSet<>(tableNames.size()); 
    47. Range<Long> range = shardingValue.getValueRange(); 
    48. for (Long i = range.lowerEndpoint(); i < range.upperEndpoint(); i++) { 
    49. for (String each : tableNames) { 
    50. if (each.endsWith(i % 2 + "")) { 
    51. result.add(each); 
    52.  
    53. return result; 

    2.4配置数据源

    数据源配置类DataSourceConfig

    1. @Configuration 
    2. public class DataSourceConfig
    3. @Bean 
    4. public IdGenerator getIdGenerator()
    5. return new CommonSelfIdGenerator(); 
    6.  
    7. @Bean 
    8. public DataSource getDataSource()
    9. return buildDataSource(); 
    10.  
    11.  
    12. private DataSource buildDataSource()
    13. //1.设置分库映射 
    14. Map<String, DataSource> dataSourceMap = new HashMap<>(2); 
    15. dataSourceMap.put("ds_0", createDataSource("ds_0")); 
    16. dataSourceMap.put("ds_1", createDataSource("ds_1")); 
    17. //设置默认db为ds_0,也就是为那些没有配置分库分表策略的指定的默认库 
    18. //如果只有一个库,也就是不需要分库的话,map里只放一个映射就行了,只有一个库时不需要指定默认库, 
    19. // 但2个及以上时必须指定默认库,否则那些没有配置策略的表将无法操作数据 
    20. DataSourceRule rule = new DataSourceRule(dataSourceMap, "ds_0"); 
    21.  
    22. //2.设置分表映射,将t_order_0和t_order_1两个实际的表映射到t_order逻辑表 
    23. TableRule orderTableRule = TableRule.builder("t_order"
    24. .actualTables(Arrays.asList("t_order_0", "t_order_1")) 
    25. .dataSourceRule(rule) 
    26. .build(); 
    27. //3.具体的分库分表策略 
    28. ShardingRule shardingRule = ShardingRule.builder() 
    29. .dataSourceRule(rule) 
    30. .tableRules(Arrays.asList(orderTableRule)) 
    31. .databaseShardingStrategy(new DatabaseShardingStrategy("user_id", new ModuloDatabaseShardingAlgorithm())) 
    32. .tableShardingStrategy(new TableShardingStrategy("order_id", new ModuloTableShardingAlgorithm())) 
    33. .build(); 
    34.  
    35. DataSource dataSource = ShardingDataSourceFactory.createDataSource(shardingRule); 
    36. return dataSource; 
    37.  
    38. private static DataSource createDataSource(String dataSourceName)
    39. //使用druid连接数据库 
    40. DruidDataSource druidDataSource = new DruidDataSource(); 
    41. druidDataSource.setDriverClassName("com.mysql.jdbc.Driver"); 
    42. druidDataSource.setUrl(String.format("jdbc:mysql://localhost:3306/%s?characterEncoding=utf-8", dataSourceName)); 
    43. druidDataSource.setUsername("root"); 
    44. druidDataSource.setPassword("1234"); 
    45. return druidDataSource; 

    这里的一些配置信息url,username,password等可以优化下,从配置文件读取。

    2.5测试

    1.新建OrderRepository

    1. public interface OrderRepository extends CrudRepository<Order,Long>
    2.  

    2.controller层

    1. /** 
    2. * Created by 2YSP on 2018/9/23. 
    3. */ 
    4. @RestController 
    5. @RequestMapping("/order"
    6. public class OrderController
    7.  
    8. @Autowired 
    9. private OrderRepository repository; 
    10.  
    11. @Autowired 
    12. private IdGenerator idGenerator; 
    13.  
    14. @RequestMapping("/add"
    15. public String add()
    16. for(int i=0;i<10;i++){ 
    17. Order order = new Order(); 
    18. order.setOrderId((long) i); 
    19. order.setUserId((long) i); 
    20. repository.save(order); 
    21. // Order order = new Order(); 
    22. // order.setUserId(1L); 
    23. // order.setOrderId(idGenerator.generateId().longValue()); 
    24. // repository.save(order); 
    25. return "success"
    26.  
    27. @RequestMapping("/query"
    28. public List<Order> queryAll()
    29. List<Order> orders = (List<Order>) repository.findAll(); 
    30. return orders; 
    31.  

    3.访问http://localhost:8080/order/add,即可在数据库ds_0,ds_1发现多了一些数据。
    访问http://localhost:8080/order/query可以查询刚刚添加的订单数据。
    完整代码地址:https://github.com/2YSP/sharding-jdbc-first

    三、使用sharding-jdbc-spring-boot-starter分库分表

    3.1引入依赖

    因为我的SpringBoot是2.X版本,所以引入最新的依赖。因为目前的maven仓库(包括阿里仓库)还没有对应的jar,需要自己去github下载源代码,然后执行 mvn clean install打包到本地maven仓库。

    1. <dependency> 
    2. <groupId>io.shardingsphere</groupId> 
    3. <artifactId>sharding-jdbc-spring-boot-starter</artifactId> 
    4. <version>3.0.0.M4</version> 
    5. </dependency> 

    3.2SpringBoot配置

    在application.properties文件添加如下内容:

    ##########分库分表配置##########
    sharding.jdbc.datasource.names=ds0,ds1
    ## 这里使用阿里的Druid连接池
    sharding.jdbc.datasource.ds0.type=com.alibaba.druid.pool.DruidDataSource
    sharding.jdbc.datasource.ds0.driver-class-name=com.mysql.jdbc.Driver
    sharding.jdbc.datasource.ds0.url=jdbc:mysql://localhost:3306/ds_0
    sharding.jdbc.datasource.ds0.username=root
    sharding.jdbc.datasource.ds0.password=1234
    
    sharding.jdbc.datasource.ds1.type=com.alibaba.druid.pool.DruidDataSource
    sharding.jdbc.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver
    sharding.jdbc.datasource.ds1.url=jdbc:mysql://localhost:3306/ds_1
    sharding.jdbc.datasource.ds1.username=root
    sharding.jdbc.datasource.ds1.password=1234
    
    ##默认的分库策略:user_id为奇数-->数据库ds_1,user_id为偶数-->数据库ds_0
    sharding.jdbc.config.sharding.default-database-strategy.inline.sharding-column=user_id
    sharding.jdbc.config.sharding.default-database-strategy.inline.algorithm-expression=ds$->{user_id % 2}
    ## 这里的t_order是逻辑表,由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持inline表达式
    sharding.jdbc.config.sharding.tables.t_order.actual-data-nodes=ds$->{0..1}.t_order_$->{0..1}
    ## 行表达式分片策略
    sharding.jdbc.config.sharding.tables.t_order.table-strategy.inline.sharding-column=order_id
    sharding.jdbc.config.sharding.tables.t_order.table-strategy.inline.algorithm-expression=t_order_$->{order_id % 2}
    
    

    这里还可以用Java配置,Yaml配置来代替,感兴趣的话可以访问github地址了解更多,上面有对应的中文文档。

    四、总结

    在分库分表的时候要根据实际情况来决定根据哪个字段来分(不一定都是主键),需要分几个库几张表。
    分库分表后遇到的问题:
    1.不能像以前一样使用数据库自增的主键了,会出现主键重复的问题(可以使用分布式主键来代替)。
    2.不支持一些关键字。
    3.在做一些统计查询的时候也更加困难,那时候可能需要引入搜索引擎ES了。
    4.之前以为sharding-jdbc不支持分页操作,那天测试了下竟然可以。

  • 相关阅读:
    iOS.访问通讯录.01.读取联系人信息
    iOS.定位服务与地图应用.07.调用谷歌Web地图
    iOS.定位服务与地图应用.06.调用iOS苹果地图
    iOS.定位服务与地图应用.05.跟踪用户位置变化
    iOS.定位服务与地图应用.04.使用iOS苹果地图
    iOS.定位服务与地图应用.03.地理信息编码查询
    iOS.定位服务与地图应用.02.地理信息反编码
    Java中的基本数据类型在内存所占字节
    APK文件反编译
    基于Android系统开发的简易音乐播放器
  • 原文地址:https://www.cnblogs.com/2YSP/p/9746981.html
Copyright © 2020-2023  润新知