cobar client是出自阿里的产品,cobar client只需要引入jar包即可,不需要建立服务。下面的地址是cobar client的帮助文档
http://code.alibabatech.com/docs/cobarclient/zh/
http://www.kuqin.com/system-analysis/20120212/318089.html
分库分表都是在Cobar产品里面的,Cobar分为两类,分别是Cobar Client和Cobar Server,根据业务的需要进行选择,Cobar Server是一组独立的(Stand Alone)的Server集群,Cobar Client就是第三方的Java包,他就直接嵌入到应用上面去。然后他的拆分规则都是直接写到应用的配置文件里面的。这是阿里自主开发的,没有用到外面的一些开源的工具
用到的数据库创建语句:
create database dbtest1; create database dbtest2; create database dbtest3; //分别在这三个数据库中创建下面的表: CREATE TABLE `cont` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `taobaoId` bigint(20) DEFAULT '0', `name` varchar(20) DEFAULT '', `upd_time` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
以下贴出一些关键的代码,详细的可以下载功能进行运行查看:
工程截图:
下面列举一个简单的功能,就spring与ibatis进行整合,在mysql数据库上面进行操作,对cont表进行添加和查询的功能操作:
简单看下service,dao的文件代码
Service:
public interface ContService { /** * 基本插入 * * @return */ public Long addCont(Cont cont); /** * 根据主键查询 */ public Cont getContByKey(Long id); /** * 根据条件查询 * * @param contQuery * 查询条件 * @return */ public List<Cont> getContList(ContQuery contQuery); }
@Service("contService") public class ContServiceImpl implements ContService { private static final Log log = LogFactory.getLog(ContServiceImpl.class); @Resource ContDAO contDAO; /** * 插入数据库 * * @return */ public Long addCont(Cont cont) { try { return contDAO.addCont(cont); } catch (SQLException e) { log.error("dao addCont error.:" + e.getMessage(), e); } return 0L; } /** * 根据主键查找 */ public Cont getContByKey(Long id) { try { return contDAO.getContByKey(id); } catch (SQLException e) { log.error("dao getContbyKey error.:" + e.getMessage(), e); } return null; } public List<Cont> getContList(ContQuery contQuery) { try { return contDAO.getContList(contQuery); } catch (SQLException e) { log.error("get Cont list error." + e.getMessage(), e); } return Collections.emptyList(); } }
@Repository public class ContDAO { @Resource SqlMapClientTemplate sqlMapClientTemplate; public Long addCont(Cont cont) throws SQLException { return (Long) this.sqlMapClientTemplate.insert("Cont.insertCont", cont); } /** * 根据主键查找 * * @throws SQLException */ public Cont getContByKey(Long id) throws SQLException { Map<String, Object> params = new HashMap<String, Object>(); params.put("id", id); Cont result = (Cont) this.sqlMapClientTemplate.queryForObject( "Cont.getContByKey", params); return result; } @SuppressWarnings("unchecked") public List<Cont> getContList(ContQuery contQuery) throws SQLException { if (contQuery.getFields() != null && contQuery.getFields() != "") { return (List<Cont>) this.sqlMapClientTemplate.queryForList( "Cont.getContListFields", contQuery); } return (List<Cont>) this.sqlMapClientTemplate.queryForList( "Cont.getContList", contQuery); } }
1、下面applicationContext.xmlspring配置文件是针对没有在cobar的情况下进行的常规配置:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:mybatis="http://mybatis.org/schema/mybatis-spring" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd" default-lazy-init="false"> <description>Spring公共配置</description> <context:component-scan base-package="com.hj.cobar"></context:component-scan> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location"> <value>application.development.properties</value> </property> </bean> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <!-- 基本属性 url、user、password --> <property name="url" value="${jdbc_url}"/> <property name="username" value="${jdbc_user}"/> <property name="password" value="${jdbc_password}"/> <!-- 配置初始化大小、最小、最大 --> <property name="initialSize" value="1"/> <property name="minIdle" value="1"/> <property name="maxActive" value="20"/> <!-- 配置获取连接等待超时的时间 --> <property name="maxWait" value="60000"/> <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 --> <property name="timeBetweenEvictionRunsMillis" value="60000"/> <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 --> <property name="minEvictableIdleTimeMillis" value="300000"/> <property name="validationQuery" value="SELECT 'x'"/> <property name="testWhileIdle" value="true"/> <property name="testOnBorrow" value="false"/> <property name="testOnReturn" value="false"/> <!-- 打开PSCache,并且指定每个连接上PSCache的大小 --> <property name="poolPreparedStatements" value="false"/> <property name="maxPoolPreparedStatementPerConnectionSize" value="20"/> <!-- 配置监控统计拦截的filters --> <property name="filters" value="stat"/> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 使用annotation定义事务 --> <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/> <!-- 配置ibatis的SqlMapClient --> <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="configLocation"> <value>classpath:/sqlmap-config.xml</value> </property> </bean> <bean id="sqlMapClientTemplate" class="org.springframework.orm.ibatis.SqlMapClientTemplate"> <property name="sqlMapClient" ref="sqlMapClient" /> </bean> </beans>
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:mybatis="http://mybatis.org/schema/mybatis-spring" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd" default-lazy-init="false"> <description>Spring公共配置</description> <context:component-scan base-package="com.hj.cobar"></context:component-scan> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location"> <value>application.development.properties</value> </property> </bean> <!-- 配置数据源开始 --> <bean id="dataSources" class="com.alibaba.cobar.client.datasources.DefaultCobarDataSourceService"> <property name="dataSourceDescriptors"> <set> <bean class="com.alibaba.cobar.client.datasources.CobarDataSourceDescriptor"> <property name="identity" value="partition0"/> <property name="targetDataSource" ref="dataSource0"/> <property name="targetDetectorDataSource" ref="dataSource0"/> </bean> <bean class="com.alibaba.cobar.client.datasources.CobarDataSourceDescriptor"> <property name="identity" value="partition1"/> <property name="targetDataSource" ref="dataSource1"/> <property name="targetDetectorDataSource" ref="dataSource1"/> </bean> <bean class="com.alibaba.cobar.client.datasources.CobarDataSourceDescriptor"> <property name="identity" value="partition2"/> <property name="targetDataSource" ref="dataSource2"/> <property name="targetDetectorDataSource" ref="dataSource2"/> </bean> </set> </property> <property name="haDataSourceCreator"> <bean class="com.alibaba.cobar.client.datasources.ha.FailoverHotSwapDataSourceCreator"> <property name="detectingSql" value="update cobarha set timeflag=CURRENT_TIMESTAMP()"/> </bean> </property> </bean> <!-- 数据源0 --> <bean id="dataSource0" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="${jdbc0.url}" /> <property name="username" value="${jdbc0.username}" /> <property name="password" value="${jdbc0.password}" /> <property name="filters" value="config" /> <property name="maxActive" value="5" /> <property name="initialSize" value="5" /> <property name="maxWait" value="1" /> <property name="minIdle" value="5" /> <property name="timeBetweenEvictionRunsMillis" value="3000" /> <property name="minEvictableIdleTimeMillis" value="300000" /> <property name="validationQuery" value="SELECT 'x'" /> <property name="testWhileIdle" value="true" /> <property name="testOnBorrow" value="false" /> <property name="testOnReturn" value="false" /> <property name="poolPreparedStatements" value="true" /> <property name="maxPoolPreparedStatementPerConnectionSize" value="20" /> <!-- <property name="connectionProperties" value="config.decrypt=true" /> --> </bean> <!-- 数据源1 --> <bean id="dataSource1" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="${jdbc1.url}" /> <property name="username" value="${jdbc1.username}" /> <property name="password" value="${jdbc1.password}" /> <property name="filters" value="config" /> <property name="maxActive" value="5" /> <property name="initialSize" value="5" /> <property name="maxWait" value="1" /> <property name="minIdle" value="5" /> <property name="timeBetweenEvictionRunsMillis" value="3000" /> <property name="minEvictableIdleTimeMillis" value="300000" /> <property name="validationQuery" value="SELECT 'x'" /> <property name="testWhileIdle" value="true" /> <property name="testOnBorrow" value="false" /> <property name="testOnReturn" value="false" /> <property name="poolPreparedStatements" value="true" /> <property name="maxPoolPreparedStatementPerConnectionSize" value="20" /> </bean> <!-- 数据源2 --> <bean id="dataSource2" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="${jdbc2.url}" /> <property name="username" value="${jdbc2.username}" /> <property name="password" value="${jdbc2.password}" /> <property name="filters" value="config" /> <property name="maxActive" value="5" /> <property name="initialSize" value="5" /> <property name="maxWait" value="1" /> <property name="minIdle" value="5" /> <property name="timeBetweenEvictionRunsMillis" value="3000" /> <property name="minEvictableIdleTimeMillis" value="300000" /> <property name="validationQuery" value="SELECT 'x'" /> <property name="testWhileIdle" value="true" /> <property name="testOnBorrow" value="false" /> <property name="testOnReturn" value="false" /> <property name="poolPreparedStatements" value="true" /> <property name="maxPoolPreparedStatementPerConnectionSize" value="20" /> </bean> <!-- 配置数据源结束 --> <!-- 配置路由规则开始 --> <bean id="hashFunction" class="com.hj.cobar.dao.router.HashFunction"></bean> <bean id="internalRouter" class="com.alibaba.cobar.client.router.config.CobarInteralRouterXmlFactoryBean"> <!-- functionsMap是在使用自定义路由规则函数的时候使用 --> <property name="functionsMap"> <map> <entry key="hash" value-ref="hashFunction"></entry> </map> </property> <property name="configLocations"> <list> <value>classpath:/dbRule/sharding-rules-on-namespace.xml</value> </list> </property> </bean> <!-- 配置路由规则结束 --> <!-- 事务配置 --> <bean id="transactionManager" class="com.alibaba.cobar.client.transaction.MultipleDataSourcesTransactionManager"> <property name="cobarDataSourceService" ref="dataSources"/> </bean> <!-- 使用annotation定义事务 --> <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/> <!-- iBatis SQL map定义。 --> <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"> <!-- 这里配置的dataSource0为默认的数据源,如果找不到数据库的话则到该数据源中查找 --> <property name="dataSource" ref="dataSource0" /> <property name="configLocation"> <value>classpath:/sqlmap-config.xml</value> </property> </bean> <!-- 工程里一定要使用此工程模板,不能再使用ibatis原生的api,不然有的情况会不经过cobar的过滤 --> <bean id="sqlMapClientTemplate" class="com.alibaba.cobar.client.CobarSqlMapClientTemplate"> <property name="sqlMapClient" ref="sqlMapClient" /> <property name="cobarDataSourceService" ref="dataSources" /> <property name="router" ref="internalRouter" /> <property name="sqlAuditor"> <bean class="com.alibaba.cobar.client.audit.SimpleSqlAuditor" /> </property> <property name="profileLongTimeRunningSql" value="true" /> <property name="longTimeRunningSqlIntervalThreshold" value="3600000" /> </bean> </beans>
<rules> <rule> <namespace>Cont</namespace> <!-- 表达式如果不使用自定义路由规则函数,而是直接使用 taobaoId%2==0这种的话就不用在文件 中配置<property name="functionsMap">中了 --> <shardingExpression>hash.apply(taobaoId) == 0</shardingExpression> <shards>partition0</shards> </rule> <rule> <namespace>Cont</namespace> <shardingExpression>hash.apply(taobaoId) == 1</shardingExpression> <shards>partition1</shards> </rule> <rule> <namespace>Cont</namespace> <shardingExpression>hash.apply(taobaoId) == 2</shardingExpression> <shards>partition2</shards> </rule> </rules>
4、自定义的路由规则函数类
hash的一种做法是对taobaoId进行md5加密,然后取前几位(我们这里取前两位),然后就可以将不同的taobaoId哈希到不同的用户表(cont_xx)中了。
通过这个技巧,我们可以将不同的taobaoId分散到256中用户表中,分别是cont_00,user_01 ...... cont_ff。因为taobaoId是数字且递增,根据md5的算法,可以将用户数据几乎很均匀的分别到不同的cont表中。
但是这里有个问题是,如果我们的系统的数据越来越多,势必单张表的数据量越来越大,而且根据这种算法无法扩展表,这又会回到单表量大的问题。
下面只是简单的用求余的方法来返回值
/** * 根据某种自定义的hash算法来进行散列,并根据散列的值进行路由 * 常见的水平切分规则有: 基于范围的切分, 比如 memberId > 10000 and memberId < 20000 基于模数的切分, 比如 memberId%128==1 或者 memberId%128==2 或者... 基于哈希(hashing)的切分, 比如hashing(memberId)==someValue等 * @author hj * */ public class HashFunction{ /** * 对三个数据库进行散列分布 * 1、返回其他值,没有在配置文件中配置的,如负数等,在默认数据库中查找 * 2、比如现在配置文件中配置有三个结果进行散列,如果返回为0,那么apply方法只调用一次,如果返回为2, * 那么apply方法就会被调用三次,也就是每次是按照配置文件的顺序依次的调用方法进行判断结果,而不会缓存方法返回值进行判断 * @param taobaoId * @return */ public int apply(Long taobaoId) { //先从缓存获取 没有则查询数据库 //input 可能是taobaoId,拿taobaoId到缓存里去查用户的DB坐标信息。然后把库的编号输出 System.out.println("taobaoId:"+taobaoId); int result = (int)(taobaoId % 3); System.out.println("在第"+(result + 1)+"个数据库中"); return result; } /** 注:只调用一次 taobaoId:3354 在第1个数据库中 注:调用了三次 taobaoId:7043 在第3个数据库中 taobaoId:7043 在第3个数据库中 taobaoId:7043 在第3个数据库中 */ }
/** * cobar的单元测试 */ public class AppTest extends TestCase{ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); ContService contService = (ContService) context.getBean("contService"); /** * 没有使用对象查询直接使用基本类型则到默认的数据源中去查找数据 */ public void test1(){ Cont cont = contService.getContByKey(2L); System.out.println(cont); } /** * 测试添加 */ public void test2(){ Cont cont = new Cont(); cont.setName("gd"); Long taobaoId = new Long(new Random().nextInt(10000)); System.out.println("#"+taobaoId); cont.setTaobaoId(taobaoId); contService.addCont(cont); } /** * 测试使用对象包含taobaoId属性的进行查找 * 使用这种方式可以根据taobaoId分库查找 */ public void test3(){ ContQuery contQuery = new ContQuery(); contQuery.setTaobaoId(2809L); List<Cont> list = contService.getContList(contQuery); if(list != null){ System.out.println(list.get(0)); } } }