使用Sharding-JDBC 分庫分表


當mysql單表數據量比較大時往往需要分庫分表,Sharding-JDBC是當當網開源的數據庫分庫分表中間件。Sharding-JDBC定位為輕量級java框架,使用客戶端直連數據庫,以jar包形式提供服務,無proxy代理層,無需額外部署,無其他依賴,DBA也無需改變原有的運維方式。本文主要講述該框架與spring+mybatis的整合使用。

1.准備工作

由於是分庫分表,所以需要在不同的數據庫建立相同的表。分別在sharding_0,sharding_1兩個數據庫中建立t_user0,t_user1,t_user2三張表,需要用到的SQL語句如下:

SET FOREIGN_KEY_CHECKS=0;

DROP TABLE IF EXISTS `t_user_0`;
CREATE TABLE `t_user_0` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `name` varchar(255) NOT NULL,
  `age` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `t_user_1`;
CREATE TABLE `t_user_1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `name` varchar(255) NOT NULL,
  `age` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `t_user_2`;
CREATE TABLE `t_user_2` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `name` varchar(255) NOT NULL,
  `age` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

項目的整體結構如下圖所示:

需要用到的sharding-jdbc的依賴如下所示:
<dependency>
    <groupId>io.shardingjdbc</groupId>
    <artifactId>sharding-jdbc-core</artifactId>
    <version>${latest.release.version}</version>
</dependency>

2.代碼詳解

代碼結構如上圖所示,Mapper,Service層和普通的SSM項目一樣,無需做改變,主要是增加了algorithm下面的兩個文件,一個是分庫策略,一個是分表策略。然后spring的配置文件稍稍做了修改,spring-database.xml設置的是數據庫的連接信息。spring-sharding.xml設置的是具體的分庫分表信息。

spring-database.xml配置如下所示:

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="locations">
			<list>
				<value>classpath:config/resource/jdbc_dev.properties</value>
			</list>
		</property>
	</bean>
		
	<bean name="sharding_0" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
		<property name="url" value="${jdbc_url0}" />
		<property name="username" value="${jdbc_username0}" />
		<property name="password" value="${jdbc_password0}" />
<!-- 		<property name="driverClass" value="${jdbc_driver0}" /> -->
		<!-- 初始化連接大小 -->
		<property name="initialSize" value="0" />
		<!-- 連接池最大使用連接數量 -->
		<property name="maxActive" value="20" />
		<!-- 連接池最小空閑 -->
		<property name="minIdle" value="0" />
		<!-- 獲取連接最大等待時間 -->
		<property name="maxWait" value="60000" />
		<property name="validationQuery" value="${validationQuery}" />
		<property name="testOnBorrow" value="false" />
		<property name="testOnReturn" value="false" />
		<property name="testWhileIdle" value="true" />
		<!-- 配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接,單位是毫秒 -->
		<property name="timeBetweenEvictionRunsMillis" value="60000" />
		<!-- 配置一個連接在池中最小生存的時間,單位是毫秒 -->
		<property name="minEvictableIdleTimeMillis" value="25200000" />
		<!-- 打開removeAbandoned功能 -->
		<property name="removeAbandoned" value="true" />
		<!-- 1800秒,也就是30分鍾 -->
		<property name="removeAbandonedTimeout" value="1800" />
		<!-- 關閉abanded連接時輸出錯誤日志 -->
		<property name="logAbandoned" value="true" />
		<property name="filters" value="stat" />
	</bean>
	
	<bean name="sharding_1" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
		<property name="url" value="${jdbc_url1}" />
		<property name="username" value="${jdbc_username1}" />
		<property name="password" value="${jdbc_password1}" />
<!-- 		<property name="driverClass" value="${jdbc_driver1}" /> -->
		<!-- 初始化連接大小 -->
		<property name="initialSize" value="0" />
		<!-- 連接池最大使用連接數量 -->
		<property name="maxActive" value="20" />
		<!-- 連接池最小空閑 -->
		<property name="minIdle" value="0" />
		<!-- 獲取連接最大等待時間 -->
		<property name="maxWait" value="60000" />
		<property name="validationQuery" value="${validationQuery}" />
		<property name="testOnBorrow" value="false" />
		<property name="testOnReturn" value="false" />
		<property name="testWhileIdle" value="true" />
		<!-- 配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接,單位是毫秒 -->
		<property name="timeBetweenEvictionRunsMillis" value="60000" />
		<!-- 配置一個連接在池中最小生存的時間,單位是毫秒 -->
		<property name="minEvictableIdleTimeMillis" value="25200000" />
		<!-- 打開removeAbandoned功能 -->
		<property name="removeAbandoned" value="true" />
		<!-- 1800秒,也就是30分鍾 -->
		<property name="removeAbandonedTimeout" value="1800" />
		<!-- 關閉abanded連接時輸出錯誤日志 -->
		<property name="logAbandoned" value="true" />
		<property name="filters" value="stat" />
	</bean>

spring-sharding.xml配置如下所示,從下圖中可以看到主要是指定了需要分表的策略和分庫的策略,然后進行了封裝,具體的分庫分表策略需要自己編寫。

 <!-- 配置好dataSourceRulue,即對數據源進行管理 -->
    <bean id="dataSourceRule" class="com.dangdang.ddframe.rdb.sharding.api.rule.DataSourceRule">
        <constructor-arg>
            <map>
                <entry key="sharding_0" value-ref="sharding_0"/>
                <entry key="sharding_1" value-ref="sharding_1"/>
            </map>
        </constructor-arg>
    </bean>
    
    <!-- 對t_user表的配置,進行分庫配置,邏輯表名為t_user,每個庫有實際的三張表 -->
    <bean id="userTableRule" class="com.dangdang.ddframe.rdb.sharding.api.rule.TableRule">
        <constructor-arg value="t_user" index="0"/>
        <constructor-arg index="1">
            <list>
                <value>t_user_0</value>
                <value>t_user_1</value>
                <value>t_user_2</value>
            </list>
        </constructor-arg>
        <constructor-arg index="2" ref="dataSourceRule"/>
        <constructor-arg index="3" ref="userDatabaseShardingStrategy"/>
        <constructor-arg index="4" ref="userTableShardingStrategy"/>
    </bean>
    
    <!-- t_user分庫策略 -->
    <bean id="userDatabaseShardingStrategy" class="com.dangdang.ddframe.rdb.sharding.api.strategy.database.DatabaseShardingStrategy">
        <constructor-arg index="0" value="user_id"/>
        <constructor-arg index="1">
            <bean class="com.study.dangdang.sharding.jdbc.algorithm.UserSingleKeyDatabaseShardingAlgorithm" />
        </constructor-arg>
    </bean>
    
    <!-- t_user 分表策略 -->
    <bean id="userTableShardingStrategy" class="com.dangdang.ddframe.rdb.sharding.api.strategy.table.TableShardingStrategy">
        <constructor-arg index="0" value="user_id"/>
        <constructor-arg index="1">
            <bean class="com.study.dangdang.sharding.jdbc.algorithm.UserSingleKeyTableShardingAlgorithm" />
        </constructor-arg>
    </bean>
    
    <!-- 構成分庫分表的規則 傳入數據源集合和每個表的分庫分表的具體規則 -->
    <bean id="shardingRule" class="com.dangdang.ddframe.rdb.sharding.api.rule.ShardingRule">
        <constructor-arg index="0" ref="dataSourceRule"/>
        <constructor-arg index="1">
            <list>
                <ref bean="userTableRule"/>
            </list>
        </constructor-arg>
    </bean>
    
    <!-- 對datasource進行封裝 -->
    <bean id="shardingDataSource" class="com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource">
        <constructor-arg ref="shardingRule"/>
    </bean>

接下來看一下具體的分庫文件和分表文件:

分表的文件UserSingleKeyTableShardingAlgorithm代碼如下所示:

public class UserSingleKeyTableShardingAlgorithm implements SingleKeyTableShardingAlgorithm<Integer>{

    /**
     * sql 中 = 操作時,table的映射
     */
    public String doEqualSharding(Collection<String> tableNames, ShardingValue<Integer> shardingValue) {
        for (String each : tableNames) {
            if (each.endsWith(shardingValue.getValue() % 3 + "")) {
                return each;
            }
        }
        throw new IllegalArgumentException();
    }

    /**
     * sql 中 in 操作時,table的映射
     */
    public Collection<String> doInSharding(Collection<String> tableNames, ShardingValue<Integer> shardingValue) {
        Collection<String> result = new LinkedHashSet<String>(tableNames.size());
        for (Integer value : shardingValue.getValues()) {
            for (String tableName : tableNames) {
                if (tableName.endsWith(value % 3 + "")) {
                    result.add(tableName);
                }
            }
        }
        return result;
    }

    /**
     * sql 中 between 操作時,table的映射
     */
    public Collection<String> doBetweenSharding(Collection<String> tableNames,
            ShardingValue<Integer> shardingValue) {
        Collection<String> result = new LinkedHashSet<String>(tableNames.size());
        Range<Integer> range = (Range<Integer>) shardingValue.getValueRange();
        for (Integer i = range.lowerEndpoint(); i <= range.upperEndpoint(); i++) {
            for (String each : tableNames) {
                if (each.endsWith(i % 3 + "")) {
                    result.add(each);
                }
            }
        }
        return result;
    }

}

分庫的文件UserSingleKeyDatabaseShardingAlgorithm代碼如下所示:

public class UserSingleKeyDatabaseShardingAlgorithm implements SingleKeyDatabaseShardingAlgorithm<Integer>{

    /**
     * sql 中關鍵字 匹配符為 =的時候,表的路由函數
     */
    public String doEqualSharding(Collection<String> availableTargetNames, ShardingValue<Integer> shardingValue) {
        for (String each : availableTargetNames) {
            if (each.endsWith(shardingValue.getValue() % 2 + "")) {
                return each;
            }
        }
        throw new IllegalArgumentException();
    }

    /**
     * sql 中關鍵字 匹配符為 in 的時候,表的路由函數
     */
    public Collection<String> doInSharding(Collection<String> availableTargetNames, ShardingValue<Integer> shardingValue) {
        Collection<String> result = new LinkedHashSet<String>(availableTargetNames.size());
        for (Integer value : shardingValue.getValues()) {
            for (String tableName : availableTargetNames) {
                if (tableName.endsWith(value % 2 + "")) {
                    result.add(tableName);
                }
            }
        }
        return result;
    }

    /**
     * sql 中關鍵字 匹配符為 between的時候,表的路由函數
     */
    public Collection<String> doBetweenSharding(Collection<String> availableTargetNames,
            ShardingValue<Integer> shardingValue) {
        Collection<String> result = new LinkedHashSet<String>(availableTargetNames.size());
        Range<Integer> range = (Range<Integer>) shardingValue.getValueRange();
        for (Integer i = range.lowerEndpoint(); i <= range.upperEndpoint(); i++) {
            for (String each : availableTargetNames) {
                if (each.endsWith(i % 2 + "")) {
                    result.add(each);
                }
            }
        }
        return result;
    }

}

可以看到對於不同的sql語句關鍵詞有不同的策略,主要實現了doEqualSharding,doInSharding,doBetweenSharding三個方法,看源碼可以得知針對不同的分庫分表規則需要實現不同的接口。

3. 測試

測試用例代碼如下:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath*:config/spring/spring-database.xml",
        "classpath*:config/spring/spring-sharding.xml" })
public class ShardingJdbcMybatisTest {
    @Resource
    public UserService userService;
   
    @Test
    public void testUserInsert() {
        User u = new User();
        for(int i=0;i<100;i++){
            u.setUserId(26+i);
            u.setAge(2+i);
            u.setName("war3"+i);
            Assert.assertEquals(userService.insert(u), true);
        }
    }
 }

查看數據庫可以看到100個用例均勻分布到了數據庫的表中。

參考文獻

http://blog.csdn.net/clypm/article/details/54378523

http://shardingjdbc.io/docs/00-overview/

https://my.oschina.net/editorial-story/blog/888650

http://www.infoq.com/cn/news/2016/01/sharding-jdbc-dangdang

鏡像地址

http://www.zhangwei.wiki/#/posts/4

pay


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM