马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
MySQL 读写分离
一、设置主库(Master)
1.修改主库的设置文件
修改主库的 my.cnf 设置文件,生成二进制日志 (binary log) 和服务器唯一ID,这是实现主从复制的须要设置
- [mysqld]
- # skip-grant-tables
- user=root
- port=3306
- basedir=/usr/local/mysql
- datadir=/data/mysql
- socket=/usr/local/mysql/socket/mysql.sock
- # Disabling symbolic-links is recommended to prevent assorted security risks
- # Settings user and group are ignored when systemd is used.
- # If you need to run mysqld under a different user or group,
- # customize your systemd unit file for mariadb according to the
- # instructions in http://fedoraproject.org/wiki/Systemd
- log-error=/usr/local/mysql/logs/mysqld.log
- pid_file=/var/run/mysqld/mysqld.pid
- symbolic-links=0
- # master 配置
- server-id=1 # 主库的服务器ID,必须唯一
- log-bin=mysqls-bin # 开启二进制日志,文件名可选
- binlog-format=ROW # (可选,不指定默认 STATEMENT )使用行级复制(推荐)
- # 需要重启mysql服务,master配置才会生效
复制代码 2.创建用于复制的用户
- 登录到Mysql 的主库中,创建一个专门的用户供从库利用,用于复制
- # 创建用户
- CREATE USER '用户名'@'%' IDENTIFIED BY '用户密码';
- e.g
- CREATE USER 'replica_user'@'%' IDENTIFIED BY 'replica123456';
- # 给用户赋予 '复制' 的权限
- GRANT REPLICATION SLAVE ON *.* TO '用户名'@'%';
- e.g
- GRANT REPLICATION SLAVE ON *.* TO 'replica_user'@'%';
- # 这里必须要执行,否则在从库执行 SHOW SLAVE STATUS\G; 时会报错 2061
- alter user '用户名'@'%' identified with mysql_native_password by 'mysql数据库登录密码';
- e.g
- alter user 'replica_user'@'%' identified with mysql_native_password by 'replica123456';
- # 刷新权限
- FLUSH PRIVILEGES;
复制代码 Tip
从库设置启动 复制时报错
SHOW SLAVE STATUS\G;
报错:Last_IO_Errno: 2061
Last_IO_Error: error connecting to master ‘replica_user@120.77.27.139:3306’ - rery-time: 60 retries: 1 message: Authentication plugin ‘caching_sha2_password’ reported error: Auhentication requires secure connection.
3 获取主库的二进制日志位置
- 锁住主库防止数据变化,获取当前的二进制文件名和位置
- # 锁住主库
- mysql> FLUSH TABLES WITH READ LOCK;
- Query OK, 0 rows affected (0.01 sec)
- # 获取当前二进制文件名和位置
- mysql> SHOW MASTER STATUS;
- +-------------------+----------+--------------+------------------+-------------------+
- | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
- +-------------------+----------+--------------+------------------+-------------------+
- | mysqls-bin.000001 | 157 | | | |
- +-------------------+----------+--------------+------------------+-------------------+
- 1 row in set (0.00 sec)
- # 解锁主库
- mysql> UNLOCK TABLES;
- Query OK, 0 rows affected (0.00 sec)
复制代码 二、设置从库(Slave)
1.修改从库的设置文件
- 在从库的MySQL的设置文件 my.cnf 中进行须要的设置,确保其有唯一的服务器ID并启用中继日志
- [mysqld]
- server-id=2 # 从库的服务器ID,确保唯一
- relay-log=relay-bin # 启用中继日志(用于接收主库的二进制日志)
- replicate-do-db=tbb-iov # 指定从主库中需要复制的数据库
- read-only=1 # 只读
复制代码 2.毗连到主库并启动复制
- CHANGE MASTER TO
- MASTER_HOST='主库的IP地址', # 主库的IP地址
- MASTER_PORT=3306, -- 如果主库使用非默认端口,这里需要指定
- MASTER_USER='replica_user', # 复制用户
- MASTER_PASSWORD='replica_password', # 复制用户的密码
- MASTER_LOG_FILE='mysql-bin.000001', # 主库的二进制日志文件名
- MASTER_LOG_POS=154; # 主库的二进制日志位置
- e.g
- CHANGE MASTER TO
- MASTER_HOST='1x0.xx.xx.13x',
- MASTER_PORT=3306,
- MASTER_USER='replica_user',
- MASTER_PASSWORD='replica123456',
- MASTER_LOG_FILE='mysqls-bin.000001',
- MASTER_LOG_POS=157;
复制代码
- # 启动
- START SLAVE;
- # 停止
- STOP SLAVE;
- # 查看同步状态
- SHOW SLAVE STATUS\G;
复制代码
- Slave_IO_State: Waiting for source to send event
- Master_Host: xx.xx.xx.xx
- Master_User: replica_user
- Master_Port: 3306
- Connect_Retry: 60
- Master_Log_File: mysqls-bin.000001
- Read_Master_Log_Pos: 7657
- Relay_Log_File: relay-bin.000006
- Relay_Log_Pos: 2135
- Relay_Master_Log_File: mysqls-bin.000001
- Slave_IO_Running: Yes
- Slave_SQL_Running: Yes
- Replicate_Do_DB: tbb-iov,demo
复制代码 Slave_IO_Running 和 Slave_SQL_Running 必须要为 Yes 才表现成功启动主从复制
- 连接和状态信息
- Slave_IO_State: 当前 IO 线程的状态。例如,Waiting for source to send event 表示从库正在等待主库发送事件。
- Master_Host: 主库的 IP 地址或主机名。
- Master_User: 用于复制的用户名。
- Master_Port: 主库的端口号。
- Connect_Retry: 从库尝试重新连接到主库的间隔时间(秒)。
- Master_Log_File: 当前正在读取的主库二进制日志文件。
- Read_Master_Log_Pos: 当前读取的主库二进制日志的位置。
- Relay_Log_File: 当前正在使用的中继日志文件。
- Relay_Log_Pos: 当前中继日志的位置。
- Relay_Master_Log_File: 当前中继日志对应的主库二进制日志文件。
- Slave_IO_Running: IO 线程是否正在运行。Yes 表示正在运行,No 表示停止。
- Slave_SQL_Running: SQL 线程是否正在运行。Yes 表示正在运行,No 表示停止。
- Slave_SQL_Running_State: SQL 线程的当前状态。例如,Replica has read all relay log; waiting for more updates 表示从库已经读取了所有中继日志,正在等待更多的更新。
- 复制过滤规则
- Replicate_Do_DB: 需要复制的数据库列表。
- Replicate_Ignore_DB: 不需要复制的数据库列表。
- Replicate_Do_Table: 需要复制的表列表。
- Replicate_Ignore_Table: 不需要复制的表列表。
- Replicate_Wild_Do_Table: 需要复制的表的通配符模式。
- Replicate_Wild_Ignore_Table: 不需要复制的表的通配符模式。
- 错误信息
- Last_Errno: 最近一次错误的错误码。
- Last_Error: 最近一次错误的错误信息。
- Skip_Counter: 跳过的错误事务计数器。
- Exec_Master_Log_Pos: 当前已执行的主库二进制日志的位置。
- Last_IO_Errno: 最近一次 IO 错误的错误码。
- Last_IO_Error: 最近一次 IO 错误的错误信息。
- Last_SQL_Errno: 最近一次 SQL 错误的错误码。
- Last_SQL_Error: 最近一次 SQL 错误的错误信息。
- 其他信息
- Relay_Log_Space: 中继日志占用的空间大小(字节)。
- Until_Condition: 停止复制的条件。
- Until_Log_File: 停止复制的日志文件。
- Until_Log_Pos: 停止复制的日志位置。
- Master_SSL_Allowed: 是否允许 SSL 连接。
- Master_SSL_CA_File: SSL 证书颁发机构文件路径。
- Master_SSL_CA_Path: SSL 证书颁发机构路径。
- Master_SSL_Cert: SSL 证书文件路径。
- Master_SSL_Cipher: SSL 密码套件。
- Master_SSL_Key: SSL 私钥文件路径。
- Seconds_Behind_Master: 从库落后于主库的时间(秒)。
- Master_SSL_Verify_Server_Cert: 是否验证主库的 SSL 证书。
- Master_Server_Id: 主库的服务器 ID。
- Master_UUID: 主库的 UUID。
- Master_Info_File: 存储主库信息的文件。
- SQL_Delay: SQL 线程延迟时间(秒)。
- SQL_Remaining_Delay: 剩余的延迟时间。
- Replicate_Ignore_Server_Ids: 不需要复制的服务器 ID 列表。
- Master_Retry_Count: 从库尝试重新连接到主库的最大次数。
- Master_Bind: 绑定的网络接口。
- Last_IO_Error_Timestamp: 最近一次 IO 错误的时间戳。
- Last_SQL_Error_Timestamp: 最近一次 SQL 错误的时间戳。
- Master_SSL_Crl: SSL 证书吊销列表文件路径。
- Master_SSL_Crlpath: SSL 证书吊销列表路径。
- Retrieved_Gtid_Set: 已检索的 GTID 集合。
- Executed_Gtid_Set: 已执行的 GTID 集合。
- Auto_Position: 是否启用自动定位(基于 GTID)。
- Replicate_Rewrite_DB: 数据库重写规则。
- Channel_Name: 复制通道名称。
- Master_TLS_Version: 主库支持的 TLS 版本。
- Master_public_key_path: 主库的公钥文件路径。
- Get_master_public_key: 是否获取主库的公钥。
- Network_Namespace: 网络命名空间。
复制代码 Tip
数据库开启主从复制之前,主库和从库的数据需要保持一致,
三、Spring Boot + MySQL+ Mybatis 主从复制,读写分离
1、Maven 依赖引入
主要依赖如下
- <properties>
- <java.version>1.8</java.version>
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
- <spring-boot.version>2.6.13</spring-boot.version>
- <mybatis.version>2.2.2</mybatis.version>
- <mysql.version>8.0.30</mysql.version>
- <alibabadruid.version>1.2.16</alibabadruid.version>
- <lombok.version>1.18.26</lombok.version>
- </properties>
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- </dependency>
- <!-- web依赖 -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <!-- aop -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-aop</artifactId>
- </dependency>
- <!-- mybatis -->
- <dependency>
- <groupId>org.mybatis.spring.boot</groupId>
- <artifactId>mybatis-spring-boot-starter</artifactId>
- <version>${mybatis.version}</version>
- </dependency>
- <!-- mysql -->
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- <version>${mysql.version}</version>
- </dependency>
- <!-- druid mysql数据库连接池-->
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>druid-spring-boot-starter</artifactId>
- <version>${alibabadruid.version}</version>
- </dependency>
- <!-- lombok 工具 -->
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <version>${lombok.version}</version>
- </dependency>
- </dependencies>
复制代码 2、mysql主从设置
自定义mysql的主从数据源的毗连参数,以及mybatis的设置
- server:
- port: 8082
- # 自定义mysql配置
- mysql:
- datasource:
- master:
- driver-class-name: com.mysql.cj.jdbc.Driver
- url: jdbc:mysql://ip1:port/demo?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
- username: xxx
- password: xxx
- initial-size: 5
- max-active: 20
- min-idle: 5
- max-wait: 60000
- time-between-eviction-runs-millis: 60000
- min-evictable-idle-time-millis: 300000
- max-evictable-idle-time-millis: 900000
- validation-query: SELECT 1 FROM DUAL
- test-while-idle: true
- test-on-borrow: false
- test-on-return: false
- pool-prepared-statements: true
- max-open-prepared-statements: 50
- max-pool-prepared-statement-per-connection-size: 20
- filters: stat,wall
- use-global-data-source-stat: true
- connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
- slave:
- driver-class-name: com.mysql.cj.jdbc.Driver
- url: jdbc:mysql://ip2:port/demo?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
- username: xxxx # 这里的账号最好是只读权限的mysql用户,从库只负责读,不能写入数据
- password: xxxx
- initial-size: 5
- max-active: 25
- min-idle: 5
- max-wait: 60000
- time-between-eviction-runs-millis: 60000
- min-evictable-idle-time-millis: 300000
- max-evictable-idle-time-millis: 900000
- validation-query: SELECT 1 FROM DUAL
- test-while-idle: true
- test-on-borrow: false
- test-on-return: false
- pool-prepared-statements: true
- max-open-prepared-statements: 50
- max-pool-prepared-statement-per-connection-size: 20
- filters: stat,wall
- use-global-data-source-stat: true
- connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
- # 指定mapper*.xml加载位置
- mybatis:
- config-location: classpath:mybatis/mybatis-config.xml
- mapper-locations: classpath:mybatis/mapper/*.xml
复制代码 3、数据源设置
1. DataSourceConfig 数据源设置类
- import com.alibaba.druid.pool.DruidDataSource;
- import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
- import com.datasource.demo.enums.DataSourceType;
- import org.springframework.beans.factory.annotation.Qualifier;
- import org.springframework.boot.context.properties.ConfigurationProperties;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.context.annotation.Primary;
- import java.util.HashMap;
- import java.util.Map;
- /**
- * ClassName: DataSourceConfig
- * Package: com.datasource.demo.config
- * Description:
- * 数据源配置
- *
- * @Author wfk
- * @Create 2024/11/12 14:15
- * @Version 1.0
- */
- @Configuration
- public class DataSourceConfig {
- /**
- * 主库数据源
- *
- * @return
- */
- @Bean("master")
- @ConfigurationProperties(prefix = "mysql.datasource.master")
- public DruidDataSource dataSource1() {
- return DruidDataSourceBuilder.create().build();
- }
- /**
- * 从库数据源
- *
- * @return
- */
- @Bean("slave")
- @ConfigurationProperties(prefix = "mysql.datasource.slave")
- public DruidDataSource dataSource2() {
- return DruidDataSourceBuilder.create().build();
- }
- /**
- * 配置默认数据源
- *
- * @param masterDataSource
- * @param slaveDataSource
- *
- * 必须要加 @Primary 注解,优先下面的配置
- * @return
- */
- @Primary
- @Bean("dynamicDataSource")
- public DynamicDataSource dataSource(@Qualifier("master") DruidDataSource masterDataSource,
- @Qualifier("slave") DruidDataSource slaveDataSource) {
- Map<Object, Object> targetDataSources = new HashMap<>();
- targetDataSources.put(DataSourceType.MASTER.getName(), masterDataSource);
- targetDataSources.put(DataSourceType.SLAVE.getName(), slaveDataSource);
- DynamicDataSource dynamicDataSource = new DynamicDataSource();
- dynamicDataSource.setTargetDataSources(targetDataSources);
- dynamicDataSource.setDefaultTargetDataSource(masterDataSource);
- return dynamicDataSource;
- }
- }
复制代码 以上源码分析:
- 采用了阿里云的Druid数据库毗连池,以是需要利用 DruidDataSource
- @ConfigurationProperties(prefix = “mysql.datasource.slave”) 加载 yml 设置文件的自定义属性,自定义参数名称需要和 druid 的设置的标准名称一样,不然无法主动加载
- DynamicDataSource 继承了 抽象类 AbstractRoutingDataSource,是实现动态数据源的核心类,将所有数据源注入到这个类中,通过 DataSourceContextHolder 修改数据源,实现动态切换。
- @Primary 注解必须要加上,标志为优先利用的数据源
2.DataSourceContextHolder 数据源上下文
- /**
- * ClassName: DataSourceContextHolder
- * Package: com.datasource.demo.config.datasource
- * Description:
- * 本地线程,数据源上下文
- * @Author wfk
- * @Create 2024/11/12 14:45
- * @Version 1.0
- */
- public class DataSourceContextHolder {
- // 定义一个 ThreadLocal 变量,用于保存当前线程的数据源标识
- private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
- // 设置当前线程的数据源标识
- public static void setDataSource(String dataSource){
- contextHolder.set(dataSource);
- }
- // 获取当前线程的数据源标识
- public static String getDataSource(){
- return contextHolder.get();
- }
- // 清除当前线程的数据源标识
- public static void clearDataSource(){
- contextHolder.remove();
- }
- }
复制代码 以上源码分析:
- ThreadLocal 是一个线程局部变量容器,每个线程都有本身独立的副本。这意味着每个线程都可以独立地设置和获取本身的 ThreadLocal 变量值,而不会影响其他线程。
- setDataSource(String dataSource) 方法用于设置当火线程的数据源标识。调用这个方法时,传入的数据源标识会被生存在当火线程的 ThreadLocal 变量中。
- getDataSource() 方法用于获取当火线程的数据源标识。这个方法通常在数据源路由逻辑中被调用,根据当火线程的数据源标识选择合适的数据源进行数据库操作。
- clearDataSource() 方法用于清除当火线程的数据源标识。这个方法通常在哀求处置处罚完毕后被调用,以开释资源并防止内存泄漏。
3.DynamicDataSource 数据源路由
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
- /**
- * ClassName: DynamicDataSource
- * Package: com.datasource.demo.config.datasource
- * Description:
- *
- * @Author wfk
- * @Create 2024/11/12 14:42
- * @Version 1.0
- */
- @Slf4j
- public class DynamicDataSource extends AbstractRoutingDataSource {
- @Override
- protected Object determineCurrentLookupKey() {
- String dataSource = DataSourceContextHolder.getDataSource();
- log.info("当前数据源 {}", dataSource);
- return dataSource;
- }
- }
复制代码 以上源码分析:
- AbstractRoutingDataSource 是 Spring 框架提供的一个抽象类,用于实现数据源的动态切换。它提供了一个 determineCurrentLookupKey 方法,该方法返回一个键值,用于从设置的数据源映射中查找当前应利用的数据源。
- determineCurrentLookupKey 方法用于确定当火线程应该利用哪个数据源,通过调用DataSourceContextHolder.getDataSource() 来获取当火线程的数据源标识
4.DataSourceType 自定义数据源类别罗列
- public enum DataSourceType {
- MASTER("master"),
- SLAVE("slave");
- private String name;
- DataSourceType(String name) {
- this.name = name;
- }
- public String getName() {
- return name;
- }
- }
复制代码 4、数据源切换
1.自定义注解实现
自定义注解,作用于方法上,通过AOP切面拦截,根据注解的value所对应的数据库源类别,实现数据源切换。
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface DBDataSource {
- DataSourceType value() default DataSourceType.MASTER;
- }
复制代码 2.AOP切面实现
- import com.datasource.demo.annotions.DBDataSource;
- import com.datasource.demo.config.datasource.DataSourceContextHolder;
- import com.datasource.demo.enums.DataSourceType;
- import lombok.extern.slf4j.Slf4j;
- import org.aspectj.lang.JoinPoint;
- import org.aspectj.lang.annotation.Before;
- import org.aspectj.lang.annotation.Pointcut;
- import org.aspectj.lang.reflect.MethodSignature;
- import org.springframework.stereotype.Component;
- import org.aspectj.lang.annotation.Aspect;
- import java.lang.reflect.Method;
- /**
- * ClassName: DataSourceAspect
- * Package: com.datasource.demo.aspect
- * Description:
- * 数据源AOP切面
- *
- * @Author wfk
- * @Create 2024/11/12 16:05
- * @Version 1.0
- */
- @Slf4j
- @Aspect
- @Component
- public class DataSourceAspect {
- @Pointcut("execution(* com.datasource.demo.service..*.*(..))")
- public void aspect() {
- }
- @Before("aspect()")
- private void doBefore(JoinPoint joinPoint) {
- Object target = joinPoint.getTarget();
- Class<?> clazz = target.getClass();
- // 获取方法签名
- MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
- // 获取方法名称
- String methodName = methodSignature.getName();
- // 获取方法参数列表
- Class<?>[] parameterTypes = methodSignature.getMethod().getParameterTypes();
- try {
- // 通过方法名称和参数列表可以唯一获取方法(可能有重载的同名方法)
- Method method = clazz.getMethod(methodName, parameterTypes);
- // 判断方法是否存在指定的注解
- if (method !=null && method.isAnnotationPresent(DBDataSource.class)){
- DBDataSource annotation = method.getAnnotation(DBDataSource.class);
- // 如果是从库那就切换当前线程的数据源
- if (DataSourceType.SLAVE == annotation.value()) {
- DataSourceContextHolder.setDataSource(DataSourceType.SLAVE.getName());
- return;
- }
- }
- } catch (NoSuchMethodException e) {
- e.printStackTrace();
- }
- // 默认是使用主库
- DataSourceContextHolder.setDataSource(DataSourceType.MASTER.getName());
- }
- }
复制代码 以上源码分析:
1.@Pointcut("execution( com.datasource.demo.service….(…))")*
- execution():这是定义切入点表达式的关键字,用来匹配Java方法的执行毗连点。
- *:第一个星号表现返回值类型,这里的星号意味着匹配任何返回类型的方法。‘com.datasource.demo.service…*.*(…):这部分是切入点表达式的主体,指定了要匹配的方法的位置和名称。
- com.datasource.demo.service:这是包名,指明了要匹配的方法地点的包。
- ..:两个点号表现该包下的所有子包。
- *.*:第一个星号代表类名,第二个星号代表方法名,这里利用两个星号表现匹配该包及其子包下所有类的所有方法。
- (..):括号内的两个点号表现参数列表,这里表现匹配任何参数列表的方法。
2.方法调用和注解查抄
- 获取目标对象和方法签名。
- 通过反射获取方法对象,并查抄方法是否标注了 DBDataSource 注解。
- 假如方法标注了 DBDataSource 注解且注解值为 DataSourceType.SLAVE,则设置当火线程的数据源为从库。
- 否则,默认设置当火线程的数据源为主库。
3.最终实现如下
在service层的实现类中,添加注解,查询相干的业务,通过注解指定从库,写入数据默认利用主库,实现读写分离。
- @DBDataSource(DataSourceType.SLAVE)
- @Override
- public List<BookInfoPO> getBookInfoList() {
- return bookInfoMapper.getBookInfoListMapper();
- }
复制代码 总结
关于数据源切换另有很多种方式
- 在mapper层做拦截,对insert、update、delete 和 select 完全分离,可以通过MyCat、shardingsphere 等数据库中间件实现主动分离。
- 也可以通过规范方法名前缀,对get、find、query 开头等方法进行拦截,也可以实现读写分离。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |