随着电商平台的快速崛起,很多互联网公司开始面临着单实例存储瓶颈的问题.目前业内主流的关系型数据库主要就是mysql、oracle、postgre等,拿mysql来说,mysql数据库是树形结构,复杂度是logn,他的瓶颈主要来自频繁的io的读写,对磁盘文件压力比较大,并且随着单表里面记录的疯狂增长,对于查询来说,订单表的深度也在增加.但有些人会说那加索引呀,不好意思这个只能说是一种优化方式,没有从本质上解决问题.
所以分库分表应运而生,目前分库分表有多种解决方案,比较流行的就是mycat和sharding jdbc,其中mycat基于阿里开源的的cobar进行开发的,但是目前社区不是很活跃,而且反馈不是特别理想.这里推荐一下sharding jdbc,同样也是优秀的开源作品,来自当当网,只需要依赖相应的jar包就行.两者各有优缺,具体的差异本文不在赘述,请自行谷歌.当然如果你们公司的基础团队研发实力很强,也可以自研,原理还是围绕着AOP思想、sql解析、分片策略等来做,这块下文会有demo进行补充.
目前分片的算法主要有两种:第一种就是Hash这块算法上可以实现打散的很均匀,缺点是对于范围查询比较麻烦,第二种就是range.楼主所在的公司是按照订单创建时间就行分表,对于近三个月的订单我们是存在关系型数据库,超过三个月的订单目前我们走的是Hbase,对于超过半年前的订单,建议走离线,这块主要大数据分析了,hive等.sharding jdbc提供了四个分片算法,可以根据自己的业务场景自己实现就行.
单分片键数据源分片算法SingleKeyDatabaseShardingAlgorithm
单分片表分片算法SingleKeyTableShardingAlgorithm
多分片键数据源分片算法MultipleKeyDatabaseShardingAlgorithm
多分片表分片算法MultipleKeyTableShardingAlgorithm
这四个算法都支持in、between、equal.
但是对于订单到底如何拆分呢?
首先看观察最近一年每个月的订单量,一般企业在数据库建设的时候会考略未来三到五年的一个持续的增长,所以你的技术架构应该具有前瞻性.一般一个月只有几十万或者几百万的订单量暂时就别分库了,一般分表就可以了,这样也避免了,分库以后,带来的分布式事务的问题.如果每天的订单量超过百万级别甚至千万级别,恭喜你,已经在业内小有名气了.这块具体的拆分,涉及到水平拆分和垂直拆分.水平拆分,想象一下横着切西瓜的动作,一般就是构建子表,垂直拆分的话,这块有些讲究,首先要保证分表后这些表的并集是原来表的全集.一般来说,根据冷热数据,或者字典访问的频率,来做分离.
由于前面有铺垫,所以这块讲一下如何用AOP手动实现分库呢?
cglib+aspectj:
1).自定义annotation:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
EnumDataSourceType value();
}
2)假如这里有两个数据库:
public enum EnumDataSourceType{
ORDER0("order0", "默认数据源"),
ORDER1("order1", "order1数据源");
}
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);
//设置数据源
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库数据源
if (source.equals(EnumDataSourceType.ORDER1.getSource()) {
return source;
}
}
LogUtils.COMMON.info("annotation by dataSource error, please check it.");
return null;
}
}
4)实现AbstractRoutingDataSource,这个是关键
public class DataSourceSwitcher extends AbstractRoutingDataSource {
private static final ThreadLocalDATASOURCE_KEY =new ThreadLocal<>();
public static void clearDataSourceType() {
DATASOURCE_KEY.remove();//清理ThreadLocal变量防止内存泄露
}
@Override
protected Object determineCurrentLookupKey() {
String s =DATASOURCE_KEY.get();
return s;
}
public static void setDatasourceKey(String dataSource) {
DATASOURCE_KEY.set(dataSource);
}
}
5)剩余的就是配置datasource文件
这块注意事项,需要指定一个默认的数据源.
接下来重点说一下sharding jdbc+spring boot如何实现:
首先看一下sharding实现分片的流程图:(这个图是网上拷贝过来的,地址:https://www.cnblogs.com/mr-yang-localhost/p/8313360.html#_label1)
这块抽重点讲一下几个模块:
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;
}
}
数据库测试结果:
总结:由于sharding jdbc还不支持动态建表,所以子表目前还需要提前构建,但是也可以通过其他方式去弥补.
网友评论