sharding-jdbc分库分表

雨点打透心脏的1/2处 2022-12-31 12:23 254阅读 0赞

数据库分片思想

垂直切分

按照业务拆分的方式称为垂直分片,又称为纵向拆分,它的核心理念是专库专用。

水平切分

水平分片又称为横向拆分。 相对于垂直分片,它不再将数据根据业务逻辑分类,而是通过某个字段(或某几个字段),根据某种规则将数据分散至多个库或表中,每个分片仅包含数据的一部分。 例如:根据主键分片,偶数主键的记录放入0库(或表),奇数主键的记录放入1库(或表)

Sharding-JDBC简介

定位为轻量级Java框架,在Java的JDBC层提供的额外服务。 它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。

  • 适用于任何基于Java的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC。
  • 基于任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP等。
  • 支持任意实现JDBC规范的数据库。目前支持MySQL,Oracle,SQLServer和PostgreSQL。Sharding-JDBC采用无中心化架构,适用于Java开发的高性能的轻量级OLTP应用;

功能列表

  • 分库 & 分表
  • 读写分离
  • 分布式主键

引入依赖

  1. <dependency>
  2. <groupId>io.shardingjdbc</groupId>
  3. <artifactId>sharding-jdbc-core</artifactId>
  4. <version>2.0.3</version>
  5. </dependency>

选择2.0.3作为实践版本,是因为1.4.2之前版本需要依赖druid解析,1.5.2之后采用自研解析方式,并且版本对比来说1.5.2之后性能更加稳定。不采用最新版本3.1.0.M1的原因是由于新版本性能相对而言还不太稳定,不适合在生产环境使用。

规则配置

Sharding-JDBC可以通过Java,YAML,Spring命名空间和Spring Boot Starter四种方式配置,开发者可根据场景选择适合的配置方式。详情请参见配置手册。

分片算法

通过分片算法将数据分片,支持通过=、BETWEEN和IN分片。分片算法需要应用方开发者自行实现,可实现的灵活度非常高。

目前提供4种分片算法。由于分片算法和业务实现紧密相关,因此并未提供内置分片算法,而是通过分片策略将各种场景提炼出来,提供更高层级的抽象,并提供接口让应用开发者自行实现分片算法。

精确分片算法
对应PreciseShardingAlgorithm,用于处理使用单一键作为分片键的=与IN进行分片的场景。需要配合StandardShardingStrategy使用。

范围分片算法
对应RangeShardingAlgorithm,用于处理使用单一键作为分片键的BETWEEN AND进行分片的场景。需要配合StandardShardingStrategy使用。

复合分片算法
对应ComplexKeysShardingAlgorithm,用于处理使用多键作为分片键进行分片的场景,包含多个分片键的逻辑较复杂,需要应用开发者自行处理其中的复杂度。需要配合ComplexShardingStrategy使用。

Hint分片算法
对应HintShardingAlgorithm,用于处理使用Hint行分片的场景。需要配合HintShardingStrategy使用。

项目结构jpa配置

  1. spring.jpa.database=mysql
  2. spring.jpa.show-sql=true
  3. spring.jpa.hibernate.ddl-auto=none

