利用sharding-jdbc分库分表

sharding-jdbc是当当开源的一款分库分表的数据访问层框架,能对mysql很方便的分库、分表,基本不用修改原有代码,只要配置一下即可,完整的配置参考以下内容:

  1 <?xml version="1.0" encoding="UTF-8"?>
  2 <beans xmlns="http://www.springframework.org/schema/beans"
  3        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4        xmlns:context="http://www.springframework.org/schema/context"
  5        xmlns:tx="http://www.springframework.org/schema/tx"
  6        xsi:schemaLocation="http://www.springframework.org/schema/beans
  7                         http://www.springframework.org/schema/beans/spring-beans.xsd
  8                         http://www.springframework.org/schema/tx
  9                         http://www.springframework.org/schema/tx/spring-tx.xsd
 10                         http://www.springframework.org/schema/context
 11                         http://www.springframework.org/schema/context/spring-context.xsd">
 12
 13     <context:component-scan base-package="com.cnblogs.yjmyzz.sharding"/>
 14
 15     <bean id="propertiesFactoryBean"
 16           class="org.springframework.beans.factory.config.PropertiesFactoryBean">
 17         <property name="locations">
 18             <list>
 19                 <value>classpath:properties/jdbc.properties</value>
 20             </list>
 21         </property>
 22     </bean>
 23
 24     <context:property-placeholder properties-ref="propertiesFactoryBean" ignore-unresolvable="true"/>
 25
 26     <!--父数据源-->
 27     <bean id="parentDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
 28           destroy-method="close">
 29         <property name="driverClassName" value="${jdbc-driver}"/>
 30         <property name="url" value="${jdbc-url-0}"/>
 31         <property name="username" value="${jdbc-user-0}"/>
 32         <property name="password" value="${jdbc-password-0}"/>
 33         <property name="filters" value="stat"/>
 34         <property name="maxActive" value="20"/>
 35         <property name="initialSize" value="1"/>
 36         <property name="maxWait" value="60000"/>
 37         <property name="minIdle" value="1"/>
 38         <property name="timeBetweenEvictionRunsMillis" value="3000"/>
 39         <property name="minEvictableIdleTimeMillis" value="300000"/>
 40         <property name="validationQuery" value="SELECT 'x'"/>
 41         <property name="testWhileIdle" value="true"/>
 42         <property name="testOnBorrow" value="false"/>
 43         <property name="testOnReturn" value="false"/>
 44         <property name="poolPreparedStatements" value="true"/>
 45         <property name="maxPoolPreparedStatementPerConnectionSize" value="20"/>
 46         <property name="connectionInitSqls" value="set names utf8mb4;"/>
 47     </bean>
 48
 49     <!--数据源0-->
 50     <bean id="ds_0" parent="parentDataSource">
 51         <property name="driverClassName" value="${jdbc-driver}"/>
 52         <property name="url" value="${jdbc-url-0}"/>
 53         <property name="username" value="${jdbc-user-0}"/>
 54         <property name="password" value="${jdbc-password-0}"/>
 55     </bean>
 56
 57     <!--数据源1-->
 58     <bean id="ds_1" parent="parentDataSource">
 59         <property name="driverClassName" value="${jdbc-driver}"/>
 60         <property name="url" value="${jdbc-url-1}"/>
 61         <property name="username" value="${jdbc-user-1}"/>
 62         <property name="password" value="${jdbc-password-1}"/>
 63     </bean>
 64
 65     <!--数据源2-->
 66     <bean id="ds_2" parent="parentDataSource">
 67         <property name="driverClassName" value="${jdbc-driver}"/>
 68         <property name="url" value="${jdbc-url-2}"/>
 69         <property name="username" value="${jdbc-user-2}"/>
 70         <property name="password" value="${jdbc-password-2}"/>
 71     </bean>
 72
 73     <!--真正使用的数据源-->
 74     <bean id="dataSource" class="com.dangdang.ddframe.rdb.sharding.api.rule.DataSourceRule">
 75         <constructor-arg>
 76             <map>
 77                 <entry key="ds_0" value-ref="ds_0"/>
 78                 <entry key="ds_1" value-ref="ds_1"/>
 79                 <entry key="ds_2" value-ref="ds_2"/>
 80             </map>
 81         </constructor-arg>
 82     </bean>
 83
 84     <!--t_order的"分表"设置:分N个表 -->
 85     <bean id="orderTableRule" class="com.dangdang.ddframe.rdb.sharding.api.rule.TableRule">
 86         <constructor-arg value="t_order" index="0"/>
 87         <constructor-arg index="1">
 88             <list>
 89                 <value>t_order_0</value>
 90                 <value>t_order_1</value>
 91             </list>
 92         </constructor-arg>
 93         <constructor-arg index="2" ref="dataSource"/>
 94     </bean>
 95
 96     <!--分库的sharding规则:按user_id分库 -->
 97     <bean id="databaseShardingStrategy"
 98           class="com.dangdang.ddframe.rdb.sharding.api.strategy.database.DatabaseShardingStrategy">
 99         <constructor-arg index="0" value="user_id"/>
