21、数据库可靠性模式:读写分离、分库分表、主从复制、故障转移、连接池管理(HikariCP)
数据库,往往是系统里最脆弱的一环。我见过太多项目,业务代码写得漂漂亮亮,一到高并发就垮在数据库上。说白了,数据库的可靠性不是靠运气,而是靠一套组合拳。今天我们就聊聊这五个核心模式:读写分离、分库分表、主从复制、故障转移,还有连接池管理。
主从复制:高可用的地基
主从复制是后面所有模式的基础。没有它,读写分离和故障转移都是空谈。
原理其实不复杂。主库(Master)负责写操作,把变更记录到二进制日志(binlog)里。从库(Slave)拉取这些日志,在自己身上重放一遍。这样主从数据就保持同步了。
我个人习惯用异步复制,性能好。但要注意,异步复制有数据丢失的风险。主库挂了,最后几条事务可能还没传到从库。
MySQL 8.0 配置主从复制,核心步骤就这几行:
-- 主库配置
server-id = 1
log_bin = mysql-bin
binlog_format = ROW
-- 从库配置
server-id = 2
relay_log = relay-bin
read_only = 1
-- 从库执行
CHANGE MASTER TO
MASTER_HOST='192.168.1.10',
MASTER_USER='repl',
MASTER_PASSWORD='password',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=0;
START SLAVE;
读写分离:让查询不拖累写入
你想想看,一个系统里读请求往往是写请求的几十倍。如果读写都压在同一台库上,写操作很容易被慢查询拖死。
读写分离的思路很简单:写走主库,读走从库。这样主库专心处理写入,从库分担查询压力。
实现方式有两种。一种是代码层硬编码,在 DAO 层判断 SQL 类型,动态切换数据源。另一种是用中间件,比如 ShardingSphere、MyCat。
我个人更推荐中间件方案。为什么?因为对业务代码无侵入。你想想,如果每个 DAO 方法都要手动指定走主库还是从库,那维护成本太高了。
- 写操作(INSERT/UPDATE/DELETE)→ 主库
- 实时性要求高的读 → 主库(避免主从延迟)
- 普通查询 → 从库
- 主从延迟敏感的业务,可以设置延迟阈值,超过阈值自动切回主库
分库分表:突破单库瓶颈
单库能扛的并发是有限的。当数据量到了千万级、亿级,读写分离也救不了你。这时候就得考虑分库分表了。
分库,是把数据分散到多个数据库实例。分表,是把一张大表拆成多张小表。两者可以组合使用。
分片策略很关键。我常用的策略有几种:
| 策略 | 说明 | 适用场景 |
|---|---|---|
| 哈希分片 | 对分片键取模,均匀分布 | 数据量均匀,无热点 |
| 范围分片 | 按时间、ID 范围划分 | 有天然范围属性,如订单表按月份分 |
| 列表分片 | 按枚举值划分 | 地区、类型等有限枚举值 |
嗯,这里要注意。分片键选错了,后面改起来非常痛苦。我见过一个项目用用户手机号后四位做分片键,结果某个号段的用户暴增,导致单库热点。后来他们不得不做数据迁移,折腾了两周。
分库分表后的查询,要尽量带上分片键。不带分片键的查询,会广播到所有分片,性能极差。说白了,这就是「查询路由」的问题。
故障转移:让宕机对用户透明
主库挂了怎么办?不能干等着 DBA 手动切换。故障转移就是要自动完成这件事。
常见的方案有 MHA、Orchestrator,还有 MySQL 8.0 自带的 InnoDB Cluster + MySQL Router。
流程大致是这样:
- 监控组件检测到主库不可用
- 从候选从库中选一个数据最新的
- 把选中的从库提升为新主库
- 其他从库重新指向新主库
- 应用层切换数据源连接
整个过程最好控制在 30 秒以内。超过 30 秒,用户就能感知到服务不可用了。
连接池管理:HikariCP 实战
连接池这东西,看似简单,但配置不好一样出问题。我见过有人把连接池设成 200,结果数据库连接数被打满,整个服务瘫痪。
HikariCP 是目前性能最好的连接池,Spring Boot 2.0 之后默认就是它。为什么快?因为它做了很多优化:
- 代码极简,字节码量少
- 无锁集合实现,减少线程竞争
- 代理模式优化,减少方法调用开销
配置上,我个人的经验是:
# 核心参数
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.idle-timeout=300000
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.max-lifetime=1800000
# 监控参数
spring.datasource.hikari.pool-name=MyPool
spring.datasource.hikari.register-mbeans=true
maximum-pool-size 设多少合适?这得看你的数据库规格和业务并发量。一般 4 核 8G 的数据库,20-30 个连接就够了。设太大反而增加上下文切换开销。
还有一个容易被忽略的参数:max-lifetime。它控制连接的最大存活时间。为什么要设这个?因为数据库防火墙或者网络设备可能会断开长时间空闲的连接。设一个合理的 max-lifetime,让连接定期重建,避免被「断连」。
- connection-timeout 不要设太长,30 秒足够。超过 30 秒拿不到连接,说明系统已经出问题了,不如快速失败。
- idle-timeout 要小于数据库的 wait_timeout,否则空闲连接会被数据库主动断开。
- 生产环境一定要开启 register-mbeans,方便通过 JMX 监控连接池状态。
整体架构图
下面这张图展示了这五个模式如何协同工作。你可以看到,主从复制是底层支撑,读写分离和分库分表是横向扩展手段,故障转移保证高可用,连接池管理则贯穿始终。
这五个模式不是孤立的。主从复制是基础,读写分离和分库分表解决性能问题,故障转移保证可用性,连接池管理则优化资源使用。把它们组合起来,才能构建一个真正高可靠的数据库层。
公众号:蓝海资料掘金营,微信deep3321