如何分库分表

当数据大的时候,都会考虑分库分表的实现。分库分表可以在不同的层做。一般来说有以下几种:

  • jdbc层:实现复杂,属于轻量级,对应用基本没有侵入性;缺点是不能复用数据库连接,在应用部署多的时候资源耗费大,不适于大规模部署。类似当当网的sharding-jdbc.
  • ORM层:比如蘑菇街TSharding框架封装mybatis,实现简单。缺点是必须依赖ORM层,侵入性比较大。
  • DBProxy层:如cobar和mycat,可以做到连接复用,性能不错,完全没侵入性。缺点是实现复杂,框架比较重,维护工作量大。
  • DAO层:实现简单,缺点是分表比较麻烦。美团的框架似乎就是这样做到。

无论怎么做分库分表,其基本思路都是一样的。需要有分库路由,分库规则,分库关键字等。

下面简单用Spring在DAO层做一个分库的实现。假如有2个数据源,通过在RouteKey选择不同的数据源。

设计路由关键字

public enum RouteKey {
    CURRENT,

    HISTORY;

    private static Map map = new HashMap<>(values().length);

    static {
        for (RouteKey routeKey : values()) {
            map.put(routeKey.name(), routeKey);
        }
    }

    public static RouteKey convertRoutekey(String key) {
        return map.get(key);
    }

}

设计路由注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface DataSourceConfig {

    String key();

}

路由规则实现,利用了Spring的AOP思想,对DAO方法做个拦截,进来做到对应用的无侵入性。根据传入的参数,绑定不同的路由key到当前线程,为了后续获取connection时候需要。

@Component
@Aspect
//@Order(0)
public class DataSourceAspect {

    private static final LocalVariableTableParameterNameDiscoverer parameterNameDiscoverer =
            new LocalVariableTableParameterNameDiscoverer();

    private static final ConcurrentHashMap paramNameMap = new ConcurrentHashMap<>();

    @Around(value = "@annotation(dataSourceConfig)", argNames = "joinPoint, dataSourceConfig")
//    @Around(value = "@annotation(dataSourceConfig) && args(dataSourceConfig)")
    public Object dataSourceAspect(ProceedingJoinPoint joinPoint, DataSourceConfig dataSourceConfig) throws Throwable {
        String keyName = dataSourceConfig.key();

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();


        String[] paramNames = paramNameMap.get(method);
        if (paramNames == null) {
            synchronized (this) {
                String[] names = paramNameMap.get(method);
                if (names == null){
                    names = parameterNameDiscoverer.getParameterNames(method);
                }

                paramNames = names;

            }
        }

        Object[] args = joinPoint.getArgs();
        RouteKey routeKey;

        int i = 0;
        for (String name : paramNames) {
            if (name.equals(keyName)) {
                routeKey = (RouteKey) args[i];
                DataSourceKeyHolder.setRouteKey(routeKey);
                break;
            }

            i++;
        }

        try {
            return joinPoint.proceed();

        } finally {

            DataSourceKeyHolder.clear();
        }
    }
}

 

扩展AbstractRoutingDataSource类,重载determineCurrentLookupKey方法,路由到不同的库

public class CustomDataSource extends AbstractRoutingDataSource {


    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceKeyHolder.getRouteKey().name();
    }
}

DataSourceKeyHolder是个ThreadLocal,将路由key绑定到当前线程。Spring很多涉及事务操作的都会用到ThreadLocal。

public class DataSourceKeyHolder {

    private static final ThreadLocal holder = new ThreadLocal(){
        protected RouteKey initialValue() {
            return RouteKey.CURRENT;
        }
    };

    public static RouteKey getRouteKey(){
        return holder.get();
    }


    public static void setRouteKey(RouteKey key){
        holder.set(key);
    }


    public static void clear(){
        holder.remove();
    }

}

数据源配置。Spring事务会在方法前获取数据连接connection,但是这时还没有到DAO层进行路由选择,因此需要延迟加载数据源,需要用到LazyConnectionDataSourceProxy。

 
        
        
        
        
    

    
        
        
        
        
    

        
      
          
              
              
          
      
    

    
        
    


    
        
    


    

    
        
    

最后使用

@Transactional
    @DataSourceConfig(key = "key")
    public void update(RouteKey key) {

        String sql2 = "insert into customer(name, age, address,  code) values ('Jack', 12, 'BJ', '002')";
        int effectNum = jdbcTemplate.update(sql2);

        System.out.println(effectNum);
    }

 

你可能感兴趣的:(数据库)