读写分离实现

背景

出于某些性能上的要求 我们基于当前代码做了读写分离 读写分离规划

方案

我们使用Spring的AbstractRoutingDataSource来做读写分离

该类提供了determineCurrentLookupKey进行db的获取

可以粗略的认为AbstractRoutingDataSource为一个组合的dataSource

而我们可以根据业务来进行dataSource的选择

那么很简单我们可以考虑在特殊的业务下面进行切换 随后db在getConnection时将会使用到真实的dataSource

/**
 * Retrieve the current target DataSource. Determines the
 * {@link #determineCurrentLookupKey() current lookup key}, performs
 * a lookup in the {@link #setTargetDataSources targetDataSources} map,
 * falls back to the specified
 * {@link #setDefaultTargetDataSource default target DataSource} if necessary.
 * @see #determineCurrentLookupKey()
 */
protected DataSource determineTargetDataSource() {
   Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
   Object lookupKey = determineCurrentLookupKey();
   DataSource dataSource = this.resolvedDataSources.get(lookupKey);
   if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
      dataSource = this.resolvedDefaultDataSource;
   }
   if (dataSource == null) {
      throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
   }
   return dataSource;
}
 
/**
 * Determine the current lookup key. This will typically be
 * implemented to check a thread-bound transaction context.
 * <p>Allows for arbitrary keys. The returned key needs
 * to match the stored lookup key type, as resolved by the
 * {@link #resolveSpecifiedLookupKey} method.
 */
protected abstract Object determineCurrentLookupKey();

很明显当lookupKey为空可以fallback到默认dataSource

<bean id="dataSource-normal" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="java:comp/env/jdbc/erp"/>
</bean>
 
 
<bean id="dataSource-slow" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="java:comp/env/jdbc/erp-slow"/>
</bean>
 
<bean id="dataSource-report" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="java:comp/env/jdbc/erp-saiku"/>
</bean>
<bean id="dataSource-readOnly" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="java:comp/env/jdbc/erp-readOnly"/>
</bean>
 
<bean id="dataSource" class="com.air.tqb.aop.DataSourceRouter">
    <property name="defaultTargetDataSource" ref="dataSource-normal"/>
    <property name="targetDataSources">
        <map>
            <entry key="normal" value-ref="dataSource-normal"/>
            <entry key="slow" value-ref="dataSource-slow"/>
            <entry key="report" value-ref="dataSource-report"/>
            <entry key="readOnly" value-ref="dataSource-readOnly"/>
        </map>
    </property>
</bean>

因此我们可以定义多个数据源 这样可以根据对应的key来设置到不同的数据源 当然当没有key返回时将返回normal数据源

可是由于变量共享【DataSourceRouter需要从某个方法获取当前的dataSource类型】那么普通变量必将导致dataSource会被其他线程操作

因此此处考虑threadLocal 源码分析之ThreadLocal

因此很明显方案比较明确

  1. 定义本地线程变量存储当前db路由key
  2. 根据某些方法名称或者注解等设置特定的db 路由key
  3. 根据当前线程变量路由key返回特定的dataSource

这样一个简单的方案就出现了 根据不同的routingkey返回不同的connection

实现

  1. 定义db类型枚举

    public enum DataSourceType {
     
        NORMAL("normal"), SLOW("slow"), REPORT("report"),READONLY("readOnly");
     
        DataSourceType(String value) {
            this.value = value;
        }
     
        private String value;
     
        public String getValue() {
            return value;
        }
    }
  2. 新建本地线程变量

    public static String getDataSourceRouting() {
        return DATASOURCE_ROUTING_TL.get();
    }
     
    public static void setDataSourceRouting(String routingKey) {
        DATASOURCE_ROUTING_TL.set(routingKey);
    }
  3. 根据方法注解或者名称等设置对应routing key

    @Aspect
    @Component
    @Order(Ordered.HIGHEST_PRECEDENCE + 2)
    public class DataSourceRoutingAspect {
     
        private static final Log logger = LogFactory.getLog(DataSourceRoutingAspect.class);
     
     
        @Around("execution(public * com.air.tqb.service..*.*(..)) && @annotation(com.air.tqb.annoate.DataSource) && @annotation(dataSource)")
        public Object setDataSource(ProceedingJoinPoint joinPoint, DataSource dataSource) throws Throwable {
            DataSourceType dataSourceType = dataSource.value();
            try {
                WxbStatic.setDataSourceRouting(dataSourceType.getValue());
                if (logger.isDebugEnabled()) {
                    logger.debug("DataSourceType[" + dataSourceType.getValue() + "] set.");
                }
                return joinPoint.proceed();
            } finally {
                WxbStatic.clearDataSourceRouting();
                if (logger.isDebugEnabled()) {
                    logger.debug("DataSourceType[" + dataSourceType.getValue() + "] remove.");
                }
            }
     
        }
     
     
        @Around("execution(public * com.air.tqb.service.report..*.*(..))")
        public Object setDataSource(ProceedingJoinPoint joinPoint) throws Throwable {
            DataSourceType dataSourceType = DataSourceType.READONLY;
            try {
                WxbStatic.setDataSourceRouting(dataSourceType.getValue());
                if (logger.isDebugEnabled()) {
                    logger.debug("report DataSourceType[" + dataSourceType.getValue() + "] set.");
                }
                return joinPoint.proceed();
            } finally {
                WxbStatic.clearDataSourceRouting();
                if (logger.isDebugEnabled()) {
                    logger.debug("report DataSourceType[" + dataSourceType.getValue() + "] remove.");
                }
            }
     
        }
    }
  4. 根据本地线程变量返回特定的routingKey

    public class DataSourceRouter extends AbstractRoutingDataSource {
        @Override
        protected Object determineCurrentLookupKey() {
            return WxbStatic.getDataSourceRouting();
        }
    }

实战

根据某些注解设置不同的数据源 比如

@Override
@DataSource(DataSourceType.READONLY)
public PageResult<CustomerCarVO> getPageCustomerCarList(CustomerCarVO customerCarVO, int curPage) throws Exception {
    return super.getPageList("getPageCustomerCarList", "getCountBy", customerCarVO, curPage, AppConstant.MIDPAGESIZE);
}

猜你喜欢

转载自my.oschina.net/qixiaobo025/blog/1625011