作為一種簡(jiǎn)單的分表分庫(kù)的中間件,sharding一種完全基于程序的分表分庫(kù)策略,無(wú)需其他的代理服務(wù),是一種能夠快速應(yīng)用在開(kāi)發(fā)中的策略。
本文是對(duì)我參加過(guò)的一個(gè)項(xiàng)目使用sharding的一個(gè)簡(jiǎn)單總結(jié)。我們使用的很簡(jiǎn)單,把原來(lái)純使用程序代碼分表的程序替換為sharding,sharding集成到j(luò)dbcTemplate中使用。
pom
<!-- 引入sharding-jdbc核心模塊 -->
<dependency>
<groupId>com.dangdang</groupId>
<artifactId>sharding-jdbc-core</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>com.dangdang</groupId>
<artifactId>sharding-jdbc-config-spring</artifactId>
<version>1.4.1</version>
</dependency>
首先要引入sharding的依賴,之前我們項(xiàng)目使用的是1.4.0,在并發(fā)高的時(shí)候會(huì)產(chǎn)生一些Caused by:java.lang.IndexOutOfBoundsException: Index: 1, Size: 1的異常,應(yīng)該是1.4.0版本內(nèi)部有些變量公用導(dǎo)致的,升級(jí)1.4.1就沒(méi)有這個(gè)問(wèn)題了。
sprint-data.xml
<?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:context="http://www.springframework.org/schema/context"
xmlns:rdb="http://www.dangdang.com/schema/ddframe/rdb"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.dangdang.com/schema/ddframe/rdb
http://www.dangdang.com/schema/ddframe/rdb/rdb.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
">
<bean id="defaultDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName">
<value>${datasource.driverClassName}</value>
</property>
<property name="url">
<value>${datasource.url}</value>
</property>
<property name="username">
<value>${datasource.username}</value>
</property>
<property name="password">
<value>${datasource.password}</value>
</property>
<property name="maxActive">
<value>${datasource.maxActive}</value>
</property>
<property name="maxIdle">
<value>${datasource.maxIdle}</value>
</property>
<property name="maxWait">
<value>${datasource.maxWait}</value>
</property>
<property name="defaultAutoCommit">
<value>${datasource.defaultAutoCommit}</value>
</property>
</bean>
<bean id="readDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName">
<value>${datasource.readonly.driverClassName}</value>
</property>
<property name="url">
<value>${datasource.readonly.url}</value>
</property>
<property name="username">
<value>${datasource.readonly.username}</value>
</property>
<property name="password">
<value>${datasource.readonly.password}</value>
</property>
<property name="maxActive">
<value>${datasource.readonly.maxActive}</value>
</property>
<property name="maxIdle">
<value>${datasource.readonly.maxIdle}</value>
</property>
<property name="maxWait">
<value>${datasource.readonly.maxWait}</value>
</property>
<property name="defaultAutoCommit">
<value>${datasource.readonly.defaultAutoCommit}</value>
</property>
</bean>
<rdb:master-slave-data-source id="masterSlaveDataSource" master-data-source-ref="defaultDataSource" slave-data-sources-ref="readDataSource" />
<!-- user_ticket的分表策略 -->
<rdb:strategy id="userTicketTableStrategy" sharding-columns="username"
algorithm-class="com.common.sharding.SingleKeyHashShardingAlgorithm" />
<rdb:data-source id="shardingDataSource">
<rdb:sharding-rule data-sources="masterSlaveDataSource">
<rdb:table-rules>
<rdb:table-rule logic-table="user_ticket" actual-tables="user_ticket_01,user_ticket_02,user_ticket_03,user_ticket_04,user_ticket_05,user_ticket_06,user_ticket_07,user_ticket_08,user_ticket_09,user_ticket_10,user_ticket_11,user_ticket_12,user_ticket_13,user_ticket_14,user_ticket_15,user_ticket_16,user_ticket_17,user_ticket_18,user_ticket_19,user_ticket_20"
table-strategy="userTicketTableStrategy" />
</rdb:table-rules>
</rdb:sharding-rule>
</rdb:data-source>
<!--JdbcTemplate -->
<bean id="shardingJdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource">
<ref bean="shardingDataSource" />
</property>
</bean>
<!--TransactionManager -->
<bean name="shardingTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="shardingDataSource"></property>
</bean>
<!-- 使用annotation定義事務(wù),實(shí)際做法就是在類上使用@Transactional注解,這個(gè)配置才能開(kāi)啟注解事務(wù)支持 -->
<tx:annotation-driven transaction-manager="shardingTransactionManager" proxy-target-class="true"/>
</beans>
在我們的項(xiàng)目中使用了讀寫分離,其實(shí)就是只實(shí)現(xiàn)單庫(kù)的分表,多庫(kù)應(yīng)用也差不多,如果有同道找到相關(guān)分庫(kù)的文章請(qǐng)?jiān)谙旅媪粞?。sharding的使用方式很簡(jiǎn)單,就是對(duì)數(shù)據(jù)源進(jìn)行包裝,配上分表策略,然后把包裝的數(shù)據(jù)源配到JdbcTemplate和事務(wù)中。
<rdb:table-rules>
<rdb:table-rule logic-table="user_ticket" actual-tables="user_ticket_01,user_ticket_02,user_ticket_03,user_ticket_04,user_ticket_05,user_ticket_06,user_ticket_07,user_ticket_08,user_ticket_09,user_ticket_10,user_ticket_11,user_ticket_12,user_ticket_13,user_ticket_14,user_ticket_15,user_ticket_16,user_ticket_17,user_ticket_18,user_ticket_19,user_ticket_20"
table-strategy="userTicketTableStrategy" />
</rdb:table-rules>
把20張表合成一個(gè)來(lái)使用。
<rdb:strategy id="userTicketTableStrategy" sharding-columns="username"
algorithm-class="com.common.sharding.SingleKeyHashShardingAlgorithm" />
userTicketTableStrategy策略以u(píng)sername做hash分區(qū),具體策略代碼如下:
package com.common.sharding;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import com.dangdang.ddframe.rdb.sharding.api.ShardingValue;
import com.dangdang.ddframe.rdb.sharding.api.strategy.table.SingleKeyTableShardingAlgorithm;
/**
* 根據(jù)單個(gè)字段hash來(lái)分表的實(shí)現(xiàn)
*
*/
public class SingleKeyHashShardingAlgorithm implements SingleKeyTableShardingAlgorithm<String> {
/**
*
* allActualTableNames 所有的物理表名;shardingValue 分表的key值屬性
*/
public String doEqualSharding(final Collection<String> allActualTableNames,
final ShardingValue<String> shardingValue) {
// 邏輯表名
String logicTableName = shardingValue.getLogicTableName();
// 根據(jù)比較的值,算出物理分表
String actualTableName = logicTableName
+ "_"
+ String.format("%02d",
(Math.abs(shardingValue.getValue().hashCode()) % allActualTableNames.size()) + 1);
if (allActualTableNames.contains(actualTableName))
return actualTableName;
// 如果沒(méi)有匹配到相應(yīng)的物理表名,那一定是有問(wèn)題的
throw new UnsupportedOperationException();
}
/**
* 支持分表字段的in表達(dá)式
*/
@Override
public Collection<String> doInSharding(Collection<String> allActualTableNames,
ShardingValue<String> paramShardingValue) {
// in表達(dá)式的值對(duì)應(yīng)的數(shù)據(jù)表
Set<String> inValueTables = new HashSet<String>();
Collection<String> inValues = paramShardingValue.getValues();
String logicTableName = paramShardingValue.getLogicTableName();
for (String value : inValues) {
String actualTableName = logicTableName + "_" + value.hashCode() % allActualTableNames.size();
if (allActualTableNames.contains(actualTableName))
inValueTables.add(actualTableName);
}
if (inValueTables.size() == 0)
throw new UnsupportedOperationException();
return inValueTables;
}
@Override
public Collection<String> doBetweenSharding(Collection<String> allActualTableNames,
ShardingValue<String> paramShardingValue) {
// 不支持between操作,有需求的時(shí)候再實(shí)現(xiàn)
throw new UnsupportedOperationException();
}
}
使用方式其實(shí)和普通jdbctemplate沒(méi)什么區(qū)別,用user_ticket代替所有的分區(qū)表。
package com.base.dao;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import com.base.bean.UserTicket;
@Repository
public class UserTicketDao {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* @param username
* @param page
* @return
* @throws Exception
*/
public List<UserTicket> getUserTicketDetail(String username, int pageid, int pagesize) throws Exception {
StringBuilder sqlBuilder = new StringBuilder();
sqlBuilder
.append("select * from user_ticket where username = ? and typecode='vod' and displaytime < now() order by id desc limit ?, ?");
return jdbcTemplate.query(sqlBuilder.toString(), new Object[] {username, (pageid - 1) * pagesize, pagesize},
new int[] {Types.VARCHAR, Types.INTEGER, Types.INTEGER}, new BeanPropertyRowMapper<UserTicket>(
UserTicket.class));
}
/**
*
* @param ut
*/
public void updateUserTicket(UserTicket ut) {
StringBuilder sqlBuilder = new StringBuilder();
sqlBuilder
.append("update user_ticket set username=?, times=?, validtime=?, typecode=?, fromcode=?, createtime=?, usetime=?, channelid=?, status=?, displaytime=?, starttime=? where ticketno=? and username=?");
Object[] args = new Object[] {ut.getUsername(), ut.getTimes(), ut.getValidtime(), ut.getTypecode(),
ut.getFromcode(), ut.getCreatetime(), ut.getUsetime(), ut.getChannelid(), ut.getStatus(),
ut.getDisplaytime(), ut.getStarttime(), ut.getTicketno(), ut.getUsername()};
int[] types = new int[] {Types.VARCHAR, Types.INTEGER, Types.TIMESTAMP, Types.VARCHAR, Types.VARCHAR,
Types.TIMESTAMP, Types.TIMESTAMP, Types.INTEGER, Types.INTEGER, Types.TIMESTAMP, Types.TIMESTAMP,
Types.VARCHAR, Types.VARCHAR};
jdbcTemplate.update(sqlBuilder.toString(), args, types);
}
/**
* @param list
* @return
*/
public void batchSaveUserTicket(final List<UserTicket> list) {
for (int k = 0; k < list.size(); k++) {
StringBuffer sb = new StringBuffer();
UserTicket ut = list.get(k);
sb.append("insert into user_ticket(ticketno, username, validtime, typecode, fromcode, createtime, channelid, status, displaytime, starttime)");
sb.append("values(");
sb.append("'").append(ut.getTicketno()).append("'").append(",");
sb.append("'").append(ut.getUsername()).append("'").append(",");
sb.append("'").append(new SuperDate(ut.getValidtime()).getDateTimeString()).append("'").append(",");
sb.append("'").append(ut.getTypecode()).append("'").append(",");
sb.append("'").append(ut.getFromcode()).append("'").append(",");
sb.append("'").append(new SuperDate(ut.getCreatetime()).getDateTimeString()).append("'").append(",");
sb.append(ut.getChannelid()).append(",");
sb.append(ut.getStatus()).append(",");
sb.append("'").append(new SuperDate(ut.getDisplaytime()).getDateTimeString()).append("'").append(",");
sb.append("'").append(new SuperDate(ut.getStarttime()).getDateTimeString()).append("'");
sb.append(")");
jdbcTemplate.update(sb.toString());
}
}
}
最后提一下遷移過(guò)程中發(fā)現(xiàn)的坑,雖然說(shuō)使用基本和原生相同,但還有一些需要注意一下,1.表名錢不要加上庫(kù)名,原生的情況加庫(kù)名,不加庫(kù)名其實(shí)是一樣的,但使用shareding的表就會(huì)報(bào)錯(cuò);2.shareding是不支持jdbctemplate的批量修改操作的。
歡迎大家指正。