100         <constructor-arg index="1">
101             <bean class="com.cnblogs.yjmyzz.sharding.algorithm.SingleKeyModuloDatabaseShardingAlgorithm">
102                 <!--dbount的值要跟上面dataSource的个数匹配-->
103                 <property name="dbCount" value="3"/>
104             </bean>
105         </constructor-arg>
106     </bean>
107
108     <!--分表的规则:按order_id分表-->
109     <bean id="tableShardingStrategy" class="com.dangdang.ddframe.rdb.sharding.api.strategy.table.TableShardingStrategy">
110         <constructor-arg index="0" value="order_id"/>
111         <constructor-arg index="1">
112             <bean class="com.cnblogs.yjmyzz.sharding.algorithm.SingleKeyModuloTableShardingAlgorithm">
113                 <!--tableCount的值要跟上面t_order表设置的分表个数保持一致-->
114                 <property name="tableCount" value="2"/>
115             </bean>
116         </constructor-arg>
117     </bean>
118
119     <!--sharding规则Bean-->
120     <bean id="shardingRule" class="com.dangdang.ddframe.rdb.sharding.api.rule.ShardingRule">
121         <constructor-arg index="0" ref="dataSource"/>
122         <constructor-arg index="1">
123             <list>
124                 <ref bean="orderTableRule"/>
125             </list>
126         </constructor-arg>
127         <constructor-arg index="2" ref="databaseShardingStrategy"/>
128         <constructor-arg index="3" ref="tableShardingStrategy"/>
129     </bean>
130
131     <!--sharding数据源-->
132     <bean id="shardingDataSource" class="com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource">
133         <constructor-arg ref="shardingRule"/>
134     </bean>
135
136     <!--sharding事务管理器-->
137     <!--<bean id="transactionManager"-->
138           <!--class="org.springframework.jdbc.datasource.DataSourceTransactionManager">-->
139         <!--<property name="dataSource" ref="shardingDataSource"/>-->
140     <!--</bean>-->
141
142     <!--<tx:annotation-driven transaction-manager="transactionManager"/>-->
143
144     <!--mybatis配置-->
145     <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
146         <property name="configLocation" value="classpath:mybatis-config.xml"></property>
147         <property name="dataSource" ref="shardingDataSource"/>
148         <property name="mapperLocations" value="classpath:mybatis/OrderMapper.xml"/>
149     </bean>
150
151     <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
152         <property name="basePackage" value="com.cnblogs.yjmyzz.sharding.mapper"/>
153         <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
154     </bean>
155
156 </beans>

View Code

上面的配置,表示T_Order表按user_id进行分成ds_0,ds_1,ds_2共三库,每个库中又按order_id分成T_Order_0,T_Order_1二张表。

分库、分表是按常见的取模算法处理的,需要用户自定义二个类(基本上就是模板代码,不需要什么改动)

