应用层实现读写分离

**随着业务量的增大,频繁的读写操作对数据库造成很大压力。一种方式是在应用层和数据库层增加缓存来缓解对数据库的压力;另可使用读写分离的方式使应用对数据库的压力降低。 
有两种方式可以实现读写分离:1.应用层实现。2.借助数据库中间件实现。**

使用Spring实现数据库读写分离: 
原理,所有的读操作从库;非读操作主库。 
这里写图片描述 
具体实现(一主一从,基于Spring Aop): 
1.自定义注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 使用指定的数据源,请标记到controller的public方法上
 * 
 * @project risk-control-dynamic-datasource
 * @author 
 * @version 1.0
 * @date 2017年11月24日 上午10:54:32   
 */

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
    String value();
}

2.切面

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;

import com.xxx.riskcontrol.datasource.DynamicDataSourceHolder;
import com.xxx.riskcontrol.datasource.annotation.DataSource;

/**
 * 动态数据源切面
 * 
 * @project risk-control-dynamic-datasource
 * @author 
 * @version 1.0
 * @date 2017年11月24日 上午10:57:18   
 */

public class DataSourceAspect {

    private static final Logger logger = LoggerFactory.getLogger(DataSourceAspect.class);

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 在controller公共方法前执行,设置使用哪个数据源
     * 
     * @author 
     * @version 1.0
     * @date 2017年11月29日 下午6:12:47
     * @param point void
     */
    public void before(JoinPoint point) {
        try {
            Method m = getMethod(point);
            String methodName = getMethodFlag(m);
            log(methodName, "before前");
            if(DynamicDataSourceHolder.getMethodFlag() == null){
                DynamicDataSourceHolder.putMethodFlag(methodName);
                String flag = null;
                try {
                    flag = (String) redisTemplate.opsForValue().get("rwSwitch");
                } catch(Throwable e) {
                    logger.error("redis异常", e);
                }

                if(flag != null){
                    if (m != null && m.isAnnotationPresent(DataSource.class) && Modifier.isPublic(m.getModifiers())) {
                        DataSource data = m.getAnnotation(DataSource.class);
                        DynamicDataSourceHolder.putDataSource(data.value());
                        log(methodName, "before后");
                    }
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 在controller公共方法后执行,清理掉数据源标识(tomcat容器创建的线程会重复使用)
     * 
     * @author 
     * @version 1.0
     * @date 2017年11月29日 下午6:13:17
     * @param point void
     */
    public void after(JoinPoint point) {
        try{
            Method m = getMethod(point);
            String methodName = getMethodFlag(m);
            log(methodName, "after前");
            //由于线程会重复使用,需清理上次操作遗留的值
            if(methodName.equals(DynamicDataSourceHolder.getMethodFlag())){
                DynamicDataSourceHolder.putDataSource(null);
                DynamicDataSourceHolder.putMethodFlag(null);
            }
            log(methodName, "after后");
        }catch (Exception e){
            throw new RuntimeException(e);
        }

    }

    private void log(String methodName, String prefix) {
        if(logger.isDebugEnabled()) {
            logger.debug(prefix + "=====================getDataSourceKey===================================>>>>>>{},{},{}", methodName, DynamicDataSourceHolder.getDataSouce(), DynamicDataSourceHolder.getMethodFlag());
        }
    }

    /**
     * 获取方法
     * 
     * @author 
     * @version 1.0
     * @date 2017年12月13日 上午10:34:56
     * @param point
     * @return
     * @throws NoSuchMethodException
     * @throws SecurityException Method
     */
    private Method getMethod(JoinPoint point) throws NoSuchMethodException, SecurityException {
        Object target = point.getTarget();
        String method = point.getSignature().getName();
        Class<?>[] parameterTypes = ((MethodSignature) point.getSignature()).getMethod().getParameterTypes();
        return target.getClass().getMethod(method, parameterTypes);
    }

    /**
     * 获取类名方法名字符串
     * 
     * @author 
     * @version 1.0
     * @date 2017年12月13日 上午10:21:48
     * @param m
     * @return String
     */
    private String getMethodFlag(Method m) {
        return m.getDeclaringClass().getName() + "." + m.getName();
    }


}

3.数据源标识

/**
 * 数据源标识操作类
 * 
 * @project risk-control-dynamic-datasource
 * @author 
 * @version 1.0
 * @date 2017年11月24日 上午10:55:44   
 */

public class DynamicDataSourceHolder {
    /**
     * 数据源标识
     */
    public static final ThreadLocal<String> threadLocal = new ThreadLocal<String>();
    /**
     * 访问controller的第一个方法(使用第一个方法上的数据源标识)
     */
    public static final ThreadLocal<String> methodFlagThreadLocal = new ThreadLocal<String>();

    public static void putDataSource(String name) {
        threadLocal.set(name);
    }

    public static String getDataSouce() {
        return threadLocal.get();
    }

    public static void putMethodFlag(String name){
        methodFlagThreadLocal.set(name);
    }

    public static String getMethodFlag(){
        return methodFlagThreadLocal.get();
    }
}

4.动态数据源 
import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/** 
* 动态数据源 

* @project risk-control-dynamic-datasource 
* @author 
* @version 1.0 
* @date 2017年11月24日 上午10:53:50 
*/

public class DynamicDataSource extends AbstractRoutingDataSource {

private static final Logger logger = LoggerFactory.getLogger(DynamicDataSource.class);

@Override
protected Object determineCurrentLookupKey() {
    String value = DynamicDataSourceHolder.getDataSouce();
    if(logger.isDebugEnabled()) {
        logger.debug("===========================getDataSourceKey===================================>>>>>>\tdetermineCurrentLookupKey\t" + value);
    }
    return value;
}


5.aop配置

 <bean id="manyDataSourceAspect" class="com.xxx.riskcontrol.datasource.aspect.DataSourceAspect"/>
    <aop:config>
        <aop:aspect id="c" ref="manyDataSourceAspect">
            <aop:pointcut id="tx" expression="execution(* com.XXX..*.controller.*.*(..))"/>
            <aop:before pointcut-ref="tx" method="before"/>
            <aop:after pointcut-ref="tx" method="after"/>
        </aop:aspect>
    </aop:config>

6.配置动态数据源

<bean id="dataSource" class="com.xxx.riskcontrol.datasource.DynamicDataSource">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <!-- write database -->
                <entry key="master" value-ref="masterDataSource"/>
                <!-- read database -->
                <entry key="slave" value-ref="slaveDataSource"/>
            </map>
        </property>
        <property name="defaultTargetDataSource" ref="masterDataSource"/>  
    </bean>

7.controller示例 
//使用从库读数据

@DataSource(DynamicDatasourceConstant.SLAVE_DATABASE_FLAG)
    @RequestMapping(value ="/toApplyInfoList")
    public ModelAndView toapplyInfoList(){
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("applyInfo/applyInfoList");
        return modelAndView;
    }
posted @ 2018-03-02 17:03  w k  阅读(316)  评论(0编辑  收藏  举报