隨著電商平臺(tái)的快速崛起,很多互聯(lián)網(wǎng)公司開(kāi)始面臨著單實(shí)例存儲(chǔ)瓶頸的問(wèn)題.目前業(yè)內(nèi)主流的關(guān)系型數(shù)據(jù)庫(kù)主要就是mysql、oracle、postgre等,拿mysql來(lái)說(shuō),mysql數(shù)據(jù)庫(kù)是樹(shù)形結(jié)構(gòu),復(fù)雜度是logn,他的瓶頸主要來(lái)自頻繁的io的讀寫(xiě),對(duì)磁盤(pán)文件壓力比較大,并且隨著單表里面記錄的瘋狂增長(zhǎng),對(duì)于查詢來(lái)說(shuō),訂單表的深度也在增加.但有些人會(huì)說(shuō)那加索引呀,不好意思這個(gè)只能說(shuō)是一種優(yōu)化方式,沒(méi)有從本質(zhì)上解決問(wèn)題.
所以分庫(kù)分表應(yīng)運(yùn)而生,目前分庫(kù)分表有多種解決方案,比較流行的就是mycat和sharding jdbc,其中mycat基于阿里開(kāi)源的的cobar進(jìn)行開(kāi)發(fā)的,但是目前社區(qū)不是很活躍,而且反饋不是特別理想.這里推薦一下sharding jdbc,同樣也是優(yōu)秀的開(kāi)源作品,來(lái)自當(dāng)當(dāng)網(wǎng),只需要依賴相應(yīng)的jar包就行.兩者各有優(yōu)缺,具體的差異本文不在贅述,請(qǐng)自行谷歌.當(dāng)然如果你們公司的基礎(chǔ)團(tuán)隊(duì)研發(fā)實(shí)力很強(qiáng),也可以自研,原理還是圍繞著AOP思想绝页、sql解析、分片策略等來(lái)做,這塊下文會(huì)有demo進(jìn)行補(bǔ)充.
目前分片的算法主要有兩種:第一種就是Hash這塊算法上可以實(shí)現(xiàn)打散的很均勻,缺點(diǎn)是對(duì)于范圍查詢比較麻煩,第二種就是range.樓主所在的公司是按照訂單創(chuàng)建時(shí)間就行分表,對(duì)于近三個(gè)月的訂單我們是存在關(guān)系型數(shù)據(jù)庫(kù),超過(guò)三個(gè)月的訂單目前我們走的是Hbase,對(duì)于超過(guò)半年前的訂單,建議走離線,這塊主要大數(shù)據(jù)分析了,hive等.sharding jdbc提供了四個(gè)分片算法,可以根據(jù)自己的業(yè)務(wù)場(chǎng)景自己實(shí)現(xiàn)就行.
單分片鍵數(shù)據(jù)源分片算法SingleKeyDatabaseShardingAlgorithm
單分片表分片算法SingleKeyTableShardingAlgorithm
多分片鍵數(shù)據(jù)源分片算法MultipleKeyDatabaseShardingAlgorithm
多分片表分片算法MultipleKeyTableShardingAlgorithm
這四個(gè)算法都支持in妇萄、between溃列、equal.
但是對(duì)于訂單到底如何拆分呢?
首先看觀察最近一年每個(gè)月的訂單量,一般企業(yè)在數(shù)據(jù)庫(kù)建設(shè)的時(shí)候會(huì)考略未來(lái)三到五年的一個(gè)持續(xù)的增長(zhǎng),所以你的技術(shù)架構(gòu)應(yīng)該具有前瞻性.一般一個(gè)月只有幾十萬(wàn)或者幾百萬(wàn)的訂單量暫時(shí)就別分庫(kù)了,一般分表就可以了,這樣也避免了,分庫(kù)以后,帶來(lái)的分布式事務(wù)的問(wèn)題.如果每天的訂單量超過(guò)百萬(wàn)級(jí)別甚至千萬(wàn)級(jí)別,恭喜你,已經(jīng)在業(yè)內(nèi)小有名氣了.這塊具體的拆分,涉及到水平拆分和垂直拆分.水平拆分,想象一下橫著切西瓜的動(dòng)作,一般就是構(gòu)建子表,垂直拆分的話,這塊有些講究,首先要保證分表后這些表的并集是原來(lái)表的全集.一般來(lái)說(shuō),根據(jù)冷熱數(shù)據(jù),或者字典訪問(wèn)的頻率,來(lái)做分離.
由于前面有鋪墊,所以這塊講一下如何用AOP手動(dòng)實(shí)現(xiàn)分庫(kù)呢?
cglib+aspectj:
1).自定義annotation:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
? ? EnumDataSourceType value();
}
2)假如這里有兩個(gè)數(shù)據(jù)庫(kù):
public enum EnumDataSourceType{
? ? ORDER0("order0", "默認(rèn)數(shù)據(jù)源"),
? ? ORDER1("order1", "order1數(shù)據(jù)源");
}
3)Aspect
@Component
@Aspect
public class DataSourceAspect {
@Pointcut("@annotation(com.example..switchdatasource.DataSource)")
private void dataSourcePointCut() {
}
@Around("dataSourcePointCut()")
public Object around(ProceedingJoinPoint joinPoint) {
try {
String dataSource = getDataSourceFromMethod(joinPoint);
//設(shè)置數(shù)據(jù)源
DataSourceSwitcher.setDatasourceKey(dataSource);
return joinPoint.proceed();
}catch (Throwable throwable) {
LogUtils.ERROR.error(joinPoint.getSignature().getDeclaringTypeName() +"." + joinPoint.getSignature().getName() +" error", throwable);
}finally {
DataSourceSwitcher.clearDataSourceType();
}
return? "ERROR";
}
private String getDataSourceFromMethod(ProceedingJoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
//判斷方法體上是否使用注解
? ? ? ? if (method.isAnnotationPresent(DataSource.class)) {
DataSource annotation = method.getAnnotation(DataSource.class);
//獲取該方法上的注解名
? ? ? ? ? ? String source = annotation.value().getSource();
//新order庫(kù)數(shù)據(jù)源
? ? ? ? ? ? if (source.equals(EnumDataSourceType.ORDER1.getSource()) {
return source;
}
}
LogUtils.COMMON.info("annotation by dataSource error, please check it.");
return null;
}
}
4)實(shí)現(xiàn)AbstractRoutingDataSource,這個(gè)是關(guān)鍵
public class DataSourceSwitcher extends AbstractRoutingDataSource {
private static final ThreadLocalDATASOURCE_KEY =new ThreadLocal<>();
public static void clearDataSourceType() {
DATASOURCE_KEY.remove();//清理ThreadLocal變量防止內(nèi)存泄露
}
@Override
? ? protected Object determineCurrentLookupKey() {
String s =DATASOURCE_KEY.get();
return s;
}
public static void setDatasourceKey(String dataSource) {
DATASOURCE_KEY.set(dataSource);
}
}
5)剩余的就是配置datasource文件
這塊注意事項(xiàng),需要指定一個(gè)默認(rèn)的數(shù)據(jù)源.
接下來(lái)重點(diǎn)說(shuō)一下sharding jdbc+spring boot如何實(shí)現(xiàn):
首先看一下sharding實(shí)現(xiàn)分片的流程圖:(這個(gè)圖是網(wǎng)上拷貝過(guò)來(lái)的,地址:https://www.cnblogs.com/mr-yang-localhost/p/8313360.html#_label1)
這塊抽重點(diǎn)講一下幾個(gè)模塊:
1.pom.xml首先maven引入依賴:
<dependency>
<groupId>io.shardingjdbc</groupId>
<artifactId>sharding-jdbc-core-spring-boot-starter</artifactId>
<version>這塊省了</version>
</dependency>
2.分表算法
public final class MultipleKeysModuloTableShardingAlgorithm implements MultipleKeysTableShardingAlgorithm {?
@Override?
public Collection<String> doSharding(final Collection<String> availableTargetNames, final Collection<ShardingValue<?>> shardingValues) {?
Set orderIdValueSet = getShardingValue(shardingValues,"order_id");?
Set userIdValueSet = getShardingValue(shardingValues,"user_id");?
List result =new ArrayList<>();? ? Set<List<Integer>> valueResult = Sets.cartesianProduct(userIdValueSet, orderIdValueSet);?
for (List<Integer> value : valueResult) {?
String suffix = Joiner.on("").join(value.get(0) % 2, value.get(1) % 2);?
for (String tableName : availableTargetNames) {?
if (tableName.endsWith(suffix)) {?
? ? ? ? ? ? ? ? ? ? result.add(tableName);?
? ? ? ? ? ? ? ? }?
? ? ? ? ? ? }?
? ? ? ? }?
return result;?
? ? }?
private Set<Integer> getShardingValue(final Collection<ShardingValue<?>> shardingValues, final String shardingKey) {?
Set valueSet =new HashSet<>();?
ShardingValue shardingValue =null;?
for (ShardingValue<?> each : shardingValues) {?
if (each.getColumnName().equals(shardingKey)) {?
? ? ? ? ? ? ? ? shardingValue = (ShardingValue<Integer>) each;?
break;?
? ? ? ? ? ? }?
? ? ? ? }?
if (null == shardingValue) {?
return valueSet;?
? ? ? ? }?
switch (shardingValue.getType()) {?
case SINGLE:?
? ? ? ? ? ? ? ? valueSet.add(shardingValue.getValue());?
break;?
case LIST:?
? ? ? ? ? ? ? ? valueSet.addAll(shardingValue.getValues());?
break;?
case RANGE:?
for (Integer i = shardingValue.getValueRange().lowerEndpoint(); i <= shardingValue.getValueRange().upperEndpoint(); i++) {?
? ? ? ? ? ? ? ? ? ? valueSet.add(i);?
? ? ? ? ? ? ? ? }?
break;?
default:?
throw new UnsupportedOperationException();?
? ? ? ? }?
return valueSet;?
? ? }?
}?
數(shù)據(jù)庫(kù)測(cè)試結(jié)果:
總結(jié):由于sharding jdbc還不支持動(dòng)態(tài)建表,所以子表目前還需要提前構(gòu)建,但是也可以通過(guò)其他方式去彌補(bǔ).