【服务实现读写分离】

文章目录

  • 什么是读写分离
  • 基于Spring实现实现读写分离
  • 项目中常用的数据源切换依赖包

什么是读写分离

服务读写分离(Service Read-Write Splitting)是一种常见的数据库架构设计模式,旨在提高系统的性能和可扩展性。通过将读操作和写操作分离到不同的数据库实例上,可以减轻单个数据库实例的负载,提高整体系统的响应速度和可靠性。
核心思想
写操作:所有的写操作(插入、更新、删除)都发送到主数据库(Master)。
读操作:所有的读操作(查询)都发送到从数据库(Slave)。
主要步骤
主从复制:配置一个主数据库和一个或多个从数据库,从数据库实时同步主数据库的数据更新。
路由层(Routing Layer):在应用程序层或通过中间件(如代理服务器)实现读写请求的路由。写请求路由到主数据库,读请求路由到从数据库。
数据一致性:保证数据在主数据库和从数据库之间的一致性,通常使用同步或异步复制策略。

基于Spring实现实现读写分离

在这里插入图片描述

Spring实现应用层实现读写分离,是基于AbstractRoutingDataSource
来实现。
AbstractRoutingDataSource是基于特定的查找key路由到特定的数据源。它内部维护了一组目标数据源,并且做了路由key与目标数据源之间的映射,提供基于key查找数据源的方法。

代码实现

//实现数据源动态切换的核心代码
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.lang.Nullable;
public class MyRoutingDataSource extends AbstractRoutingDataSource {
    @Nullable
    @Override
    protected Object determineCurrentLookupKey() {
        return DBContextHolder.get();
    }
}

利用ThreadLocal获取存储或获取数据源

package com.zqtest.config;

import java.util.concurrent.atomic.AtomicInteger;

public class DBContextHolder {
    private static final ThreadLocal<DBTypeEnum> contextHolder=new ThreadLocal<DBTypeEnum>();
    private static final AtomicInteger counter=new AtomicInteger(-1);
    public static void set(DBTypeEnum dbTypeEnum){
        contextHolder.set(dbTypeEnum);
    }
    public static DBTypeEnum get(){
        return contextHolder.get();
    }
    public static void master(){
        set(DBTypeEnum.MASTER);
        System.out.println("切换到Master");

    }
    public static void slave(){
        //从节点进行轮训
        int index=counter.getAndIncrement()%2;
        if(counter.get()>9999){
            counter.set(-1);
        }
        if(index==0){
            set(DBTypeEnum.SLAVE1);
            System.out.println("切换到slave1");
        }
        if(index==1){
            set(DBTypeEnum.SLAVE2);
            System.out.println("切换到slave2");
        }

    }
}

数据源注册

package com.zqtest.config;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class DataSourceConfig {
    @Bean
    @ConfigurationProperties("spring.datasource.master")
    public DataSource masterDataSource(){
        return DataSourceBuilder.create().build();
    }
    @Bean
    @Primary
    @ConfigurationProperties("spring.datasource.slave1")
    public DataSource slave1DataSource() {
        return DataSourceBuilder.create().build();
    }
    @Bean
    @ConfigurationProperties("spring.datasource.slave2")
    public DataSource slave2DataSource() {
        return DataSourceBuilder.create().build();
    }
    @Bean
    public DataSource myRoutingDataSource( DataSource masterDataSource,
                                           DataSource slave1DataSource,
                                           DataSource slave2DataSource
                                          ) {
        Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
        targetDataSources.put(DBTypeEnum.MASTER, masterDataSource);
        targetDataSources.put(DBTypeEnum.SLAVE1, slave1DataSource);
        targetDataSources.put(DBTypeEnum.SLAVE2, slave2DataSource);
        MyRoutingDataSource myRoutingDataSource = new MyRoutingDataSource();
        myRoutingDataSource.setDefaultTargetDataSource(slave1DataSource);
        myRoutingDataSource.setTargetDataSources(targetDataSources);
        return myRoutingDataSource;
    }

}

spring:
  application:
    name: test
  datasource:
    master:
      url: jdbc:mysql://ip1:3306/database
      username: username
      password: password
      driver-class-name: com.mysql.jdbc.Driver
    slave1:
      url: jdbc:mysql://ip2:3306/database
      username: username   # 只读账户
      password: password
      driver-class-name: com.mysql.jdbc.Driver
    slave2:
      url: jdbc:ip3:3306/database
      username: username   # 只读账户
      password: password
      driver-class-name: com.mysql.jdbc.Driver
server:
  port: 8080