SingleKeyModuloDatabaseShardingAlgorithm

 1 /**
 2  * Copyright 1999-2015 dangdang.com.
 3  * <p>
 4  * Licensed under the Apache License, Version 2.0 (the "License");
 5  * you may not use this file except in compliance with the License.
 6  * You may obtain a copy of the License at
 7  * <p/>
 8  * http://www.apache.org/licenses/LICENSE-2.0
 9  * <p/>
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  * </p>
16  */
17
18 package com.cnblogs.yjmyzz.sharding.algorithm;
19
20 import com.dangdang.ddframe.rdb.sharding.api.ShardingValue;
21 import com.dangdang.ddframe.rdb.sharding.api.strategy.database.SingleKeyDatabaseShardingAlgorithm;
22 import com.google.common.collect.Range;
23
24 import java.util.Collection;
25 import java.util.LinkedHashSet;
26
27 public final class SingleKeyModuloDatabaseShardingAlgorithm implements SingleKeyDatabaseShardingAlgorithm<Integer> {
28
29     private int dbCount = 1;
30
31     @Override
32     public String doEqualSharding(final Collection<String> availableTargetNames, final ShardingValue<Integer> shardingValue) {
33         for (String each : availableTargetNames) {
34             if (each.endsWith(shardingValue.getValue() % dbCount + "")) {
35                 return each;
36             }
37         }
38         throw new UnsupportedOperationException();
39     }
40
41     @Override
42     public Collection<String> doInSharding(final Collection<String> availableTargetNames, final ShardingValue<Integer> shardingValue) {
43         Collection<String> result = new LinkedHashSet<>(availableTargetNames.size());
44         Collection<Integer> values = shardingValue.getValues();
45         for (Integer value : values) {
46             for (String dataSourceName : availableTargetNames) {
47                 if (dataSourceName.endsWith(value % dbCount + "")) {
48                     result.add(dataSourceName);
49                 }
50             }
51         }
52         return result;
53     }
54
55     @Override
56     public Collection<String> doBetweenSharding(final Collection<String> availableTargetNames, final ShardingValue<Integer> shardingValue) {
57         Collection<String> result = new LinkedHashSet<>(availableTargetNames.size());
58         Range<Integer> range = shardingValue.getValueRange();
59         for (Integer i = range.lowerEndpoint(); i <= range.upperEndpoint(); i++) {
60             for (String each : availableTargetNames) {
61                 if (each.endsWith(i % dbCount + "")) {
62                     result.add(each);
63                 }
64             }
65         }
66         return result;
67     }
68
69     /**
70      * 设置database分库的个数
71      * @param dbCount
72      */
73     public void setDbCount(int dbCount) {
74         this.dbCount = dbCount;
75     }
76 }

View Code

SingleKeyModuloTableShardingAlgorithm

 1 /**
 2  * Copyright 1999-2015 dangdang.com.
 3  * <p>
 4  * Licensed under the Apache License, Version 2.0 (the "License");
 5  * you may not use this file except in compliance with the License.
 6  * You may obtain a copy of the License at
 7  * <p/>
 8  * http://www.apache.org/licenses/LICENSE-2.0
 9  * <p/>
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  * </p>
16  */
17
18 package com.cnblogs.yjmyzz.sharding.algorithm;
19
20 import com.dangdang.ddframe.rdb.sharding.api.ShardingValue;
21 import com.dangdang.ddframe.rdb.sharding.api.strategy.table.SingleKeyTableShardingAlgorithm;
22 import com.google.common.collect.Range;
23
24 import java.util.Collection;
25 import java.util.LinkedHashSet;
26
27 public final class SingleKeyModuloTableShardingAlgorithm implements SingleKeyTableShardingAlgorithm<Integer> {
28
29     private int tableCount = 1;
30
31     @Override
32     public String doEqualSharding(final Collection<String> availableTargetNames, final ShardingValue<Integer> shardingValue) {
33         for (String each : availableTargetNames) {
34             if (each.endsWith(shardingValue.getValue() % tableCount + "")) {
35                 return each;
36             }
37         }
38         throw new UnsupportedOperationException();
39     }
40
41     @Override
42     public Collection<String> doInSharding(final Collection<String> availableTargetNames, final ShardingValue<Integer> shardingValue) {
43         Collection<String> result = new LinkedHashSet<>(availableTargetNames.size());
44         Collection<Integer> values = shardingValue.getValues();
45         for (Integer value : values) {
46             for (String tableNames : availableTargetNames) {
47                 if (tableNames.endsWith(value % tableCount + "")) {
48                     result.add(tableNames);
49                 }
50             }
51         }
52         return result;
53     }
54
55     @Override
56     public Collection<String> doBetweenSharding(final Collection<String> availableTargetNames, final ShardingValue<Integer> shardingValue) {
57         Collection<String> result = new LinkedHashSet<>(availableTargetNames.size());
58         Range<Integer> range = shardingValue.getValueRange();
59         for (Integer i = range.lowerEndpoint(); i <= range.upperEndpoint(); i++) {
60             for (String each : availableTargetNames) {
61                 if (each.endsWith(i % tableCount + "")) {
62                     result.add(each);
63                 }
64             }
65         }
66         return result;
67     }
68
69     /**
70      * 设置分表的个数
71      *
72      * @param tableCount
73      */
74     public void setTableCount(int tableCount) {
75         this.tableCount = tableCount;
76     }
77 }

