码迷,mamicode.com
首页 > 数据库 > 详细

SpringBoot使用sharding-jdbc分库分表

时间:2018-10-06 13:27:52      阅读:379      评论:0      收藏:0      [点我收藏+]

标签:config   number   sha   create   asList   dha   func   添加   一个   

一、前言

  一般来说,随着业务的发展数据库的数据量会越来越多,当单表数据超过上千万时执行一些查询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.  
  22. public void setOrderId(Long orderId) { 
  23. this.orderId = orderId; 
  24. } 
  25.  
  26. public Long getUserId() { 
  27. return userId; 
  28. } 
  29.  
  30. public void setUserId(Long userId) { 
  31. this.userId = userId; 
  32. } 
  33. } 
  34.  

这里需要注意 @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. } 
  9. } 
  10. throw new IllegalArgumentException(); 
  11. } 
  12.  
  13. @Override 
  14. public Collection<String> doInSharding(Collection<String> availableDatabaseNames, ShardingValue<Long> shardingValue) { 
  15. Collection<String> result = new LinkedHashSet<>(availableDatabaseNames.size()); 
  16. for(Long value : shardingValue.getValues()){ 
  17. for(String name : availableDatabaseNames){ 
  18. if (name.endsWith(value%2 + "")){ 
  19. result.add(name); 
  20. } 
  21. } 
  22. } 
  23. return result; 
  24. } 
  25.  
  26. @Override 
  27. public Collection<String> doBetweenSharding(Collection<String> availableDatabaseNames, ShardingValue<Long> shardingValue) { 
  28. Collection<String> result = new LinkedHashSet<>(availableDatabaseNames.size()); 
  29. Range<Long> range = shardingValue.getValueRange(); 
  30. for(Long i = range.lowerEndpoint() ; i < range.upperEndpoint();i++){ 
  31. for(String each : availableDatabaseNames){ 
  32. if (each.endsWith( i % 2+"")){ 
  33. result.add(each); 
  34. } 
  35. } 
  36. } 
  37.  
  38. return result; 
  39. } 
  40. } 
  41.  

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. } 
  20.  
  21. throw new IllegalArgumentException(); 
  22. } 
  23.  
  24. /** 
  25. * select * from t_order from t_order where order_id in (11,44) 
  26. * ├── SELECT * FROM t_order_0 WHERE order_id IN (11,44) 
  27. * └── SELECT * FROM t_order_1 WHERE order_id IN (11,44) 
  28. * select * from t_order from t_order where order_id in (11,13,15) 
  29. * └── SELECT * FROM t_order_1 WHERE order_id IN (11,13,15) 
  30. * select * from t_order from t_order where order_id in (22,24,26) 
  31. * └──SELECT * FROM t_order_0 WHERE order_id IN (22,24,26) 
  32. */ 
  33. @Override 
  34. public Collection<String> doInSharding(Collection<String> tableNames, ShardingValue<Long> shardingValue) { 
  35. Collection<String> result = new LinkedHashSet<>(tableNames.size()); 
  36. for (Long value : shardingValue.getValues()) { 
  37. for (String table : tableNames) { 
  38. if (table.endsWith(value % 2 + "")) { 
  39. result.add(table); 
  40. } 
  41. } 
  42. } 
  43. return result; 
  44. } 
  45.  
  46. /** 
  47. * select * from t_order from t_order where order_id between 10 and 20 
  48. * ├── SELECT * FROM t_order_0 WHERE order_id BETWEEN 10 AND 20 
  49. * └── SELECT * FROM t_order_1 WHERE order_id BETWEEN 10 AND 20 
  50. */ 
  51. @Override 
  52. public Collection<String> doBetweenSharding(Collection<String> tableNames, ShardingValue<Long> shardingValue) { 
  53. Collection<String> result = new LinkedHashSet<>(tableNames.size()); 
  54. Range<Long> range = shardingValue.getValueRange(); 
  55. for (Long i = range.lowerEndpoint(); i < range.upperEndpoint(); i++) { 
  56. for (String each : tableNames) { 
  57. if (each.endsWith(i % 2 + "")) { 
  58. result.add(each); 
  59. } 
  60. } 
  61. } 
  62.  
  63. return result; 
  64. } 
  65. } 

2.4配置数据源

数据源配置类DataSourceConfig

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

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

2.5测试

1.新建OrderRepository

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

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. } 
  22. // Order order = new Order(); 
  23. // order.setUserId(1L); 
  24. // order.setOrderId(idGenerator.generateId().longValue()); 
  25. // repository.save(order); 
  26. return "success"; 
  27. } 
  28.  
  29. @RequestMapping("/query") 
  30. public List<Order> queryAll(){ 
  31. List<Order> orders = (List<Order>) repository.findAll(); 
  32. return orders; 
  33. } 
  34. } 
  35.  

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分库分表

暂时省略,可以访问github地址了解更多。

四、总结

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

SpringBoot使用sharding-jdbc分库分表

标签:config   number   sha   create   asList   dha   func   添加   一个   

原文地址:https://www.cnblogs.com/2YSP/p/9746981.html

踩
(0)
赞
(0)
   
举报
评论 一句话评论(0)
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!