java枚举类


public enum DBTypeEnum {
    MASTER,SLAVE1,SLAVE2,
}

注意这里一定要加事务管理,防止代码出现多数据源问题。

import javax.annotation.Resource;
import javax.sql.DataSource;
@Configuration
@EnableTransactionManagement
public class MyBatiesConfig {
    @Resource(name="myRoutingDataSource")
    private DataSource myRoutingDataSource;
    @Bean
    public SqlSessionFactory sqlSessionFactory ()throws Exception{
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(myRoutingDataSource);
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
        return sqlSessionFactoryBean.getObject();

    }
    @Bean
    public PlatformTransactionManager platformTransactionManager(){
        return new DataSourceTransactionManager(myRoutingDataSource);
    }
}

AOP的实现
注解实现

public @interface Master {
}

核心点

package com.zqtest.aop;

import com.zqtest.config.DBContextHolder;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class DataSourceAop {
    @Pointcut("!@annotation(com.zqtest.annotation.Master) " +
            "&& (execution(* com.zqtest.service..*.select*(..)) " +
            "|| execution(* com.zqtest.service..*.get*(..)))")
    public void readPointcut() {

    }

    @Pointcut("@annotation(com.zqtest.annotation.Master) " +
            "|| execution(* com.zqtest.service..*.insert*(..)) " +
            "|| execution(* com.zqtest.service..*.add*(..)) " +
            "|| execution(* com.zqtest.service..*.update*(..)) " +
            "|| execution(* com.zqtest.service..*.edit*(..)) " +
            "|| execution(* com.zqtest.service..*.delete*(..)) " +
            "|| execution(* com.zqtest.service..*.remove*(..))")
    public void writePointcut() {

    }

    @Before("readPointcut()")
    public void read(){
        DBContextHolder.slave();
    }
    @Before("writePointcut()")
    public void write(){
        DBContextHolder.master();

    }
}

项目中常用的数据源切换依赖包

<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
  <version>${version}</version>
</dependency>

dynamic-datasource-spring-boot-starter 是一个基于springboot的快速集成多数据源的启动器。
其支持 Jdk 1.7+, SpringBoot 1.4.x 1.5.x 2.x.x。
特性
1.支持 数据源分组 ,适用于多种场景 纯粹多库 读写分离 一主多从 混合模式。
2.支持数据库敏感配置信息 加密 ENC()。
3.支持每个数据库独立初始化表结构schema和数据库database。
4.支持无数据源启动,支持懒加载数据源(需要的时候再创建连接)。
支持 自定义注解 ,需继承DS(3.2.0+)。
5.提供并简化对Druid,HikariCp,BeeCp,Dbcp2的快速集成。
6.提供对Mybatis-Plus,Quartz,ShardingJdbc,P6sy,Jndi等组件的集成方案。
7.提供 自定义数据源来源 方案(如全从数据库加载)。
8.提供项目启动后 动态增加移除数据源 方案。
9.提供Mybatis环境下的 纯读写分离 方案。
10.提供使用 spel动态参数 解析数据源方案。内置spel,session,header,支持自定义。
支持 多层数据源嵌套切换 。(ServiceA >>> ServiceB >>> ServiceC)。
11.提供 基于seata的分布式事务方案。
12.提供 本地多数据源事务方案。 附:不能和原生spring事务混用。
约定
1.本框架只做 切换数据源 这件核心的事情,并不限制你的具体操作,切换了数据源可以做任何CRUD。
2.配置文件所有以下划线 _ 分割的数据源 首部 即为组的名称,相同组名称的数据源会放在一个组下。
3.切换数据源可以是组名,也可以是具体数据源名称。组名则切换时采用负载均衡算法切换。
4.默认的数据源名称为 master ,你可以通过 spring.datasource.dynamic.primary 修改。
5.方法上的注解优先于类上注解。DS支持继承抽象类上的DS,暂不支持继承接口上的DS。
快速配置数据源:
1.引入dynamic-datasource-spring-boot-starter。

<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
  <version>${version}</version>
</dependency>

2.配置数据源

spring:
  datasource:
    dynamic:
      primary: master #设置默认的数据源或者数据源组,默认值即为master
      strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
      datasource:
        master:
          url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic
          username: root
          password: 123456
          driver-class-name: com.mysql.jdbc.Driver # 3.2.0开始支持SPI可省略此配置
        slave_1:
          url: jdbc:mysql://xx.xx.xx.xx:3307/dynamic
          username: root
          password: 123456
          driver-class-name: com.mysql.jdbc.Driver
        slave_2:
          url: ENC(xxxxx) # 内置加密,使用请查看详细文档
          username: ENC(xxxxx)
          password: ENC(xxxxx)
          driver-class-name: com.mysql.jdbc.Driver
       #......省略
       #以上会配置一个默认库master,一个组slave下有两个子库slave_1,slave_2

