MySQL 运维知识点(十六)---- 读写分离

一、 读写分离介绍

1. 什么是读写分离?

读写分离是基于主从复制架构,让 主数据库处理事务性写操作从数据库处理查询读操作,通过应用逻辑或中间件将读写操作路由到不同数据库服务器。

2. 为什么要读写分离?
  • 提升性能:分摊数据库负载,避免读写冲突

  • 提高并发:主库专注写,多个从库分担读压力

  • 高可用性:从库可作为主库的故障备用

  • 业务解耦:不同业务模块可使用不同的数据库连接

3. 实现方式
实现方式 描述 优点 缺点
应用层实现 在代码中区分读写数据源 灵活可控,性能好 代码侵入性强,维护复杂
中间件代理 使用Mycat、ShardingSphere等中间件 对应用透明,集中管理 单点风险,增加网络跳转

二、 一主一从架构

1. 架构图
复制代码
┌─────────────┐   复制    ┌─────────────┐
│  主库 Master │ ────────> │ 从库 Slave  │
│   (写)      │           │   (读)      │
└─────────────┘           └─────────────┘
2. 配置步骤

主库配置 (my.cnf)

复制代码
[mysqld]
server-id = 1
log-bin = mysql-bin
binlog-format = ROW

从库配置 (my.cnf)

复制代码
[mysqld]
server-id = 2
relay-log = mysql-relay-bin
read_only = 1  # 从库只读

建立复制

sql 复制代码
-- 在主库创建复制用户
CREATE USER 'repl'@'%' IDENTIFIED BY 'password';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';

-- 在从库配置主从连接
CHANGE MASTER TO
MASTER_HOST='master_ip',
MASTER_USER='repl',
MASTER_PASSWORD='password',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=154;

START SLAVE;

三、 一主一从读写分离

1. 应用层实现(Spring Boot示例)
sql 复制代码
# application.yml
spring:
  datasource:
    master:
      url: jdbc:mysql://master:3306/db
      username: user
      password: pass
    slave:
      url: jdbc:mysql://slave:3306/db
      username: user
      password: pass
java 复制代码
@Configuration
public class DataSourceConfig {
    
    @Bean
    @Primary
    public DataSource dataSource() {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("master", masterDataSource());
        targetDataSources.put("slave", slaveDataSource());
        
        RoutingDataSource routingDataSource = new RoutingDataSource();
        routingDataSource.setTargetDataSources(targetDataSources);
        routingDataSource.setDefaultTargetDataSource(masterDataSource());
        return routingDataSource;
    }
}

// 使用注解切换数据源
@Service
public class UserService {
    
    @Transactional
    @DataSource("master")  // 写操作使用主库
    public void createUser(User user) {
        userMapper.insert(user);
    }
    
    @DataSource("slave")   // 读操作使用从库
    public User getUserById(Long id) {
        return userMapper.selectById(id);
    }
}
2. 中间件实现(Mycat配置)

schema.xml

XML 复制代码
<dataHost name="host1" maxCon="1000" minCon="10" balance="1"
          writeType="0" dbType="mysql" dbDriver="jdbc">
    <heartbeat>select user()</heartbeat>
    <writeHost host="hostM1" url="jdbc:mysql://master:3306" 
               user="root" password="123456">
        <readHost host="hostS1" url="jdbc:mysql://slave:3306" 
                  user="root" password="123456"/>
    </writeHost>
</dataHost>

balance参数说明

  • 0:不开启读写分离

  • 1:所有读操作随机发送到所有读Host

  • 2:所有读操作随机发送到所有Host(包括WriteHost)

  • 3:所有读操作随机发送到所有读Host,不在writeHost的readHost上执行

四、 双主双从架构

1. 架构图
XML 复制代码
┌─────────────┐           ┌─────────────┐
│ 主库1 Master1│ ←───────→ │ 主库2 Master2│
└─────────────┘           └─────────────┘
      ↓                           ↓
┌─────────────┐           ┌─────────────┐
│ 从库1 Slave1 │           │ 从库2 Slave2 │
└─────────────┘           └─────────────┘
2. 双主配置

Master1 配置

XML 复制代码
[mysqld]
server-id = 1
log-bin = mysql-bin
auto_increment_increment = 2  # 自增步长
auto_increment_offset = 1     # 自增起始值

Master2 配置

XML 复制代码
[mysqld]
server-id = 2
log-bin = mysql-bin
auto_increment_increment = 2
auto_increment_offset = 2

配置互为主从

sql 复制代码
-- 在Master1上执行
CHANGE MASTER TO
MASTER_HOST='master2_ip',
MASTER_USER='repl',
MASTER_PASSWORD='password',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=154;

-- 在Master2上执行  
CHANGE MASTER TO
MASTER_HOST='master1_ip',
MASTER_USER='repl',
MASTER_PASSWORD='password',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=154;
3. 双主双从优势
  • 高可用:任一主库故障,另一主库可继续提供服务

  • 负载均衡:写操作可分摊到两个主库

  • 容灾备份:多节点数据冗余,数据更安全

五、 双主双从读写分离

1. Mycat 配置实现

schema.xml

