首页 > 其他分享 >读写分离原来如此简单

读写分离原来如此简单

时间:2023-06-14 17:37:58浏览次数:39  
标签:读写 分离 springframework private user org import com 原来如此


读写分离原来如此简单_spring

一、为什么要用读写分离,有哪些使用场景?

  1. 高并发读取场景:当应用程序有大量读取操作,但相对较少的写入操作时,使用读写分离可以将读取操作分散到多个从库,提高系统的读取性能和并发能力。
  2. 数据报表和分析场景:在需要生成复杂报表或进行数据分析的场景中,读写分离可以将读取操作与写入操作分开,确保报表和分析任务不会影响主库的写入性能。
  3. 全球分布式架构:对于全球性的应用程序,使用读写分离可以将读取操作路由到离用户更近的从库,减少网络延迟,提高用户体验。
  4. 容灾和高可用性:通过将数据复制到多个从库,读写分离可以提供容灾和高可用性。当主库发生故障时,可以快速切换到可用的从库,确保业务的持续运行。
  5. 降低数据库负载:通过将读取操作分散到从库,读写分离可以降低主库的负载压力,提高主库的写入性能和吞吐量。 需要注意的是,读写分离并非适用于所有场景。对于对数据一致性要求非常高的应用程序,可能需要额外的同步机制来保证主库和从库之间的数据一致性。此外,读写分离还需要考虑数据库同步延迟和数据更新策略等因素。

二、读写分离有什么好处?

  1. 提高性能和可扩展性:通过将读操作分配给只负责读取的从库,可以将读操作的负载均衡分散到多个数据库实例上,从而提高读取性能和系统的可扩展性。
  2. 降低主库压力:将读操作从主库转移到从库,可以减轻主库的负载压力,让主库专注于处理写操作。这可以提高主库的写入性能和吞吐量。
  3. 提高数据可用性和容灾能力:通过使用多个从库进行数据复制,可以提高系统的容灾能力。当主库发生故障时,可以快速切换到从库,确保业务的持续运行。
  4. 灵活的数据分发:读写分离允许根据应用程序需求将不同类型的读操作分发到特定的从库。例如,可以将只读查询路由到离用户更近的从库,以减少网络延迟并提供更好的用户体验。
  5. 降低数据库锁竞争:读写分离可以减少主库上的并发写操作,从而减少数据库锁竞争的可能性,提高整体系统的并发能力。

总体而言,MySQL读写分离可以通过优化读操作的负载分布、提高性能和可用性、降低主库压力和提供灵活的数据分发,从而提升应用程序的性能和可伸缩性,并改善用户体验。 总的来说,就是降压提性能

三、读写分离分析

技术选型

  • SpringBoot
  • MybatisPlus
  • MySQL

基本原理逻辑

  1. 配置主从数据库
  2. 创建数据源配置类
  3. 创建动态数据源
  4. 配置MyBatis-Plus
  5. 事务管理配置

我自己的理解就是,先在服务器上做主从复制,保证数据的一致性。再通过SpringBoot与MyBatisPlus配置DataSource(master与slave两种),创建一个动态数据源类,例如DynamicDataSource,继承自AbstractRoutingDataSource。创建一个SqlSessionFactory bean,并设置数据源为动态数据源。创建一个DataSourceTransactionManager bean,并设置数据源为动态数据源。通过AOP+自定义注解,使得读写可以在业务中进行配置。

四、具体代码实现

我的mysql是在云服务器上,通过docker部署的,所以下面的将用此方法,但是整体逻辑大同小异

First. MySQL主从复制

前提: 已经使用Docker搭建了两个数据库了,一主一从。版本相同,端口不同,都启动了二进制日志(Binary Log),同时主库和从库之间需要具备可靠的网络连接。

① 修改my.cnf

主库与从库my.cnf中的server-id 需要不同,保证MySQL实例的唯一性。该配置在[mysqld]模块中。如下图所示:

读写分离原来如此简单_spring_02

② 配置主库

首先需要查看当前数据库正在写入的二进制日志文件中的名称与位置。

show master status;

读写分离原来如此简单_mysql_03

进行配置:

CHANGE MASTER TO
  MASTER_HOST='120.46.129.14',
  MASTER_PORT=3389,
  MASTER_USER='root',
  MASTER_PASSWORD='root_886YJ',
  MASTER_LOG_FILE='mysql_bin.000003',
  MASTER_LOG_POS=154;

读写分离原来如此简单_spring_04

查看是否状态