3.使用 @DS 切换数据源。
没有@DS 默认数据源
@DS(“dsName”) dsName可以为组名也可以为具体某个库的名称

@Service
@DS("slave")
public class UserServiceImpl implements UserService {

  @Autowired
  private JdbcTemplate jdbcTemplate;

  public List selectAll() {
    return  jdbcTemplate.queryForList("select * from user");
  }
  
  @Override
  @DS("slave_1")
  public List selectByCondition() {
    return  jdbcTemplate.queryForList("select * from user where age >10");
  }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/695602.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ChatTTS改良版 - 新增精选高品质音色,新增超长文本推理,新增api接口

这个版本是ChatTTS的一个分支&#xff0c;基于ChatTTS修改&#xff0c;由6drf21e大佬改良&#xff0c;大佬GitHub地址 GitHub - 6drf21e/ChatTTS_colab: &#x1f680; 一键部署&#xff08;含离线整合包&#xff09;&#xff01;基于 ChatTTS &#xff0c;支持音色抽卡、长音频…

SpringBootWeb 篇-深入了解 Redis 五种类型命令与如何在 Java 中操作 Redis

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 Redis 概述 1.1 Redis 下载与安装 2.0 Redis 数据类型 3.0 Redis 常见五种类型的命令 3.1 字符串操作命令 3.2 哈希操作命令 3.3 列表操作命令 3.4 集合操作命令 …

【面试干货】 B 树与 B+ 树的区别

【面试干货】 B 树与 B 树的区别 1、B 树2、 B 树3、 区别与优缺点比较4、 总结 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在数据库系统中&#xff0c;B 树和 B 树是常见的索引结构&#xff0c;它们在存储和组织数据方面有着不同的设计…

树莓派4B 零起点(三) 树莓派 VNC 远程桌面配置(1)

目录 一、启用树莓派4B的 VNC 服务 二、在PC的操作系统上安装VNC客户端 1、下载安装 RealVNC 2、下载安装 TigerVNC 三、登录VNC远程桌面 1、通过使用 ifconfig 查看树莓派的 IP 地址 2、启动 TigerVNC 客户端&#xff08;Windows版本演示&#xff09; 在前两章的基础下…

【C++修行之道】类和对象(六)再谈构造函数(初始化列表)| explicit关键字 | static成员 | 友元|匿名对象|拷贝时一些编译器优化

目录 一、再谈构造函数 1.1 构造函数体赋值 1.2 初始化列表 1. 所有的成员,既可以在初始化列表初始化,也可以在函数体内初始化 2. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次) 3. 类中包含以下成员&#xff0c;必须放在初始化列表位置进行初始化&…

SpringBoot个人网盘系统-计算机毕业设计源码92922

摘 要 随着科学技术的飞速发展&#xff0c;社会的方方面面、各行各业都在努力与现代的先进技术接轨&#xff0c;通过科技手段来提高自身的优势或改善自身的缺点&#xff0c;互联网的发展文件管理带来了福音。个人网盘系统是以实际运用为开发背景&#xff0c;运用软件工程原理和…

分析示例 | Simufact焊接工艺仿真变形精确预测汽车结构

导语 焊接是汽车制造过程中一个关键环节&#xff0c;白车身、发动机、底盘和变速箱等都离不开焊接工艺的应用&#xff0c;主要涉及气保焊、电阻点焊、激光焊、电子束焊等多种焊接工艺。由于汽车车型众多、成形结构复杂、汽车制造质量、效率、成本等方面的综合要求。如何高效、…

WDF驱动开发-PNP和电源管理(三)

对于PNP设备来说&#xff0c;理解它们的启动和删除顺序&#xff0c;以及意外移除顺序非常重要&#xff0c;在早期&#xff0c;经常有拔插U盘导致windows重启的例子&#xff0c;这就是意外移除带来的问题。 功能或Filter驱动程序的启动顺序 下图显示了框架调用 WDF (KMDF 和 U…

中国蚁剑 安装教程 2024年5月

2024/5/11 中国蚁剑 安装教程 一、下载中国蚁剑的加载器和核心源码&#xff08;两个都要用到&#xff09; github官方下载地址&#xff1a;https://github.com/AntSwordProject/ 参考文档&#xff1a;antSword/README_CN.md at master AntSwordProject/antSword GitHub 核…

