侧边栏壁纸
博主头像
落叶人生博主等级

走进秋风,寻找秋天的落叶

  • 累计撰写 130562 篇文章
  • 累计创建 28 个标签
  • 累计收到 9 条评论
标签搜索

目 录CONTENT

文章目录

数据源读写分离1 - 使用AOP的方式实现

2022-06-26 星期日 / 0 评论 / 0 点赞 / 64 阅读 / 20369 字

以springboot2.x+mybatis+mysql+HikariCP为例,记录下自己的开发记录。实现思路读写分离有四种实现方式。我这里使用的是AOP切面实现的。目前实现的是一个主(Master)

springboot2.x+mybatis+mysql+HikariCP为例,记录下自己的开发记录。

实现思路

读写分离有四种实现方式。我这里使用的是AOP切面实现的。目前实现的是一个主(Master),两个从(Slave)。废话少说,讲下我的思路。

  • 定义四个数据源,为什么是四个呢? Master*1 ,Slave*2,统一的数据源*1(一下称为DynamicDatasource,这个数据源是其他三个数据源的总和,可以执行它使用哪个数据源(Master,Slave1,Slave2)进行操作).
  • 定义一个DataSourceHolder,存放的是三个数据源(Master,Slave1,Slave2),可以按照条件去获取指定类型的数据源之一。
  • DynamicDataSource替换掉SpringBoot默认提供的数据源。
  • 在方法上使用注解或者使用某一类方法调用前指定使用的数据源。

什么是读写分离

将数据库分为主从库,一个主库用于写数据,多个从库完成读数据的操作。主库之间通过某种机制进行数据的同步,是一种常见的数据库架构。

数据库读写分离架构解决什么问题?

读写分离是用来解决数据库的读写瓶颈的。

在互联网的应用场景中,常常数据量大,并发量高,高可用要求高,一致性要求高,需要使用读写分离架构。需要注意如下问题:

  • 数据库连接池要机型区分,哪些是读连接池,哪些是写连接池,研发的难度会增加。
  • 为了保证高可用,读连接池需能够实现故障自动转移。
  • 主从的一致性需要考虑。

其他减轻数据库压力的方式

  • 缓存。这里就不再过多介绍了。关于缓存,也是比较大的一方面,后面我会单独以一篇文章介绍自己的理解吧。

  • 数据库水平切分。
    数据库水平切分,也是一种常见的数据库架构,是一种通过算法,将数据库进行分割的架构。一个水平切分集群中的每个数据库,通常称为一个“分片”。每一个分片中的数据没有重合,所有分片中的数据并集组成全部数据。
    水平切分架构解决什么问题呢?
    大部分的互联网业务,数据量都非常大,单库容量最容易成为瓶颈,当单库的容量成为了瓶颈,我们希望提高数据库的写性能,降低单库容量的话,就可以采用水平切分了。

上代码

  • pom.xml中加上如下内容。
<!--- mybatis --><!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter --><dependency>    <groupId>org.mybatis.spring.boot</groupId>    <artifactId>mybatis-spring-boot-starter</artifactId>    <version>2.0.1</version></dependency><dependency>    <groupId>mysql</groupId>    <artifactId>mysql-connector-java</artifactId></dependency><!-- HikariCP 数据库连接池--><!-- https://mvnrepository.com/artifact/com.zaxxer/HikariCP --><dependency>    <groupId>com.zaxxer</groupId>    <artifactId>HikariCP</artifactId>    <version>3.3.0</version></dependency>
  • 我们首先定义好三个特定的数据源Master*1,Slave*2
public enum DBTypeEnum {    /**     * 主数据源     */    Master,    /**     * 从数据源1     */    Slave1,    /**     * 从数据源2     */    Slave2}
  • 然后,设置DataSourceHolder。这样我们就定义好了数据源的调用的模型了。
@Slf4jpublic class DBContextHolder {    private static final ThreadLocal<DBTypeEnum> contextHolder = new ThreadLocal<>();    private static final AtomicInteger counter = new AtomicInteger(-1);    public static void set(DBTypeEnum dbType) {        contextHolder.set(dbType);    }    public static DBTypeEnum get() {        return contextHolder.get();    }    public static void master() {        set(DBTypeEnum.Master);        log.debug("使用的数据源是: master");    }    public static void slave() {        int index = counter.getAndIncrement() % 2;        if (counter.get() > 99) {            counter.set(-1);        }        if (index == 0) {            set(DBTypeEnum.Slave1);            log.debug("使用的数据源是: slave1");        } else {            set(DBTypeEnum.Slave2);            log.debug("使用的数据源是: slave2");        }    }}
  • 设置动态数据源,可以指定上面的任何一种数据源去真正的操作数据库。
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;public class DynamicDataSource  extends AbstractRoutingDataSource {    @Override    protected Object determineCurrentLookupKey() {        return DBContextHolder.get();    }}
  • 创建四种数据源的实例,ConfigurationProperties注解中的配置项是在application.propreties中配置的。
