基于Spring Boot与MyBatis框架构建动态读写分离模型

2021-03-17 07:32张旭刚张昕高若寒
微型电脑应用 2021年2期
关键词:数据源调用线程

张旭刚, 张昕, 高若寒

(国电南瑞科技股份有限公司 信息系统集成分公司, 江苏 南京 210000)

0 引言

读写分离集群,不仅提高了系统的健壮性和可靠性,以及系统的吞吐量和性能,保障了系统业务的连续性,而且也实现了资源的最大利用率。当前的实现方法主要通过静态方式配置,主要有中间件方式,如amoeba和mysql-proxy,分业务方式,对读写操作配置url。静态方式缺乏灵活性,无法根据系统负载、用户需求等情况,实现资源的快速动态收缩,难以满足在不停机的条件下进行数据源切换,无法保证业务的连续性。

利用Spring Boot和MyBatis框架提供的优势,通过面向切面编程AOP,实现一种对应用透明、数据源可以动态收缩与切换的模型。

1 Spring Boot架构

Spring Boot是由Pivotal团队提供,简化Spring开发的微服务框架。通过约定优于配置和起步依赖,简化复杂的依赖关系,大量减少XML配置文件,基本实现自动化位置,能够快速创建独立运行的Spring项目,并且集成了主流框架,如AOP和MyBatis。为实现动态读写分离模型,主要利用面向切面编程技术AOP、MyBatis映射、SpringBoot的类Abstract Routing Data Source和Thread Local实现不同线程间的数据隔离[1]。

1.1 Spring AOP

Spring AOP(Aspect-Oriented Programming,面向切面编程),是一种称为“横切”的技术,把与业务无关逻辑,但为业务模块共同调用的逻辑或功能封装起来,将其命名为“Aspect”,即方面,减少系统的重复代码,降低模块间的耦合度,便于后期的操作和维护。在论文中,主要使用AOP的前置通知,拦截MyBatis映射的SQL语句,动态选择数据源。

1.2 MyBatis映射

Mybatis是一个支持普通SQL查询、存储过程和高级映射的优秀持久层框架,在持久层映射关系的开发中,可以不用写实现类,能以代理方式自动生成实现代码,同时SQL语句写在映射XML文件中,实现了代码与SQL分离,降低耦合度。在映射XML文件中,通过id标识不同类型的SQL语句,对查询、插入、删除和更新语句进行区分,如查询语句的id前缀为query,删除语句的id前缀为delete,通过甄别判断为不同SQL语句选择对应的数据源,实现动态的读写分离。

1.3 Abstract Routing Data Source

Spring Boot提供了Abstract Routing Data Source根据用户定义的规则选择当前的数据源,可以在执行SQL操作前,设置使用的数据源,实现动态路由数据源的模型,它的方法determine Target Data Source()返回一个数据源,在该方法内部会调用抽象方法determine Current Lookup Key()决定使用哪个数据源,lookup key键通常是通过Thread Local绑定的上下文来实现。

1.4 Thread Local

Thread Local作用是提供线程内的局部变量,维护变量时Thread Local为每个使用该变量的线程提供独立的变量副本。

在面向切面编程AOP的前置通知中通过Thread Local设置线程的数据源类型,是读数据源还是写数据源。在返回数据源的时候,通过determine Current Lookup Key()调用Thread Local取得线程的数据源类型,从而为本次访问指定具体的数据源,是访问读库还是写库[2]。

2 动态读写分离设计与实现

2.1 总体架构

程序实现基于Spring Boot框架,通过Maven进行编译、测试和打包。Spring Boot基于Spring,减少了配置,简化了编码,使开发更高效便捷[3]。整体实现分五层,第一层客户端即应用程序,发起数据访问;第二层访问到DAO(数据访问对象),访问的sql语句配置在MyBatis的映射文件里,与程序的DAO接口形成映射关系,由MyBatis自动实现接口的文件,对数据库进行访问;第三层,AOP,即面向切面编程层,在DAO访问数据库之前,进行拦截,根据访问id进行动态选择数据源,如果是查询语句则访问读库,如果是修改语句,则指向到主数据库,实现数据的读写分离,主要功能有负载均衡、高可用性、SQL过滤、读写分离和数据库路由等;第四层,创建和封装两个数据源,每个数据源创建一个数据库资源池,分别指向写数据库和读数据库;第五层,主备数据库之间,通过binlog进行数据实时同步,并进行故障切换[4]。

通过上面五层,与Spring Boot和MyBatis架构构建程序一致,对原有程序透明,无任何侵入,原程序不需要任何改造,简单便捷地实现了动态的数据库读写分离[5]。

同时,这种结构可以进行横向扩展,当性能无法满足需求时,添加数据源,添加数据库,进行负载分担,对应用透明,如图1所示。

2.2 读写分离的实现

实现MySQL数据库的动态读写分离,读写分离的实现类图,如图2所示。

主要由四个类实现,Dynamic Data Source动态的根据数据源的值返回数据源;Data Source Context Holder封装了Thread Local,用于设置和获取本次访问的数据源的值;Dynamic Data Source Aspect实现AOP的前置通知,拦截和解析SQL的id,根据id判断是读操作还是写操作,通过Data Source Context Holder动态设置数据源的值,然后Dynamic Data Source获取到要访问的数据源;Multi Data Source Con-fig配置多个数据源,在应用启动后有多个数据源可以选择。

图1 总体结构图

