天天看點

資料源管理 | 主從庫動态路由,AOP模式讀寫分離一、多資料源應用二、資料源路由三、讀寫分離四、源代碼位址

一、多資料源應用

1、基礎描述

在相對複雜的應用服務中,配置多個資料源是常見現象,例如常見的:配置主從資料庫用來寫資料,再配置一個從庫讀資料,這種讀寫分離模式可以緩解資料庫壓力,提高系統的并發能力和穩定性,執行效率。

資料源管理 | 主從庫動态路由,AOP模式讀寫分離一、多資料源應用二、資料源路由三、讀寫分離四、源代碼位址

2、核心API

在處理這種常見問題,要學會查詢服務基礎架構的API,說直白點就是查詢Spring架構的API(工作幾年,還沒用過Spring之外的架構搭建環境),這種常用的業務模式,基本上Spring都提供了API支援。

核心API:AbstractRoutingDataSource

底層維護Map容器,用來儲存資料源集合,提供一個抽象方法,實作自定義的路由政策。

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

補刀一句

:為何架構的原理很難通過一篇文章看明白?因為使用的不多,基本意識沒有形成,熟悉架構原理的基本要求:對架構的各種功能都熟悉,經常使用,自然而然的就明白了,鹽大曬的久,鹹魚才夠味。

二、資料源路由

1、資料源管理

配置兩個資料源

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           

從實際開發角度,這兩個資料源需要配置主從複制流程,再基于安全角度,寫庫可以隻給寫權限,讀庫隻給讀權限。

Map容器加載

@Configuration
public class DruidConfig {
    // 忽略參數加載,源碼中有
    @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;
    }
}           

這裡的Map容器管理兩個key,masterDataSource和slaveDataSource代表兩個不同的庫,使用不同的key即加載對應的庫。

2、容器Key管理

使用ThreadLocal管理目前會會話中線程參數,存取使用極其友善。

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、路由Key實作

擷取ThreadLocal中,目前資料源的key,适配相關聯的資料源。

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

三、讀寫分離

1、AOP思維

基于AOP的切面思想,不同的方法類型,去設定對應路由Key,這樣就可以在業務邏輯執行之前,切換到不同的資料源。

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("請求方法:"+method+";執行庫:"+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 ;
    }
}           

常見的讀取方法:select、count、query、get、find等等,方法的命名要遵循自定義的路由規則。

2、提供測試接口

控制層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("張三","write") ;
        userService.insert(user) ;
        return "success" ;
    }
}           

服務實作

@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);
    }
}           

這樣資料源基于不同的類型方法就會一直的動态切換。

四、源代碼位址

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