美文网首页业务方案架构
数据源管理 | 主从库动态路由,AOP模式读写分离

数据源管理 | 主从库动态路由,AOP模式读写分离

作者: 知了一笑 | 来源:发表于2020-04-01 07:39 被阅读0次

    本文源码:GitHub·点这里 || GitEE·点这里

    一、多数据源应用

    1、基础描述

    在相对复杂的应用服务中,配置多个数据源是常见现象,例如常见的:配置主从数据库用来写数据,再配置一个从库读数据,这种读写分离模式可以缓解数据库压力,提高系统的并发能力和稳定性,执行效率。

    01-1.png

    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
    

    相关文章

      网友评论

        本文标题:数据源管理 | 主从库动态路由,AOP模式读写分离

        本文链接:https://www.haomeiwen.com/subject/cnanuhtx.html