图2 读写分离的实现类图

Dynamic Data Source,用于获取数据库访问的数据源,如果是查询操作,返回只读库数据源,如果是增删改则访问写库。继承Abstract Routing Data Source并重写其中的方法determine Current Lookup Key(),该方法调用封装了Thread Local的Database Context Holder,获取当前线程的Database Type。

Data Source Context Holder,用户设置数据库访问的数据源,具体设置通过切面拦截调用该类的方法set Data Source Type。该类拥有一个Thread Local的静态常量私有属性private static final Thread Local〈String〉 CONTEXT_HOLDER = new Thread Local〈String〉(),静态方法set Data Source Type(String data Source Key)和get Data Source Type()通过CONTEXT_HOLDER属性,用于标识数据源,给每个访问数据库的线程返回要访问的数据源。

Dynamic Data Source Aspect用于定义要拦截的SQL操作,通过前置通知解析MyBatis中配置的id,根据id判断SQL操作是读操作还是增删改,并利用Data Source Context Holder的静态方法设置当前线程的数据源类型。在进行数据源选择时,Dynamic Data Source返回设置的当前线程的数据源类型,当前线程准确地找到需要访问的数据源。它的主要实现方法如下。

@Pointcut("execution( * com.sboot.dao.*.*(..))")

public void daoAspect() {

}

@Before("daoAspect()")

public void switchDataSource(JoinPoint point) {

System.out.println("Begin to execute "+point.getSignature().getName());

Boolean isQueryMethod = isQueryMethod(point.getSignature().getName());

if (isQueryMethod) {

DataSourceContextHolder.setDataSourceType("slave");

System.out.println("Slave DataSource begin to execute "+point.getSignature().getName());

}

}

Multi Data Source Config,是一个基于注解的配置,主要封装了写和读两个数据源,实现多数据源,需要取消Spring Boot的自动数据源配置,主要实现方法如下。

@Bean("dynamicDataSource")

public DataSource dynamicDataSource() {

Map〈Object, Object〉 targetDataSources = new HashMap〈Object, Object〉();

targetDataSources.put("master", masterDataSource());

targetDataSources.put("slave", slaveDataSource());

DynamicDataSource dataSource = new DynamicDataSource();

dataSource.setTargetDataSources(targetDataSources);

dataSource.setDefaultTargetDataSource(masterDataSource());

return dataSource;

}

2.3 配置多数据源

在application.yml中添加两个数据源[6]:

pring:

datasource:

master://写数据源的配置

url:

jdbc:mysql://192.168.10.12:3306/masterdb?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true

username: studba

password: stuDba1

driverClassName: com.mysql.cj.jdbc.Driver

slave://读数据源的配置

url:

jdbc:mysql://192.168.10.13:3306/slavedb?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true

username: studba

password: stuDba1

driverClassName: com.mysql.cj.jdbc.Driver

然后在类DataSourceConfig中,利用注解的方式生成数据源:

@Primary

@Bean("masterDataSource")

@ConfigurationProperties(prefix = "spring.datasource.master")

public DataSource masterDataSource() {

return DataSourceBuilder.create().build();

}

通过@ConfigurationProperties注解把在配置文件的配置自动的匹配配置数据源需要的值,生成数据源。备数据源的原理与上面一致。

2.4 数据访问流程

数据访问流程,如图3所示。

图3 数据访问流程

(1) 客户端访问数据库,正常流程走到DAO层,MyBatis进行映射接口,取得映射的sql语句,如findStudentById。

(2) 取得sql语句访问数据库。

(3) 通过@Before("daoAspect()")拦截访问,并检查是查询语句,设置数据源为读数据库。

判断出是find开头的sql语句,设置读数据源DataSourceContextHolder.setDataSourceType("slave")。

(4) MultiDynamicDataSource

在方法determineCurrentLookupKey()中返回数据源类型return DataSourceContextHolder.getDataSourceType()。

(5) MultiDynamicDataSource的方法

determineTargetDataSource()根据上面determineCurrentLookupKey()函数返回的key值选择一个指定的数据源。

(6) 返回要访问的数据源,本次访问返回的是读数据源。

(7) 根据返回的读数据源,访问读数据库。

2.5 应用验证

通过学生ID查询学生信息进行验证,查询操作到读库进行操作。查询学生信息的MyBatis SQL id是findStudent ById,在浏览器输入http://192.168.1.10:8080/stuInfo,进行查询,日志输出信息,如图4所示。

图4 测试验证

日志打印出执行sql语句findStudentById,动态选择读数据源Slave DataSource执行。

3 总结

本文基于Spring Boot和MyBatis框架,实现了动态的MySQL读写分离模型,方法简单、便捷,对应用透明,低耦合,无侵入性,安装和拆卸对现有程序无任何影响,没有额外的成本。后续可加入多数据源,通过zookeeper进行状态监控和管理,实现更智能和动态的数据库的横向扩展和收缩,满足云计算场景需求。

猜你喜欢
数据源调用线程
基于C#线程实验探究
基于国产化环境的线程池模型研究与实现
核电项目物项调用管理的应用研究
线程池调度对服务器性能影响的研究*
Web 大数据系统数据源选择*
基于不同网络数据源的期刊评价研究
基于系统调用的恶意软件检测技术研究
基于真值发现的冲突数据源质量评价算法
分布式异构数据源标准化查询设计与实现
利用RFC技术实现SAP系统接口通信