Sharding-JDBC分库分表使用实例

Posted rhwayfunn

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Sharding-JDBC分库分表使用实例相关的知识,希望对你有一定的参考价值。

Sharding-JDBC是当当开源的用于分库分表的基础类库。定位轻量级java框架,可以通过客户端直接连接数据库,只需要在增加额外的数据源配置就可以轻松实现完整的分库分表功能。

Sharding-JDBC是一个开源的适用于微服务的分布式数据访问基础类库,它始终以云原生的基础开发套件为目标。

目前Sharding-JDBC已经实现的功能包括(最新稳定版本为2.0.0.M1):

  • 分库分表
  • 读写分离
  • 分布式主键
  • 柔性事务
  • 分布式治理能力(2.0新功能),具体包括:
    • 配置集中化与动态化,可支持数据源、表与分片策略的动态切换
    • 客户端的数据库治理,数据源失效自动切换
    • 基于Open Tracing协议的APM信息输出

下面的例子都是基于Spring Boot开发:Spring Boot + Mybatis + Druid + Sharding-Jdbc

工程结构

Application是项目的启动入口

DataSourceConfig是数据源配置,包括如何结合Sharding-Jdbc设置分库分表

algorithm下面是设置的分库分表策略,比如当city_id % 2为偶数放在库A,否则放在库B

UserMapper是Mybatis的接口,由于采用了全注解配置,所以没有Mapper文件

druid下面是druid的监控页面配置

pom配置


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-boot-learning-examples</artifactId>
        <groupId>com.rhwayfun</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spring-boot-mybatis-sharding-jdbc</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
        </dependency>

        <dependency>
            <groupId>com.dangdang</groupId>
            <artifactId>sharding-jdbc-core</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

使用Sharding-Jdbc需要依赖sharding-jdbc-core,其他都是Spring Boot相关的依赖。

详解数据源配置

/**
 * 数据源配置
 *
 * @author happyxiaofan
 * @since 0.0.1
 */
@Configuration
@ConfigurationProperties(prefix = DataSourceConstants.DATASOURCE_PREFIX)
@MapperScan(basePackages =  DataSourceConstants.MAPPER_PACKAGE , sqlSessionFactoryRef = "mybatisSqlSessionFactory")
public class DataSourceConfig 

    private String url;

    private String username;

    private String password;

    @Bean(name = "mybatisDataSource")
    public DataSource getDataSource() throws SQLException 
        //设置分库映射
        Map<String, DataSource> dataSourceMap = new HashMap<>(2);
        dataSourceMap.put("springboot_0", mybatisDataSource("springboot"));
        dataSourceMap.put("springboot_1", mybatisDataSource("springboot2"));

        //设置默认库,两个库以上时必须设置默认库。默认库的数据源名称必须是dataSourceMap的key之一
        DataSourceRule dataSourceRule = new DataSourceRule(dataSourceMap, "springboot_0");

        //设置分表映射
        TableRule userTableRule = TableRule.builder("user")
                .generateKeyColumn("user_id") //将user_id作为分布式主键
                .actualTables(Arrays.asList("user_0", "user_1"))
                .dataSourceRule(dataSourceRule)
                .build();

        //具体分库分表策略
        ShardingRule shardingRule = ShardingRule.builder()
                .dataSourceRule(dataSourceRule)
                .tableRules(Collections.singletonList(userTableRule))
                .databaseShardingStrategy(new DatabaseShardingStrategy("city_id", new ModuloDatabaseShardingAlgorithm()))
                .tableShardingStrategy(new TableShardingStrategy("user_id", new ModuloTableShardingAlgorithm())).build();

        DataSource dataSource = ShardingDataSourceFactory.createDataSource(shardingRule);

        //return new ShardingDataSource(shardingRule);
        return dataSource;
    

    private DataSource mybatisDataSource(final String dataSourceName) throws SQLException 
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(DataSourceConstants.DRIVER_CLASS);
        dataSource.setUrl(String.format(url, dataSourceName));
        dataSource.setUsername(username);
        dataSource.setPassword(password);

        /* 配置初始化大小、最小、最大 */
        dataSource.setInitialSize(1);
        dataSource.setMinIdle(1);
        dataSource.setMaxActive(20);

        /* 配置获取连接等待超时的时间 */
        dataSource.setMaxWait(60000);

        /* 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */
        dataSource.setTimeBetweenEvictionRunsMillis(60000);

        /* 配置一个连接在池中最小生存的时间,单位是毫秒 */
        dataSource.setMinEvictableIdleTimeMillis(300000);

        dataSource.setValidationQuery("SELECT 'x'");
        dataSource.setTestWhileIdle(true);
        dataSource.setTestOnBorrow(false);
        dataSource.setTestOnReturn(false);

        /* 打开PSCache,并且指定每个连接上PSCache的大小。
           如果用Oracle,则把poolPreparedStatements配置为true,
           mysql可以配置为false。分库分表较多的数据库,建议配置为false */
        dataSource.setPoolPreparedStatements(false);
        dataSource.setMaxPoolPreparedStatementPerConnectionSize(20);

        /* 配置监控统计拦截的filters */
        dataSource.setFilters("stat,wall,log4j");
        return dataSource;
    

    /**
     * Sharding-jdbc的事务支持
     *
     * @return
     */
    @Bean(name = "mybatisTransactionManager")
    public DataSourceTransactionManager mybatisTransactionManager(@Qualifier("mybatisDataSource") DataSource dataSource) throws SQLException 
        return new DataSourceTransactionManager(dataSource);
    

    @Bean(name = "mybatisSqlSessionFactory")
    public SqlSessionFactory mybatisSqlSessionFactory(@Qualifier("mybatisDataSource") DataSource mybatisDataSource)
            throws Exception 
        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(mybatisDataSource);
        return sessionFactory.getObject();
    

    // 省略setter、getter