分表分库数据源

  1. @Configuration
  2. public class DataSourceConfig {
  3. /**
  4. * 分表
  5. *
  6. * @return 数据源
  7. * @throws SQLException 数据库异常
  8. */
  9. @Bean
  10. public DataSource userShardingDataSource() throws SQLException {
  11. ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
  12. // 单库分表
  13. TableRuleConfiguration result = new TableRuleConfiguration();
  14. result.setLogicTable("t_user");
  15. result.setActualDataNodes("ds0.t_user${0..2}");
  16. result.setKeyGeneratorColumnName("user_id");
  17. result.setKeyGeneratorClass(MySqlKeyGenerator.class.getName());
  18. result.setDatabaseShardingStrategyConfig(new StandardShardingStrategyConfiguration("user_id",
  19. ModuloShardingDatabaseAlgorithm.class.getName(),
  20. ModuloShardingDatabaseAlgorithm.class.getName()));
  21. result.setTableShardingStrategyConfig(new StandardShardingStrategyConfiguration("user_id",
  22. ModuloShardingTableAlgorithm.class.getName(),
  23. ModuloShardingTableAlgorithm.class.getName()));
  24. TableRuleConfiguration result2 = new TableRuleConfiguration();
  25. result2.setLogicTable("t_order");
  26. result2.setActualDataNodes("ds${0..1}.t_order${[0,1]}");
  27. result2.setKeyGeneratorColumnName("order_id");
  28. result2.setKeyGeneratorClass(RedisKeyGenerator.class.getName());
  29. result2.setDatabaseShardingStrategyConfig(new StandardShardingStrategyConfiguration("user_id",
  30. ModuloShardingDatabaseAlgorithm.class.getName(),
  31. ModuloShardingDatabaseAlgorithm.class.getName()));
  32. result2.setTableShardingStrategyConfig(new StandardShardingStrategyConfiguration("order_id",
  33. ModuloShardingTableAlgorithm.class.getName(),
  34. ModuloShardingTableAlgorithm.class.getName()));
  35. TableRuleConfiguration result3 = new TableRuleConfiguration();
  36. result3.setLogicTable("t_order_item");
  37. result3.setActualDataNodes("ds${0..1}.t_order_item${[0,1]}");
  38. result3.setKeyGeneratorColumnName("order_item_id");
  39. result3.setKeyGeneratorClass(RedisKeyGenerator.class.getName());
  40. result3.setDatabaseShardingStrategyConfig(new StandardShardingStrategyConfiguration("user_id",
  41. ModuloShardingDatabaseAlgorithm.class.getName(),
  42. ModuloShardingDatabaseAlgorithm.class.getName()));
  43. result3.setTableShardingStrategyConfig(new StandardShardingStrategyConfiguration("order_id",
  44. ModuloShardingTableAlgorithm.class.getName(),
  45. ModuloShardingTableAlgorithm.class.getName()));
  46. shardingRuleConfig.getTableRuleConfigs().add(result);
  47. shardingRuleConfig.getTableRuleConfigs().add(result2);
  48. shardingRuleConfig.getTableRuleConfigs().add(result3);
  49. // 配置表关联
  50. shardingRuleConfig.getBindingTableGroups().add("t_order, t_order_item");
  51. Map<String, DataSource> dbMap = new HashMap<>(2);
  52. dbMap.put("ds0", DataSourceUtil.dataSource("ds0"));
  53. dbMap.put("ds1", DataSourceUtil.dataSource("ds1"));
  54. Properties properties = new Properties();
  55. properties.put("sql.show", true);
  56. return ShardingDataSourceFactory.createDataSource(dbMap, shardingRuleConfig, new HashMap<>(1), properties);
  57. }
  58. }

选库算法表算法

  1. @Slf4j
  2. public final class ModuloShardingTableAlgorithm implements PreciseShardingAlgorithm<Long>, RangeShardingAlgorithm<Long> {
  3. /**
  4. * 选表
  5. */
  6. @Override
  7. public String doSharding(final Collection<String> tableNames, final PreciseShardingValue<Long> shardingValue) {
  8. log.info("tableNames:{}", JSON.toJSONString(tableNames));
  9. log.info("shardingValue:{}", JSON.toJSONString(shardingValue));
  10. String tableName = "";
  11. if ("t_user".equalsIgnoreCase(shardingValue.getLogicTableName())) {
  12. for (String each : tableNames) {
  13. if (each.endsWith(shardingValue.getValue() % tableNames.size() + "")) {
  14. log.info("table:{}", each);
  15. tableName = each;
  16. break;
  17. }
  18. }
  19. }
  20. // 分库分表
  21. if ("t_order".equalsIgnoreCase(shardingValue.getLogicTableName()) ||
  22. "t_order_item".equalsIgnoreCase(shardingValue.getLogicTableName())) {
  23. for (String each : tableNames) {
  24. if (each.endsWith(shardingValue.getValue() % tableNames.size() + "")) {
  25. tableName = each;
  26. break;
  27. }
  28. }
  29. }
  30. log.info("tableName:{}", tableName);
  31. if (StringUtils.isNotEmpty(tableName)) {
  32. return tableName;
  33. }
  34. throw new UnsupportedOperationException();
  35. }
  36. /**
  37. * 实现between and查询
  38. */
  39. @Override
  40. public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Long> rangeShardingValue) {
  41. log.info("collection:{}", JSON.toJSONString(collection));
  42. log.info("rangeShardingValue:{}", JSON.toJSONString(rangeShardingValue));
  43. Collection<String> collect = new ArrayList<>();
  44. Range<Long> valueRange = rangeShardingValue.getValueRange();
  45. for (Long i = valueRange.lowerEndpoint(); i <= valueRange.upperEndpoint(); i++) {
  46. for (String each : collection) {
  47. if (each.endsWith(i % collection.size() + "")) {
  48. collect.add(each);
  49. }
  50. }
  51. }
  52. return collect;
  53. }
  54. }