SHOW SLAVE STATUS\G

读写分离原来如此简单_mysql_05

出现了两个Yes即为成功

Second. 前提准备

  1. 创建数据表,作为测试
DROP TABLE IF EXISTS `user`; 

CREATE TABLE `user` ( 
  `user_id` bigint(20) NOT NULL COMMENT '用户id', 
  `user_name` varchar(255) DEFAULT '' COMMENT '用户名称', 
  `user_phone` varchar(50) DEFAULT '' COMMENT '用户手机', 
  `address` varchar(255) DEFAULT '' COMMENT '住址', 
  `weight` int(3) NOT NULL DEFAULT '1' COMMENT '权重,大者优先', 
  `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 
  `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', 
  PRIMARY KEY (`user_id`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 
 
INSERT INTO `user` VALUES ('1196978513958141952', '测试1', '18826334748', '广州市海珠区', '1', '2019-11-20 10:28:51', '2019-11-22 14:28:26'); 
INSERT INTO `user` VALUES ('1196978513958141953', '测试2', '18826274230', '广州市天河区', '2', '2019-11-20 10:29:37', '2019-11-22 14:28:14'); 
INSERT INTO `user` VALUES ('1196978513958141954', '测试3', '18826273900', '广州市天河区', '1', '2019-11-20 10:30:19', '2019-11-22 14:28:30');
  1. 创建SpringBoot项目,创建对应实体类,配置pom文件,填写yml文件

User实体类

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
 * @author LukeZhang
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    @TableId(type = IdType.AUTO)
    private Long userId;
    private String userName;
    private String userPhone;
    private String address;
    private Integer weight;
    private Date createdAt;
    private Date updatedAt;
}

pom配置-依赖部分

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!-- Druid连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.22</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.4</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
    </dependencies>

yml配置

server: 
  port: 8002
spring: 
  jackson: 
   date-format: yyyy-MM-dd HH:mm:ss 
   time-zone: GMT+8 
  datasource: 
    type: com.alibaba.druid.pool.DruidDataSource
    master:
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://<yourip>:<yourport>/user?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&failOverReadOnly=false&useSSL=false&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
      username: <yourusername>
      password: <yourpassword>
    slave:
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://<yourip>:<yourport>/user?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&failOverReadOnly=false&useSSL=false&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
      username: <yourusername>
      password: <yourpassword>

mapper创建

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.rwdemo.domain.User;
/**
 * @author LukeZhang
 * @date 2023/6/14 16:35 
 */
public interface UserMapper extends BaseMapper<User> {
}

Third. 实现动态数据源

  1. 枚举类方便区分主从
import lombok.Getter;
/**
 * @author LukeZhang
 */
@Getter
public enum DynamicDataSourceEnum {
    MASTER("master"),
    SLAVE("slave");
    private String dataSourceName;
    DynamicDataSourceEnum(String dataSourceName) {
        this.dataSourceName = dataSourceName;
    }
}
  1. 创建对应实体类接收参数 MasterDataBase
/**
 * @author LukeZhang
 */
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "spring.datasource.master")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Master {
    private String url;
    private String driverClassName;
    private String username;
    private String password;
}

SlaveDataBase

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
 * @author LukeZhang
 */
@Configuration
@ConfigurationProperties(prefix = "spring.datasource.slave")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Slave {
    private String url;
    private String driverClassName;
    private String username;
    private String password;
}
  1. 配置动态数据源
/**
 * @author LukeZhang
 * @date 2023/6/14 16:37 
 */
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.example.rwdemo.domain.DynamicDataSourceEnum;
import com.example.rwdemo.holder.DynamicDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
@MapperScan("com.example.rwdemo.mapper")
public class DataSourceConfig {
    @Autowired
    private Master master;
    @Autowired
    private Slave slave;
    @Bean(name = "masterDataSource")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().url(master.getUrl()).driverClassName(master.getDriverClassName()).password(master.getPassword()).username(master.getUsername()).build();
    }
    @Bean(name = "slaveDataSource")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create()
                .url(slave.getUrl()).driverClassName(slave.getDriverClassName()).username(slave.getUsername()).password(slave.getPassword())
                .build();
    }
    /**
     * 主从动态配置
     */
    @Bean
    public DynamicDataSource dynamicDb(@Qualifier("masterDataSource") DataSource masterDataSource,
                                       @Autowired(required = false) @Qualifier("slaveDataSource") DataSource slaveDataSource) {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DynamicDataSourceEnum.MASTER.getDataSourceName(), masterDataSource);
        if (slaveDataSource != null) {
            targetDataSources.put(DynamicDataSourceEnum.SLAVE.getDataSourceName(), slaveDataSource);
        }
        dynamicDataSource.setTargetDataSources(targetDataSources);
        dynamicDataSource.setDefaultTargetDataSource(masterDataSource);
        return dynamicDataSource;
    }
    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDb") DataSource dynamicDataSource) throws Exception {
        MybatisSqlSessionFactoryBean sessionFactoryBean = new MybatisSqlSessionFactoryBean();
        sessionFactoryBean.setDataSource(dynamicDataSource);
        sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*Mapper.xml"));
        return sessionFactoryBean.getObject();
    }
    @Bean(name = "transactionManager")
    public DataSourceTransactionManager transactionManager(@Qualifier("dynamicDb") DataSource dynamicDataSource) {
        return new DataSourceTransactionManager(dynamicDataSource);
    }
}
  1. 通过aop方便实现

