【服务实现读写分离】

news2024/9/19 10:37:08

文章目录

  • 什么是读写分离
  • 基于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");
  }
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

借助ChatGPT快速仿写一篇优质论文,无痛仿写、完美创作

大家好&#xff0c;感谢关注。我是七哥&#xff0c;一个在高校里不务正业&#xff0c;折腾学术科研AI实操的学术人。可以添加我&#xff08;yida985&#xff09;交流学术写作或ChatGPT等AI领域相关问题&#xff0c;多多交流&#xff0c;相互成就&#xff0c;共同进步 在学术写…

探索智慧景区票务系统的架构与应用

随着旅游业的迅速发展&#xff0c;智慧景区票务系统已经成为提升景区管理效率、优化游客体验的重要工具。智慧景区票务系统的架构设计与应用&#xff0c;将现代信息技术与景区管理相结合&#xff0c;为景区的门票销售、入园管理和游客服务提供了全新的解决方案。本文将深入探讨…

形参和实参的区别

形参&#xff1a;函数定义时声明的参数。 实参&#xff1a;调用函数时传递的参数。

数字孪生智慧水利:精准管理与智能决策的新时代

图扑数字孪生技术在智慧水利中的应用&#xff0c;通过虚拟模型与真实水利系统的无缝连接&#xff0c;实现对水资源和水利工程的全面监控和精细管理。实时数据采集与动态模拟提升了水利系统的预测和响应能力&#xff0c;从洪水预警到水质监测&#xff0c;数字孪生助力各项决策更…

一款开源文件加速下载利器

前言 大文件的下载&#xff0c;浏览器支持不是很好&#xff0c;今天下载了一个20个G的文件&#xff0c;连续失败了好多次。 然后寻找到了一个开源的下载工具gospeed&#xff0c;可以完美的解决这个问题。而且下载速度快。 简介 Gopeed&#xff08;全称 Go Speed&#xff09;&am…

k8s面试题大全,保姆级的攻略哦(三)

目录 1、简述ETCD及其特点? 2、简述ETCD适应的场景? 3、简述什么是Kubernetes? 4、简述Kubernetes和Docker的关系? 5、简述Kubernetes中什么是Minikube、Kubectl、Kubelet? 6、简述Kubernetes常见的部署方式? 7、简述Kubernetes如何实现集群管理? 8、简述Kubern…

2 - 寻找用户推荐人(高频 SQL 50 题基础版)

2.寻找用户推荐人 考点: sql里面的不等于&#xff0c;不包含null -- null 用数字判断筛选不出来 select name from Customer where referee_id !2 OR referee_id IS NULL;

RK3288 android7.1 实现ota升级时清除用户数据

一&#xff0c;OTA简介(整包&#xff0c;差分包) OTA全称为Over-The-Air technology(空中下载技术)&#xff0c;通过移动通信的接口实现对软件进行远程管理。 1. 用途&#xff1a; OTA两种类型最大的区别莫过于他们的”出发点“&#xff08;我们对两种不同升级包的创建&…

这4个科研思维陷阱,可能正在阻碍你发表论文!

我是娜姐 迪娜学姐 &#xff0c;一个SCI医学期刊编辑&#xff0c;探索用AI工具提效论文写作和发表。 昨天&#xff0c;有位同学忧心忡忡的过来问我&#xff1a;一区文章已经接收了&#xff0c;因为两张图里有错误&#xff0c;想要撤稿重投。 我的建议如下&#xff1a; 1 重新投…

【深度学习】PuLID: Pure and Lightning ID Customization via Contrastive Alignment

论文&#xff1a;https://arxiv.org/abs/2404.16022 代码&#xff1a;https://github.com/ToTheBeginning/PuLID 文章目录 AbstractIntroductionRelated WorkMethods Abstract 我们提出了一种新颖的、无需调整的文本生成图像ID定制方法——Pure and Lightning ID customizatio…

三极管十大品牌

三极管十大品牌-三极管品牌-晶体三极管哪个品牌好-Maigoo品牌榜

Java学习 - MyBatis - 入门实例详解

前言 在上一篇文章中&#xff0c;我们讨论了持久化的概念&#xff0c;并简要介绍了 MyBatis。今天我们将深入到 MyBatis 的实际应用中&#xff0c;通过创建一个入门实例来展示如何使用 MyBatis 执行基本的 CRUD&#xff08;创建、读取、更新、删除&#xff09;操作。这个过程将…

软件项目安全保证措施(Word原件)

软件安全保证措施 一、身份鉴别 二、访问控制 三、通信完整性、保密性 四 、数据完整性 六、数据保密性 七、应用安全支撑系统设计获取本原件及更多资料&#xff1a;本文末个人名片。

OpenGauss数据库-3.数据库管理

第1关&#xff1a;创建数据库 gsql -d postgres -U gaussdb -w passwd123123 create database accessdb with ownergaussdb templatetemplate0;第2关&#xff1a;修改数据库 gsql -d postgres -U gaussdb -w passwd123123 alter database accessdb rename to human_tpcds; 第…

Golang | Leetcode Golang题解之第141题环形链表

题目&#xff1a; 题解&#xff1a; func hasCycle(head *ListNode) bool {if head nil || head.Next nil {return false}slow, fast : head, head.Nextfor fast ! slow {if fast nil || fast.Next nil {return false}slow slow.Nextfast fast.Next.Next}return true }

SpringSecurity入门(三)

12、密码加密 12.1、不指定具体加密方式&#xff0c;通过DelegatingPasswordEncoder&#xff0c;根据前缀自动选择 PasswordEncoder passwordEncoder PasswordEncoderFactories.createDelegatingPasswordEncoder();12.2、指定具体加密方式 // Create an encoder with streng…

【数据结构】前缀树(字典树)汇总

基础 {“a”,“abc”,“bac”,“bbc”,“ca” }的字典树如下图&#xff1a; 最主用的应用&#xff1a;一&#xff0c;字符串编码。二&#xff0c;位运算。 字符串编码 相比利用哈希映射编码&#xff0c;优点如下&#xff1a; 依次查询长度为n的字符串s的前缀时间复杂度是O(…

Qt图标字体文件中提取字体保存为图片

本文借用别人写的一个IconHelper来做说明。 1. 加载一个字体文件 QScopedPointer<IconHelper> iconHelper(new IconHelper(":/fa-regular-400.ttf", "Font Awesome 6 Pro Regular"));构造函数 IconHelper::IconHelper(const QString &fontFile…

C#操作MySQL从入门到精通(14)——汇总数据

前言 我们有时候需要对数据库查询的值进行一些处理,比如求平均值等操作,本文就是详细讲解这些用法,本文测试使用的数据库数据如下: 1、求平均值 求所有student_age 列的平均值 string sql = string.Empty; if (radioButton_AVG.Checked) {sql = “select AVG( student_…