主键策略

默认使用雪花算法(snowflake)生成64bit的长整型数据。如果在请求并发小的情况下会出现所生产的主键都为偶数。有时候我们需要自增主键,就需要自定义主键成策略。

自定义分布式redis主键

  1. public class RedisKeyGenerator implements KeyGenerator {
  2. private RedisClient client = RedisClient.create(RedisURI.Builder.redis("192.168.97.57",6379).withTimeout(Duration.ofMillis(6000)).withPassword("666666").withDatabase(1).build());
  3. private GenericObjectPool<StatefulRedisConnection<String, String>> pool = ConnectionPoolSupport.createGenericObjectPool(() -> client.connect(), getGenericObjectPoolConfig());
  4. public RedisKeyGenerator() {
  5. }
  6. private GenericObjectPoolConfig getGenericObjectPoolConfig() {
  7. GenericObjectPoolConfig config = new GenericObjectPoolConfig();
  8. config.setMaxIdle(8);
  9. config.setMinIdle(0);
  10. config.setMaxTotal(8);
  11. config.setMaxWaitMillis(-1);
  12. return config;
  13. }
  14. @Override
  15. public synchronized Number generateKey() {
  16. try (StatefulRedisConnection<String, String> connection = pool.borrowObject()) {
  17. RedisAsyncCommands<String, String> commands = connection.async();
  18. RedisFuture<Long> future = commands.incr("id");
  19. return future.get();
  20. } catch (Exception e) {
  21. e.printStackTrace();
  22. return System.currentTimeMillis();
  23. }
  24. }
  25. @Override
  26. protected void finalize() throws Throwable {
  27. if (!pool.isClosed()){
  28. pool.close();
  29. }
  30. super.finalize();
  31. }
  32. }

自定义mysql自增主键

建立表t_generate_key

  1. CREATE TABLE
  2. IF NOT EXISTS ds0.t_generate_key (
  3. user_id BIGINT NOT NULL AUTO_INCREMENT,
  4. PRIMARY KEY (user_id)
  5. );

代码

  1. public class MySqlKeyGenerator implements KeyGenerator {
  2. private DataSource dataSource;
  3. private String sql = "insert into t_generate_key()values();";
  4. public MySqlKeyGenerator() {
  5. this.dataSource = DataSourceUtil.dataSource("ds0");
  6. }
  7. @Override
  8. public synchronized Number generateKey() {
  9. try (Connection conn = dataSource.getConnection();
  10. Statement statement = conn.createStatement()) {
  11. statement.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS);
  12. ResultSet resultSet = statement.getGeneratedKeys();
  13. if (resultSet.next()) {
  14. return resultSet.getLong(1);
  15. }
  16. } catch (Exception e) {
  17. return System.currentTimeMillis();
  18. }
  19. return System.currentTimeMillis();
  20. }
  21. }

源码下载

spring-boot-jpa-sharding-jdbc

参考

  1. 性能测试报告
  2. 源码示例参考

    欢迎关注我的公众号,和我一起,每天进步一点点

20201229174551456.png

发表评论

表情:
评论列表 (有 0 条评论,254人围观)

还没有评论,来说两句吧...

相关阅读