注解获取信息

import com.example.rwdemo.domain.DynamicDataSourceEnum;
import java.lang.annotation.*;
/**
 * @author LukeZhang
 * @date 2023/6/14 16:40 
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface DataSourceSelector {
    DynamicDataSourceEnum value() default DynamicDataSourceEnum.MASTER;
    boolean clear() default true;
}

编写AOP配置

import com.example.rwdemo.annotation.DataSourceSelector;
import com.example.rwdemo.holder.DataSourceContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
 * @author LukeZhang 
 */
@Slf4j 
@Aspect 
@Order(value = 1) 
@Component 
public class DataSourceContextAop {
         @Around("@annotation(com.example.rwdemo.annotation.DataSourceSelector)")
            public Object setDynamicDataSource(ProceedingJoinPoint pjp) throws Throwable {
                boolean clear = true; 
                try { 
                    Method method = this.getMethod(pjp);
                    DataSourceSelector dataSourceImport = method.getAnnotation(DataSourceSelector.class);
                    clear = dataSourceImport.clear(); 
                    DataSourceContextHolder.set(dataSourceImport.value().getDataSourceName());
                    log.info("========数据源切换至:{}", dataSourceImport.value().getDataSourceName()); 
                    return pjp.proceed(); 
                } finally { 
                    if (clear) { 
                        DataSourceContextHolder.clear(); 
                    } 
         
                } 
            } 
            private Method getMethod(JoinPoint pjp) {
                MethodSignature signature = (MethodSignature)pjp.getSignature();
                return signature.getMethod(); 
            } 
         
        }

Fourth. 编写服务与测试

  1. 编写UserService
import com.example.rwdemo.annotation.DataSourceSelector;
import com.example.rwdemo.domain.DynamicDataSourceEnum;
import com.example.rwdemo.domain.User;
import com.example.rwdemo.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
 * @author LukeZhang
 * @date 2023/6/14 16:45 
 */
@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;
    @DataSourceSelector(value = DynamicDataSourceEnum.MASTER)
    public int update(Long userId) {
        User user = new User();
        user.setUserId(userId);
        user.setUserName("老薛");
        return userMapper.updateById(user);
    }
    @DataSourceSelector(value = DynamicDataSourceEnum.SLAVE)
    public User find(Long userId) {
        return userMapper.selectById(userId);
    }
}
  1. 编写测试
import com.example.rwdemo.domain.User;
import com.example.rwdemo.mapper.UserMapper;
import com.example.rwdemo.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
/**
 * @author LukeZhang
 * @date 2023/6/14 16:45 
 */
@SpringBootTest
class UserServiceTest {
    @Autowired
    UserService userService;
    @Autowired
    UserMapper userMapper;
    @Test
    void find() { 
        User user = userService.find(1196978513958141952L);
        System.out.println("id:" + user.getUserId()); 
        System.out.println("name:" + user.getUserName()); 
        System.out.println("phone:" + user.getUserPhone()); 
    }
    @Test 
    void update() { 
        Long userId = 1196978513958141952L;
        int update = userService.update(userId);
        System.out.println("update执行"+update);
    }
    @Test
    public void test() {
        List<User> users = userMapper.selectList(null);
        users.forEach(
                user -> {
                    System.out.println(user.toString());
                }
        );
    }
}

读写分离原来如此简单_mysql_06

参考

读写分离原来这么简单,一个小注解就够了友情支持————》chatgpt

总结

第一篇博客!接下来会把自己学到的东西慢慢的记录下来,也算是自己的一个成长记录。同时也方便自己查阅以及和大家一起学习探讨。

