Implementation of read and write separation

background

Due to some performance requirements, we have made a read-  write separation plan based on the current code.

plan

We use Spring's AbstractRoutingDataSource to do read-write separation

This class provides determineCurrentLookupKey for db acquisition

It can be roughly considered that AbstractRoutingDataSource is a combined dataSource

And we can choose the dataSource according to the business

Then it's very simple, we can consider switching under a special business, and then db will use the real dataSource when getConnection

/**
 * 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();

Obviously when the lookupKey is empty it can fallback to the default 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>

Therefore, we can define multiple data sources, which can be set to different data sources according to the corresponding key. Of course, when no key is returned, the normal data source will be returned.

However, due to variable sharing [DataSourceRouter needs to obtain the current dataSource type from a method], ordinary variables will inevitably cause dataSource to be operated by other threads

Therefore, consider ThreadLocal for threadLocal  source code analysis here

Therefore, it is clear that the plan is relatively clear

  1. Define a local thread variable to store the current db routing key
  2. Set a specific db routing key based on some method name or annotation, etc.
  3. Returns a specific dataSource based on the current thread variable routing key

Such a simple solution appears to return different connections according to different routingkeys

accomplish

  1. Define db type enumeration

    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. Create a new thread-local variable

    public static String getDataSourceRouting() {
        return DATASOURCE_ROUTING_TL.get();
    }
     
    public static void setDataSourceRouting(String routingKey) {
        DATASOURCE_ROUTING_TL.set(routingKey);
    }
  3. Set the corresponding routing key according to the method annotation or name, etc.

    @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. Return a specific routingKey based on a local thread variable

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

     

actual combat

Set up different data sources according to some annotations such as

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

 

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326940800&siteId=291194637