使用Spring实现读写分离

1 背景

我们一般应用对数据库而言都是“读多写少”,也就说对数据库读取数据的压力比较大,有一个思路就是说采用数据库集群的方案,
其中一个是主库,负责写入数据,我们称之为:写库;
其它都是从库,负责读取数据,我们称之为:读库;

2 方案

解决读写分离的方案有两种:应用层解决和中间件解决。

2.1 应用层解决:

使用Spring实现读写分离_第1张图片

优点:

  1. 多数据源切换方便,由程序自动完成;
  2. 不需要引入中间件;
  3. 理论上支持任何数据库;

缺点:

  1. 由程序员完成,运维参与不到;
  2. 不能做到动态增加数据源;

2.2 中间件解决:

使用Spring实现读写分离_第2张图片

优点:

  1. 源程序不需要做任何改动就可以实现读写分离;
  2. 动态添加数据源不需要重启程序;

缺点:

  1. 程序依赖于中间件,会导致切换数据库变得困难;
  2. 由中间件做了中转代理,性能有所下降;

相关中间件产品使用:
mysql-proxy
Amoeba for MySQL

3 使用Spring基于应用层实现

3.1

使用Spring实现读写分离_第3张图片

在进入Service之前,使用AOP来做出判断,是使用写库还是读库,判断依据可以根据方法名判断,比如说以query、find、get等开头的就走读库,其他的走写库。

3.2 DynamicDataSource:

/**
 * @author kris
 * @create 2018-02-23 10:53
 * 定义动态数据源,实现通过集成Spring提供的AbstractRoutingDataSource,只需要实现determineCurrentLookupKey方法即可
 * 由于DynamicDataSource是单例的,线程不安全的,所以采用ThreadLocal保证线程安全,由DynamicDataSourceHolder完成。
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        // 使用DynamicDataSourceHolder保证线程安全,并且得到当前线程中的数据源key
        return DynamicDataSourceHolder.getDataSourceKey();
    }
}

3.3 DynamicDataSourceHolder:

/**
 * @author kris
 * @create 2018-02-23 10:54
 * 使用ThreadLocal技术来记录当前线程中的数据源的key
 */
public class DynamicDataSourceHolder {

    //写库对应的数据源key
    private static final String MASTER = "master";

    //读库对应的数据源key
    private static final String SLAVE = "slave";

    //使用ThreadLocal记录当前线程的数据源key
    private static final ThreadLocal holder = new ThreadLocal();

    /**
     * 设置数据源key
     * @param key
     */
    public static void putDataSourceKey(String key) {
        holder.set(key);
    }

    /**
     * 获取数据源key
     * @return
     */
    public static String getDataSourceKey() {
        return holder.get();
    }

    /**
     * 标记写库
     */
    public static void markMaster(){
        putDataSourceKey(MASTER);
    }

    /**
     * 标记读库
     */
    public static void markSlave(){
        putDataSourceKey(SLAVE);
    }

}

3.4 DataSourceAspect:

/**
 * @author kris
 * @create 2018-02-23 10:57
 * 定义数据源的AOP切面,通过该Service的方法名判断是应该走主库还是从库
 */
public class DataSourceAspect {

    /**
     * 在进入Service方法之前执行
     *
     * @param point 切面对象
     */
    public void before(JoinPoint point) {
        // 获取到当前执行的方法名
        String methodName = point.getSignature().getName();
        if (isSlave(methodName)) {
            // 标记为主库
            DynamicDataSourceHolder.markSlave();
        } else {
            // 标记为次库
            DynamicDataSourceHolder.markMaster();
        }
    }

    /**
     * 判断是否为次库
     *
     * @param methodName
     * @return
     */
    private Boolean isSlave(String methodName) {
        // 方法名以query、find、get开头的方法名走从库
        return StringUtils.startsWithAny(methodName, "query","find","get");
    }

}

3.5 配置2个数据源:

3.5.1 jdbc.properties:

jdbc.master.driver=com.mysql.jdbc.Driver
jdbc.master.url=jdbc:mysql://localhost:3306/market?useUnicode=true&characterEncoding=UTF-8
jdbc.master.username=root
jdbc.master.password=123456

jdbc.slave01.driver=com.mysql.jdbc.Driver
jdbc.slave01.url=jdbc:mysql://localhost:3307/market?useUnicode=true&characterEncoding=UTF-8
jdbc.slave01.username=root
jdbc.slave01.password=123456

3.5.2 定义连接池:



    
    
    
    

    
    
    
    
    
    
    
    
    
    
    
    

    
    
    
    



    
    
    
    

    
    
    
    
    
    
    
    
    
    
    
    

    
    
    
    

3.5.3 定义DataSource:



    
    
        
            
            
            
        
    
    
    

3.6 配置事务管理以及动态切换数据源切面:

3.6.1 定义事务管理器:



    
    

3.6.2 定义事务策略:



    
        
        
        
        
        
        
        
        
        
        

        
        
    

3.6.3 定义切面:







    
    
    
    

    
    
        
    


你可能感兴趣的:(Java)