用咖啡来理解springboot3的自动配置机制

大家好&#xff0c;这里是教授.F 目录 前提知识&#xff1a; 场景引入&#xff1a; 1.Starter依赖&#xff1a; 2.默认配置&#xff1a; 3.自定义配置&#xff1a; 4.条件化配置&#xff1a; 5.自动装配&#xff1a; 具体过程&#xff1a; 扫包路径的配置&#xff1a; 配置…

招聘在家抄书员?小心是骗局!!!

在家抄书员的骗局是一种常见的网络诈骗手段&#xff0c;旨在利用人们想要在家轻松赚钱的心理。这种骗局通常会以招聘兼职抄写员的形式出现&#xff0c;声称只需在家中抄写书籍即可赚取可观的收入。然而&#xff0c;实际上这背后隐藏着诸多陷阱和虚假承诺。 首先&#xff0c;这些…

生成式人工智能 - 本地windows 11 + PyCharm运行stable diffusion流程简述

一、环境说明 硬件:本地电脑windows11、32.0 GB内存、2060的6G的卡。 软件:本地有一个python环境,主要是torch 2.2.2+cu118 二、准备工作 1、下载模型 https://huggingface.co/CompVishttps://huggingface.co/CompVis 进入上面的网址,我这里下载的是这个里面的 …

二分#背包#快排#LCS详解

二分#背包#快排#LCS详解 文章目录 二分#背包#快排#LCS详解1. 二分搜索2. 01背包问题3. 快速排序4. 最长公共子序列 1. 二分搜索 在处理大规模数据集时&#xff0c;查找操作的效率显得尤为重要。二分搜索是一种在有序数组中查找目标值的高效算法&#xff0c;其时间复杂度为O(lo…

超详细 | 使用Nexus搭建私服 (带代码演示)

为什么需要搭建私有仓库&#xff1f; 在企业开发的过程中&#xff0c;不是所有公司都能直接访问外网。在这种情况下&#xff0c;就需要在局域网内找一台有外网访问权限的服务器&#xff0c;搭建Nexus私服仓库&#xff0c;开发人员连接到这台私服上&#xff0c;通过搭建的Nexus…

Golang | Leetcode Golang题解之第142题环形链表II

题目&#xff1a; 题解&#xff1a; func detectCycle(head *ListNode) *ListNode {slow, fast : head, headfor fast ! nil {slow slow.Nextif fast.Next nil {return nil}fast fast.Next.Nextif fast slow {p : headfor p ! slow {p p.Nextslow slow.Next}return p}}r…

调研管理系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;管理员管理&#xff0c;基础数据管理&#xff0c;教师类型管理&#xff0c;课程类型管理&#xff0c;公告类型管理 前台账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;论坛&#…

OPPO高级项目经理曹帆受邀为第十三届中国PMO大会演讲嘉宾

全国PMO专业人士年度盛会 OPPO互联网服务系统内容生态中心高级互联网项目经理曹帆先生受邀为PMO评论主办的2024第十三届中国PMO大会演讲嘉宾&#xff0c;演讲议题为“加、减、乘、除——激活项目团队效能”。大会将于6月29-30日在北京举办&#xff0c;敬请关注&#xff01; 议…

【数学】各种图面积公式的推导

Hello&#xff01;大家好&#xff0c;我是学霸小羊&#xff0c;今天讲讲面积公式。 1.长方形 长方形是 由无数条 长度为长方形的长&#xff08;或宽&#xff09;的线 组成的图形&#xff0c;这些线有多少根&#xff0c;我们不知道&#xff0c;只需要知道他们垒成了一个由高 宽…

如何获取MySQL中表的大小?(官方校正版)

与大多数关系数据库一样&#xff0c;MySQL 提供了有关数据库本身的有用元数据。虽然大多数其他数据库将此信息称为 catalog&#xff0c; 但MySQL 官方文档INFORMATION_SCHEMA 将元数据 称为 tables。 目录 1 列出单个数据库中的单表大小 2 列出所有数据库中的所有表大小 以下…

【第14章】SpringBoot实战篇之多环境配置

文章目录 前言一、通用配置文件1. 定义2. 使用2.1 application.yml2.2 启动类 3. 测试 二、多环境配置文件1.定义1.1 application-local.yml1.2 application-dev.yml1.3 application-test.yml1.4 application-prod.yml 2.使用2.1 application.yml2.2 启动类 3.测试 三、多环境配…