View Code

然后mybatis里就可以类似常规操作一样来写sql了,具体可参考源码中的示例代码。

不过,经个人测试发现一些小问题,以避免大家踩坑:

1、聚合函数的使用要特别小心,我试了下max/min/count这几个函数,返回时记得给返回结果加字段别名,即: select count(*) order_count from t_order,否则可能返回的结果不正确(已经向作者反馈,估计很快会修复该bug)

2、另外分库的key,不支持范围搜索,类似 select * from t_order where user_id > 100的操作,直接报错,如果需要这样的操作,建议先取max(user_id),比如最大用户id为120,然后user_id in (101,102...120) 或者 between ... and 这样处理。

3、如果采用druid数据源,目前有点不稳定,偶尔会出异常,建议采用dbcp(跟作者反馈了下,说是很快会修复该问题)

4、批量插入问题,insert xxx values(...),(...),(...) 不支持,主要原因是因为要插入的记录,无法定位分片。但是可以适当预处理下变通解决,思路:按db-key将List<T>中的对象先划分成Map<dbkey,List<T>>,然后每个entry的List<T>再按tableKey做同样的map映射,即:将List<T>变成Map<dbkey,Map<tableKey,List<T>> 这种结构,相当于人工把同一分片的数据整理到一起,再做insert批量插入就可以了。

 

其它一些使用上的限制,参考:

http://dangdangdotcom.github.io/sharding-jdbc/post/limitations/

 

最后,我在github上放了一个示例代码sharding-jdbc-sample,需要的同学可以参考

时间: 2025-01-29 11:40:54

利用sharding-jdbc分库分表的相关文章

水平分库分表的关键问题及解决思路

分片技术的由来 关系型数据库本身比较容易成为系统性能瓶颈,单机存储容量.连接数.处理能力等都很有限,数据库本身的"有状态性"导致了它并不像Web和应用服务器那么容易扩展.在互联网行业海量数据和高并发访问的考验下,聪明的技术人员提出了分库分表技术(有些地方也称为Sharding.分片).同时,流行的分布式系统中间件(例如MongoDB.ElasticSearch等)均自身友好支持Sharding,其原理和思想都是大同小异的. 分布式全局唯一ID 在很多中小项目中,我们往往直接使用数据库自

水平分库分表的关键步骤和技术难点