标签:读写,分离,springframework,private,user,org,import,com,原来如此
From: https://blog.51cto.com/u_15845806/6478700

相关文章

  • 主从架构如何保证读写一致性(主从网络延迟)
    问题在高并发的场景下,一般是读写分离,写主库,读从库。但是主从同步存在延迟,原因可能有a.主库的从库太多b.从库硬件配置比主库差c.慢SQL语句过多d.主从库之间的网络延迟e.主库读写压力大如果数据写入主库之后还未来得及同步到从库,此时读从库就会读到脏数据解决方案1......
  • 用java做操作系统内核:软盘读写
    在前两节,我们将一段代码通过软盘加载到了系统内存中,并指示cpu执行加入到内存的代码,事实上,操作系统内核加载也是这么做的。只不过我们加载的代码,最大只能512byte,一个操作系统内核,少说也要几百兆,由此,系统内核不可能直接从软盘读入系统内存。通常的做法是,被加载进内存的512Byte程......
  • 咖啡机缺水提醒方案-分离式液位传感器
    随着人们生活水平的提高,咖啡已成为许多人不可或缺的一部分。而咖啡机则成为了很多人在家中制作咖啡的首选工具。然而,由于大多数咖啡机都需要使用水来制作咖啡,因此需要定期加水。如果您忘记加水,咖啡机可能会受到损坏或咖啡就无法制作。因此,一个咖啡机缺水提醒方案成为了必要的。其中......
  • 深度可分离卷积
    深度可分离卷积(DepthwiseSeparableConvolution)是一种在卷积神经网络中常用的卷积操作,它可以有效地减少计算量和模型参数的数量,从而提高模型的效率和速度。传统的卷积操作是在输入特征图的每个通道上进行的,使用一组卷积核对每个通道进行卷积计算。而深度可分离卷积将卷积操作分......
  • Spring Boot&Vue3前后端分离实战wiki知识库系统<八>--分类管理功能开发二
    接着上一次SpringBoot&Vue3前后端分离实战wiki知识库系统<七>--分类管理功能开发的分类功能继续完善。分类编辑功能优化:概述:现在分类编辑时的界面长这样:很明显目前的父分类的展现形式不太人性,这里需要指定父分类的id才可以,对于用户来说这种交互是反人道的,用户怎么知道父分类......
  • Python+pandas分离Excel数据到同一个Excel文件中多个Worksheets
    封面图片:《Python程序设计(第2版)》,董付国,清华大学出版社===============问题描述:已知文件“超市营业额2.xlsx”中结构与部分数据如图所示:现在要求把每个员工的交易数据写入文件“各员工数据.xlsx”,每个员工的数据占一个worksheet,结构和“超市营业额2.xlsx”一样,并以员工姓名作为work......
  • 野火STM32 读写内部FLASH
    解锁、上锁函数1voidFLASH_Unlock(void);2voidFLASH_Lock(void);擦除函数:1FLASH_StatusFLASH_ErasePage(uint32_tPage_Address);2FLASH_StatusFLASH_EraseAllPages(void);3FLASH_StatusFLASH_EraseOptionBytes(void);写入函数:1FLASH_StatusFLASH_Progra......
  • 使用Python读写文本文件内容
    本文主要演示如何读写文本文件的内容,以及上下文管理语句with的用法。使用上下文管理语句with时,即使在操作文件内容时引发异常也能保证文件被正确关闭。#'w'表示写入文件,默认为文本文件#如果文件test1.txt不存在,就创建#如果文件test1.txt已存在,就覆盖withopen('test1.txt','w')......
  • 使用Python扩展库spleeter分离MP3音乐文件中的伴奏和人声
    spleeter是由法国的音乐流媒体公司Deezer开源的项目,可以把音乐文件其分成2、4、5等多个独立的音轨,支持mp3、wav、ogg等常见音频格式。Spleeter基于TensorFlow开发,依赖sniffio、six、oauthlib、rfc3986、requests-oauthlib、numpy、llvmlite、h11、anyio、wheel、tensorbo......
  • mycat读写分离方式下强制指定select从主库查数据的方法
    在程序代码的sql语句前,如mybatis中指定select前加入/mycat:db_type=master/这个注释标识,select语句就会直接在主库查询数据,如下:/mycat:db_type=master/SELECT*FROMtb_table;上面的注释中:/!mycat:db_type=master//#mycat:db_type=master//mycat:db_type=master/使用哪一......