@Configurationpublic class DynamicDataSourceConfig {    @Bean    @ConfigurationProperties("spring.datasource.master")    public HikariDataSource masterDataSource() {        return new HikariDataSource();    }    @Bean    @ConfigurationProperties("spring.datasource.slave1")    public HikariDataSource slave1DataSource() {        return new HikariDataSource();    }    @Bean    @ConfigurationProperties("spring.datasource.slave2")    public HikariDataSource slave2DataSource() {        return new HikariDataSource();    }    @Bean    public DataSource dynamicDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,                                        @Qualifier("slave1DataSource") DataSource slave1DataSource,                                        @Qualifier("slave2DataSource") DataSource slave2DataSource) {        Map<Object, Object> targetDataSources = new HashMap<>(3);        // 设置所有的数据源        targetDataSources.put(DBTypeEnum.Master, masterDataSource);        targetDataSources.put(DBTypeEnum.Slave1, slave1DataSource);        targetDataSources.put(DBTypeEnum.Slave2, slave2DataSource);        DynamicDataSource dynamicDataSource = new DynamicDataSource();        // 将写库设置为默认的数据源        dynamicDataSource.setDefaultTargetDataSource(masterDataSource);        dynamicDataSource.setTargetDataSources(targetDataSources);        return dynamicDataSource;    }   }
  • 指定我们使用的数据源配置,url,username,password等。
spring.datasource.master.jdbc-url=jdbc:mysql://10.1.14.177:3306/dor_human?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNullspring.datasource.master.username=rootspring.datasource.master.password=123456#spring.datasource.master.driver-class-name=com.mysql.jdbc.Driverspring.datasource.slave1.jdbc-url=jdbc:mysql://10.1.14.177:3306/dor_human?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNullspring.datasource.slave1.username=rootspring.datasource.slave1.password=123456#spring.datasource.slave1.driver-class-name=com.mysql.jdbc.Driverspring.datasource.slave2.jdbc-url=jdbc:mysql://10.1.14.177:3306/dor_human?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNullspring.datasource.slave2.username=rootspring.datasource.slave2.password=123456#spring.datasource.slave2.driver-class-name=com.mysql.jdbc.Driver
  • 这是我们要替换掉springboot默认的数据源
@EnableTransactionManagement@Configuration@Slf4jpublic class MyBatisConfig {    @Autowired    private DataSource dynamicDataSource;    /**     * SqlSessionFactory     *     * @return SqlSessionFactory     * @throws Exception     */    @Bean    public SqlSessionFactory sqlSessionFactory() throws Exception {        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();        sqlSessionFactoryBean.setDataSource(dynamicDataSource);        sqlSessionFactoryBean.setTypeAliasesPackage("com.fxb.doraemon.human.entity");        sqlSessionFactoryBean.setConfigLocation(                new PathMatchingResourcePatternResolver().getResource("classpath:mapper/mybatis-config.xml"));        sqlSessionFactoryBean.setMapperLocations(                new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*Mapper.xml"));        log.debug("初始化SqlSessionFactory");        return sqlSessionFactoryBean.getObject();    }    /**     * 事务管理器     *     * @return PlatformTransactionManager     */    @Bean    public PlatformTransactionManager platformTransactionManager() {        return new DataSourceTransactionManager(dynamicDataSource);    }}

好了,这样我们的读写分离的准备工作就做好了,接下来要做的就是使用了,这里我们使用AOP的方式,来规定什么时候使用什么数据源。

@Aspect@Component@Slf4jpublic class DataSourceAop {    /**     * 织入读库数据源     */    @Pointcut("execution(public * com.fxb.doraemon.human.service..*.get*(..)) " +            "|| execution(public * com.fxb.doraemon.human.service..*.select*(..))")    public void readPointcut() {    }    /***     * @1:这里有点小问题.     */    @Pointcut("@annotation(com.fxb.doraemon.human.annotation.Master) " +            "|| execution(public * com.fxb.doraemon.human.service..*.save*(..)) " +            "|| execution(public * com.fxb.doraemon.human.service..*.insert*(..)) " +            "|| execution(public * com.fxb.doraemon.human.service..*.update*(..)) " +            "|| execution(public * com.fxb.doraemon.human.service..*.edit*(..)) " +            "|| execution(public * com.fxb.doraemon.human.service..*.delete*(..)) " +            "|| execution(public * com.fxb.doraemon.human.service..*.del*(..)) " +            "|| execution(public * com.fxb.doraemon.human.service..*.remove*(..)) ")    public void writePointcut() {    }    @Before("readPointcut()")    public void read() {        DBContextHolder.slave();    }    @Before("writePointcut()")    public void write() {        DBContextHolder.master();    }}
  • 最后,最重要的一步,不然你会发现,aop怎么都切入不了。那是因为没有启动织入代理方式。在你的启动类上加上下面的这个注解。
@EnableAspectJAutoProxy

验证一下我们的结果:

好了,这样我们的数据分离就配置好了。

最后

下一篇《数据库读写分离的坑,你已经在坑了!》文章,我们来解释一下这个@1的这个问题。

如果想了解最新动态,欢迎关注公众号: 方家小白. 欢迎一起交流学习。

.

.

广告 广告

评论区