XML 复制代码
<dataHost name="host1" maxCon="1000" minCon="10" balance="1"
          writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1">
    <heartbeat>select user()</heartbeat>
    <writeHost host="hostM1" url="jdbc:mysql://master1:3306" 
               user="root" password="123456">
        <readHost host="hostS1" url="jdbc:mysql://slave1:3306" 
                  user="root" password="123456"/>
    </writeHost>
    <writeHost host="hostM2" url="jdbc:mysql://master2:3306" 
               user="root" password="123456">
        <readHost host="hostS2" url="jdbc:mysql://slave2:3306" 
                  user="root" password="123456"/>
    </writeHost>
</dataHost>

关键参数

  • switchType="1":自动切换,当写Host宕机时自动切换到其他写Host

  • balance="1":读请求负载均衡到所有读Host

2. 应用层多数据源配置
java 复制代码
@Configuration
public class MultiDataSourceConfig {
    
    @Bean
    public DataSource routingDataSource() {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("master1", master1DataSource());
        targetDataSources.put("master2", master2DataSource());
        targetDataSources.put("slave1", slave1DataSource());
        targetDataSources.put("slave2", slave2DataSource());
        
        RoutingDataSource routingDataSource = new RoutingDataSource();
        routingDataSource.setTargetDataSources(targetDataSources);
        routingDataSource.setDefaultTargetDataSource(master1DataSource());
        return routingDataSource;
    }
    
    // 动态数据源路由
    public class ReadWriteRoutingStrategy {
        private AtomicInteger masterCounter = new AtomicInteger(0);
        private AtomicInteger slaveCounter = new AtomicInteger(0);
        
        public String determineMasterLookupKey() {
            int index = masterCounter.incrementAndGet() % 2;
            return "master" + (index + 1);
        }
        
        public String determineSlaveLookupKey() {
            int index = slaveCounter.incrementAndGet() % 2;
            return "slave" + (index + 1);
        }
    }
}

六、 读写分离的注意事项

1. 数据一致性问题
  • 复制延迟:主从同步存在毫秒级延迟

  • 解决方案

    • 对实时性要求高的读操作强制走主库

    • 使用半同步复制减少数据丢失风险

    • 应用层根据业务容忍度选择数据源

2. 事务问题
java 复制代码
@Service
public class OrderService {
    
    @Transactional
    public void createOrder(Order order) {
        // 强制使用主库,避免事务中读写分离
        DataSourceContext.setDataSource("master");
        try {
            orderMapper.insert(order);
            // 即使查询也走主库,保证事务内数据一致性
            Order newOrder = orderMapper.selectById(order.getId());
            // ... 其他业务逻辑
        } finally {
            DataSourceContext.clear();
        }
    }
}
3. 故障转移处理
  • 监控主从复制状态

  • 自动或手动切换故障节点

  • 应用层重试机制

4. 负载均衡策略
  • 轮询:依次分配请求

  • 权重:根据服务器性能分配不同权重

  • 最少连接:选择当前连接数最少的服务器

  • 响应时间:选择响应时间最短的服务器

七、 监控与维护

1. 关键监控指标
sql 复制代码
-- 监控主从延迟
SHOW SLAVE STATUS\G
-- 关注:Seconds_Behind_Master

-- 监控数据库连接数
SHOW STATUS LIKE 'Threads_connected';

-- 监控QPS/TPS
SHOW STATUS LIKE 'Queries';
SHOW STATUS LIKE 'Com_commit';
SHOW STATUS LIKE 'Com_rollback';
2. 日常维护命令
sql 复制代码
-- 检查复制状态
SHOW REPLICA STATUS;  -- MySQL 8.0+
SHOW SLAVE STATUS;    -- MySQL 5.7

-- 停止/启动复制
STOP REPLICA;
START REPLICA;

-- 跳过复制错误(谨慎使用)
SET GLOBAL sql_replica_skip_counter = 1;

总结

读写分离架构演进

sql 复制代码
单机MySQL → 一主一从 → 一主多从 → 双主多从

技术选型建议

  • 小型项目:应用层实现 + 一主一从

  • 中型项目:Mycat中间件 + 一主多从

  • 大型项目:ShardingSphere + 双主多从 + 分库分表

核心价值 :通过读写分离,可以显著提升数据库系统的读性能可用性,是构建高并发应用的基础架构之一。

相关推荐
zym大哥大2 小时前
MySQL用户管理
数据库·mysql
musenh2 小时前
mysql学习---事务
学习·mysql
musenh2 小时前
mysql学习--DCL
学习·mysql·adb
老朋友此林2 小时前
高并发下如何保证 Caffeine + Redis 多级缓存的一致性问题?MySQL、Redis 缓存一致性问题?
数据库·redis·缓存
CS Beginner2 小时前
【Linux】安装配置mysql中出现的问题2
linux·mysql·adb
会飞的鱼_1232 小时前
MySQL主主复制+Keepalived高可用集群搭建与故障切换实战
数据库·mysql
gsfl5 小时前
Redis分布式锁
数据库·redis·分布式
Rsingstarzengjx5 小时前
搭建Jenkins gitlab 环境
运维·服务器
Li zlun6 小时前
MySQL 配置管理与日志系统完全指南:从基础到高级优化
数据库·mysql