Data Source Management | Master-Slave Library Dynamic Routing, AOP Mode Read-Write Separation

Keywords: Programming Spring github Database MySQL

Source code for this article: GitHub. Click here || GitEE. Click here

1. Application of Multiple Data Sources

1. Basic Description

In relatively complex application services, configuring multiple data sources is a common phenomenon, such as: configuring a master-slave database to write data, and configuring a slave database to read data. This read-write separation mode can alleviate database pressure, improve concurrency and stability of the system, and improve execution efficiency.

2. Core API

To address this common problem, learn to query the API of the Service Infrastructure. To put it straight, it's the API of the Query Spring Framework (you haven't used a framework other than Spring for several years). This common business model is basically supported by Spring.

Core API: AbstractRoutingDataSource

The underlying maintenance Map container holds the collection of data sources and provides an abstract way to implement custom routing strategies.

@Nullable
private Map<Object, DataSource> resolvedDataSources;
@Nullable
protected abstract Object determineCurrentLookupKey();

One more sentence: Why is the principle of the frame difficult to understand in an article?Because of the lack of use, the basic consciousness has not been formed, and the basic requirements of familiarizing yourself with the framework principles are: familiar with the various functions of the framework, frequently used, naturally you will understand that salted fish is only tasty after long exposure to salt.

2. Data Source Routing

1. Data Source Management

Configure two data sources

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.jdbc.Driver
    master:
      url: jdbc:mysql://localhost:3306/data_master
      username: root
      password: 123456
    slave:
      url: jdbc:mysql://localhost:3306/data_slave
      username: root
      password: 123456

From the practical development point of view, these two data sources need to configure the master-slave replication process. Then, from the security point of view, the write library can only give write permissions and the read library can only give read permissions.

Map Container Loading

@Configuration
public class DruidConfig {
    // Ignore parameter loading, there are
    @Bean
    @Primary
    public DataSource primaryDataSource() {
        Map<Object, Object> map = new HashMap<>();
        map.put("masterDataSource", masterDataSource());
        map.put("slaveDataSource", slaveDataSource());
        RouteDataSource routeDataSource = new RouteDataSource();
        routeDataSource.setTargetDataSources(map);
        routeDataSource.setDefaultTargetDataSource(masterDataSource());
        return routeDataSource ;
    }
    private DataSource masterDataSource() {
        return getDefDataSource(masterUrl,masterUsername,masterPassword);
    }
    private DataSource slaveDataSource() {
        return getDefDataSource(slaveUrl,slaveUsername,slavePassword);
    }
    private DataSource getDefDataSource (String url,String userName,String passWord){
        DruidDataSource datasource = new DruidDataSource();
        datasource.setDriverClassName(driverClassName);
        datasource.setUrl(url);
        datasource.setUsername(userName);
        datasource.setPassword(passWord);
        return datasource;
    }
}

The Map container here manages two keys, master DataSource and slaveDataSource representing two different libraries, which are loaded using different keys.

2. Container Key Management

ThreadLocal is used to manage thread parameters in the current session and is extremely convenient to access and use.

public class RouteContext implements AutoCloseable {

    private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
    public static void setRouteKey (String key){
        threadLocal.set(key);
    }
    public static String getRouteKey() {
        String key = threadLocal.get();
        return key == null ? "masterDataSource" : key;
    }
    @Override
    public void close() {
        threadLocal.remove();
    }
}

3. Routing Key Implementation

Gets the key of the current data source in ThreadLocal and adapts the associated data source.

public class RouteDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return RouteContext.getRouteKey();
    }
}

3. Separation of reading and writing

1. AOP thinking

Based on AOP's facet idea, different method types set corresponding routing keys so that you can switch to different data sources before the business logic executes.

Aspect
@Component
@Order(1)
public class ReadWriteAop {

    private static Logger LOGGER = LoggerFactory.getLogger(ReadWriteAop.class) ;

    @Before("execution(* com.master.slave.controller.*.*(..))")
    public void setReadDataSourceType() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String method = request.getRequestURI() ;
        boolean rwFlag = readOrWrite(method) ;
        if (rwFlag){
            RouteContext.setRouteKey("slaveDataSource");
        } else {
            RouteContext.setRouteKey("masterDataSource");
        }
        LOGGER.info("Request Method:"+method+";Execution Library:"+RouteContext.getRouteKey());
    }

    private String[] readArr = new String[]{"select","count","query","get","find"} ;
    private boolean readOrWrite (String method){
        for (String readVar:readArr) {
            if (method.contains(readVar)){
                return true ;
            }
        }
        return false ;
    }
}

Common methods of reading: select, count, query, get, find, and so on. The naming of methods follows custom routing rules.

2. Provide test interfaces

Control Layer API

import com.master.slave.entity.User;
import com.master.slave.service.UserService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;

@RestController
public class UserController {

    @Resource
    private UserService userService ;

    @GetMapping("/selectById")
    public User selectById (@RequestParam("id") Integer id) {
        return userService.selectById(id) ;
    }

    @GetMapping("/insert")
    public String insert () {
        User user = new User("Zhang San","write") ;
        userService.insert(user) ;
        return "success" ;
    }
}

Service implementation

@Service
public class UserService {

    @Resource
    private UserMapper userMapper ;

    public User selectById (Integer id) {
        return userMapper.selectById(id) ;
    }

    public void insert (User user){
        userMapper.insert(user);
    }
}

This allows data sources to switch dynamically over and over based on different types of methods.

4. Source code address

GitHub·address
https://github.com/cicadasmile/data-manage-parent
GitEE·address
https://gitee.com/cicadasmile/data-manage-parent

Posted by dey.souvik007 on Sun, 05 Apr 2020 17:38:43 -0700