一、为什么需要读写分离?

相信很多朋友都遇到过这样的场景:数据库的读请求远远多于写请求,主库的CPU经常飙到90%以上,而业务还在不断增长。这时候读写分离就像及时雨,把读请求分流到只读实例上,主库专心处理写请求,整个系统的吞吐量就能显著提升。

以电商系统为例,商品浏览、订单查询这类读操作占总请求量的80%以上。如果所有请求都走主库,就像把所有车辆都挤在一条车道上,不堵车才怪。PolarDB的读写分离功能,本质上就是给数据库开了条"公交专用道"。

二、配置中的常见陷阱

1. 连接串配置不当

新手最容易栽在连接串配置上。比如下面这个典型的错误示例:

// 错误示例:直接使用集群地址,无法实现读写分离
String url = "jdbc:mysql://pc-xxx.rwlb.rds.aliyuncs.com:3306/db";

正确的姿势应该是使用读写分离专用地址:

// 正确示例:使用读写分离地址
String url = "jdbc:mysql://pc-xxx.rwlb.rds.aliyuncs.com:3306/db?readOnly=true";

2. 事务中的读写混合

更隐蔽的问题是事务中的读写混合。看这段代码:

// 危险示例:事务中混合读写操作
@Transactional
public void updateProduct(Product product) {
    // 先查询(读操作)
    Product old = productMapper.selectById(product.getId());
    if(old.getStock() > 0) {
        // 后更新(写操作)
        productMapper.updateById(product);
    }
}

这种情况下,PolarDB会强制把所有操作都路由到主库,相当于读写分离白做了。正确的做法是把查询操作移到事务外部:

// 正确示例:分离读写操作
public void updateProduct(Product product) {
    // 非事务环境查询
    Product old = productMapper.selectById(product.getId());
    
    if(old.getStock() > 0) {
        // 开启事务只做写操作
        transactionTemplate.execute(status -> {
            productMapper.updateById(product);
            return Boolean.TRUE;
        });
    }
}

3. 负载均衡策略缺失

很多开发者以为配置了读写分离就万事大吉,却忽略了读库的负载均衡。比如:

# 不够完善的配置
DATABASES = {
    'read': {
        'HOST': 'ro-xxx.rwlb.rds.aliyuncs.com',
        'PORT': '3306'
    }
}

更专业的做法是配合连接池实现负载均衡:

# 优化后的配置
DATABASES = {
    'read': {
        'HOST': 'ro-xxx.rwlb.rds.aliyuncs.com',
        'PORT': '3306',
        'OPTIONS': {
            'read_default_file': '/etc/my.cnf',
            'init_command': 'SET SESSION read_only=1',
            'pool_size': 10,
            'max_overflow': 20,
            'pool_timeout': 30
        }
    }
}

三、高级技巧与优化

1. 读写权重调整

PolarDB允许为每个只读实例设置不同的权重。比如主库和只读库的配置比例为1:3:

-- 设置读库权重
ALTER DATABASE `read_db` 
SET READ_ONLY_WEIGHT = 3;

这在业务高峰期特别有用,可以根据实例的配置差异合理分配流量。

2. 路由策略定制

对于需要强一致性的查询,可以强制走主库:

// 强制走主库的查询
@MasterRoute
public Product getProductById(Long id) {
    return productMapper.selectById(id);
}

3. 延迟监控

通过系统表监控主从延迟:

-- 查看主从延迟
SELECT * FROM information_schema.ALIYUN_CLUSTER_STATUS 
WHERE role = 'READONLY';

建议设置告警,当延迟超过阈值时自动切换路由策略。

四、避坑指南与最佳实践

  1. 连接池配置:建议使用HikariCP或Druid,并设置合理的空闲连接超时时间。连接泄漏会导致实例连接数爆满。

  2. 故障转移:代码中需要处理只读实例不可用的情况,自动降级到主库查询。

  3. 压力测试:上线前务必用JMeter等工具模拟真实流量,验证配置是否合理。

  4. 监控告警:配置完善的监控体系,包括:连接数、QPS、延迟等核心指标。

  5. 灰度发布:新功能上线时,先对小部分流量开启读写分离,观察无异常后再全量。

五、典型应用场景分析

1. 电商大促

双11期间,读请求可能是平时的10倍。通过读写分离+自动扩容,可以轻松应对流量洪峰。某客户配置了5个只读实例,QPS从2000提升到20000。

2. 报表查询

金融行业的复杂报表查询往往耗时较长,分流到只读实例后,既不影响核心交易,又能保证报表及时生成。

3. 微服务架构

在Spring Cloud体系中,可以通过自定义LoadBalancer实现更精细的路由控制:

@Bean
public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
        ConfigurableApplicationContext context) {
    return ServiceInstanceListSupplier.builder()
            .withDiscoveryClient()
            .withHealthChecks()
            .withReadOnlyRouting()  // 自定义路由逻辑
            .build(context);
}

六、技术对比与选型

与传统的MySQL主从复制相比,PolarDB的读写分离有以下优势:

  1. 自动故障转移:只读实例宕机时自动切换,业务无感知
  2. 秒级添加只读节点:传统方案需要数小时的数据同步
  3. 一致性读:提供会话级一致性保证
  4. 弹性扩展:根据负载自动调整资源

但也要注意其局限性:跨地域部署时,延迟问题仍然存在;某些特殊SQL可能无法路由到只读库。

七、总结与展望

配置读写分离不是简单的改个连接串,而需要从架构设计、代码规范、运维监控等多个维度综合考虑。随着PolarDB的持续迭代,未来可能会出现更智能的路由策略,比如基于机器学习的负载预测。

记住:没有银弹,只有适合自己业务的方案才是最好的。建议从小规模试点开始,逐步积累经验,最终构建出既稳定又高效的数据库架构。