在之前的文章中,我介绍了分库分表的几种表现形式和玩法,也重点介绍了垂直分库所带来的问题和解决方法.本篇中,我们将继续聊聊水平分库分表的一些技巧. 分片技术的由来 关系型数据库本身比较容易成为系统性能瓶颈,单机存储容量.连接数.处理能力等都很有限,数据库本身的"有状态性"导致了它并不像Web和应用服务器那么容易扩展.在互联网行业海量数据和高并发访问的考验下,聪明的技术人员提出了分库分表技术(有些地方也称为Sharding.分片).同时,流行的分布式系统中间件(例如MongoDB.Elas

TSharding:用于蘑菇街交易平台的分库分表组件

  tsharding TSharding is the simple sharding component used in mogujie trade platform. 分库分表业界方案 分库分表TSharding TSharding组件目标 很少的资源投入即可开发完成 支持交易订单表的Sharding需求,分库又分表 支持数据源路由 支持事务 支持结果集合并 支持读写分离 TSharding Resources Abstract TSharding Resources Classes TS

透明的分库分表方案

问题提出 随着应用规模的不断扩大,单机数据库就慢慢无法满足应用的需要了,这主要表现在如下方面: 存量数据越来越大,查询速度越来越慢 访问并发越来越大,磁盘IO.网络IO.CPU都慢慢成为瓶颈 事务数越来越多,事务冲突越来越严重,导致TPS越来越少 这个时候,有的人采用了换商用数据库的方案比如Oracle,然后用Oracle的RAC方式进行水平扩展.但是带来的缺点也比较明显,第一是成本太高,一般人吃不消:第二,管理复杂度较单节点有非常大的提升,风险及管理成本也相应增加:第三,对人员的水平要求更高,

mysql 分库分表的方法

分表后怎么做全文搜索 1.merge方式分表(不好) 2. 使用 sql union 3 使用Sphinx全文检索引擎 一,先说一下为什么要分表 当一张的数据达到几百万时,你查询一次所花的时间会变多,如果有联合查询的话,我想有可能会死在那儿了.分表的目的就在于此,减小数据库的负担,缩短查询时间. 根据个人经验,MySQL执行一个sql的过程如下: 1,接收到sql;2,把sql放到排队队列中 ;3,执行sql;4,返回执行结果.在这个执行过程中最花时间在什么地方呢?第一,是排队等待的时间,第二,

Mysql分库分表Mycat详解介绍

一直对Mysql分库分表有点兴趣,但是也一直停留在有兴趣的阶段,没有遇到能应用的场景.人生苦短,与其等一个机会,不如自己创造吧.稍微调研了下,选择使用 Mycat 这样一款开源产品.没有什么特别的理由,也不去讨论挖掘机哪家强,只是为了学习. 本机环境 电脑环境:Ubuntu 16.04 JDK:1.8 Docker version 1.11.2 Mysql 5.7.13 Mycat 1.5-RELEASE Navicat for Mysql 安装Mysql 为了测试方便,Mysql都跑在Dock

MySQL分库分表的实现过程详解介绍

MySQL分库分表基础表介绍 表基本模型结构 这里我们模拟一个商城的基本的表结.此结构由(用户.门店.导购.门店商品.订单.订单对应的商品).其中,导购也是一个用户,门店是只属于一个店主的,同时店主本身也是一个导购也是一个普通用户. 结构图:   构造数据脚本 MySQL分库分表(1)-脚本 对业务场景进行模拟 场景1:购买者下订单. 1.从session中获得客户ID. 2.可以通过时间戳等拼凑一个订单ID(在创建表的时候为了方便我用自增的,在以下我们一直就吧订单ID看成不是自增的,是用程序生

分库分表的几种常见玩法及如何解决跨库查询等问题

在谈论数据库架构和数据库优化的时候,我们经常会听到"分库分表"."分片"."Sharding"-这样的关键词.让人感到高兴的是,这些朋友所服务的公司业务量正在(或者即将面临)高速增长,技术方面也面临着一些挑战.让人感到担忧的是,他们系统真的就需要"分库分表"了吗?"分库分表"有那么容易实践吗?为此,笔者整理了分库分表中可能遇到的一些问题,并结合以往经验介绍了对应的解决思路和建议. 垂直分表 垂直分表在日常开

PostgreSQL 分库分表 插件之一 pg_shard

MySQL的分库分表有非常多的解决方案,PostgreSQL 的分库分表方案也不少.今天要给大家介绍的是pg_shard插件.安装很简单,如果你的GCC版本第一4.6,那么首先要安装一个高版本的GCC,因为pg_shard里面用了gcc 4.6以后新加的特性. # yum install -y gmp mpfr libmpc libmpc-devel # wget http://gcc.cybermirror.org/releases/gcc-4.9.3/gcc-4.9.3.tar.bz2 #