分库规则,按照city_id分库:

如果cityId mod 2 为0,则落在springboot_0,也即是springboot,
如果cityId mod 2 为1,则落在springboot_1,也即是springboot2

分表规则,按照user_id分表:

如果userId mod 2 为0,则落在表user_0,
如果userId mod 2 为1,则落在表user_1

上面指定了两个数据库springbootspringboot2,对应的key分别是springboot_0springboot_1,在具体执行数据库写入的时候会先根据分库算法确定写入到哪个库(springboot或者springboot2),再根据分表算法确定写入最终哪个表(user_0或user_1)。所以这里两个数据库都有两个表,这里以user表作为示例,表结构如下:

CREATE TABLE `user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) NOT NULL COMMENT '用户id',
  `city_id` int(11) DEFAULT NULL COMMENT '城市id',
  `user_name` varchar(15) DEFAULT NULL,
  `age` int(11) DEFAULT NULL COMMENT '年龄',
  `birth` date DEFAULT NULL COMMENT '生日',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1

在设置分表映射的时候,我们将user_id作为分布式主键,但是我们在创建表的时候却将id作为了自增主键,这两个是什么关系呢,用MySQL的自增主键不行么。因为同一个逻辑表(这里的逻辑表是user)内的不同实际表(这里的实际表是user_0user_1)之间的自增键是无法互相感知的,这样会造成重复Id的生成。而Sharding-Jdbc的分布式主键保证了数据库进行分库分表后主键(这里的user_id)一定是唯一不重复的,这样就解决了生成重复Id的问题。

注意下面这段代码:


ShardingRule shardingRule = ShardingRule.builder()
                .dataSourceRule(dataSourceRule)
                .tableRules(Collections.singletonList(userTableRule))
                .databaseShardingStrategy(new DatabaseShardingStrategy("city_id", new ModuloDatabaseShardingAlgorithm()))
                .tableShardingStrategy(new TableShardingStrategy("user_id", new ModuloTableShardingAlgorithm())).build();

这里指定了具体的分库分表策略,我们要实现的是根据city_id分库,根据user_id分表。ModuloDatabaseShardingAlgorithm表示具体分库的算法,TableShardingStrategy表示具体的分表的算法。

测试

我们可以先假设下,如果插入下面这条数据,按照之前确定的分库分表规则,肯定是落在springboot2库,但是无法实现确定落在哪个表,因为我们将user_id作为了分布式主键,主键是由Sharding-Jdbc内部先生成的,所以可能会落在user_0user_1


@Test
    public void getOneSlave() throws Exception 
        UserEntity user = new UserEntity();
        user.setCityId(1);//1 mod 2 = 1,所以会落在springboot2库中
        user.setUserName("insertTest");
        user.setAge(10);
        user.setBirth(new Date());
        assertTrue(userMapper.insertSlave(user) > 0);
        Long userId = user.getUserId();
        System.out.println("Generated Key--userId:" + userId + "mod:" + 1 % 2);
        UserEntity one = userMapper.getOne(userId);
        System.out.println("Generated User:" + one);
        assertEquals("insertTest", one.getUserName());
        //assertTrue(userMapper.delete(userId) > 0);
    

通过user.getUserId可以获取Sharding-Jdbc的生成的主键,运行这个测试用例,可以看到生成主键为131004387576250368,通过计算131004387576250368 mod 2 = 0,所以这条记录在表user_0

验证如下:

柔性事务

读写分离

分布式治理

以上例子所有的代码都在GitHub仓库中:https://github.com/rhwayfun/spring-boot-learning-examples

以上是关于Sharding-JDBC分库分表使用实例的主要内容,如果未能解决你的问题,请参考以下文章

分库分表-- Sharding-Jdbc基本原理

分库分表常见概念解读+Sharding-JDBC实战

分库分表开源中间件之Sharding-JDBC使用体验

分库分表之Sharding-JDBC

SpringBoot使用Sharding-JDBC分库分表

Sharding-Jdbc实现读写分离分库分表,妙!