1. 为什么要在 MySQL 高可用里选 DRBD 这套组合
1.1 从一次真实的故障切换说起
几年前我接手过一套内部订单系统的数据库运维,单机 MySQL 跑了两年多,硬盘一坏就是半天停机,业务方电话打爆。后来我们决定上高可用,评估过主从复制加中间件、共享存储加集群文件系统、以及 DRBD 加 Pacemaker 加 Corosync 这几条路线。最后选了 DRBD 这套,原因很直接:我们的数据量不大(当时不到 200G),写入 QPS 峰值也就几千,但对数据一致性要求极高,不能容忍主从复制那种异步延迟导致的数据丢失。
DRBD 的本质是块设备级别的同步复制。你可以把它理解成两台服务器之间架了一根"虚拟的数据线",主节点写什么,从节点在块层面同步什么。它工作在操作系统内核的块设备层,MySQL 根本感知不到底下是本地盘还是网络盘。这一点很关键,因为这意味着 MySQL 不需要做任何改造,binlog、redo log、数据文件全部照常写,DRBD 在下面默默把每个块的变化复制到对端。
Pacemaker 和 Corosync 是这套方案的大脑和神经。Corosync 负责集群成员之间的心跳通信和消息传递,Pacemaker 负责根据资源状态做决策——谁当主、谁当备、什么时候切换、切换时先停什么后起什么。三者配合起来,才能实现"MySQL 主备加自动切换"这个目标。
1.2 这套方案到底解决了什么问题
单机 MySQL 的痛点很明确:硬件故障、系统崩溃、人为误操作,任何一个环节出问题都是长时间停机。传统主从复制虽然能做备份,但切换需要人工介入,而且异步复制存在数据丢失窗口。DRBD 方案的核心价值在于三点:
第一,数据零丢失。DRBD 支持协议 C(同步复制),主节点写操作必须等到从节点确认落盘才返回成功。这意味着即使主节点瞬间断电,从节点上的数据也是完整的。
第二,自动切换。Pacemaker 持续监控 MySQL 和 DRBD 的状态,主节点挂掉后自动在备节点提升 DRBD 为主、启动 MySQL、接管虚拟 IP,整个过程通常几十秒内完成,不需要人工登录操作。
第三,对应用透明。应用连接的是虚拟 IP,切换后 IP 漂移到新主节点,应用重连即可,不需要改配置。
1.3 适合什么样的场景,不适合什么样的场景
这套方案不是万能的,我踩过坑之后总结出它的适用边界。适合的场景:数据量在几百 G 以内、写入量中等、对数据一致性要求极高、预算有限不想买商业存储、团队有一定 Linux 运维基础。不适合的场景:数据量上 T 且持续增长、写入 QPS 极高(DRBD 同步复制的网络延迟会成为瓶颈)、跨机房容灾(DRBD 对网络延迟敏感,同城双活勉强,异地基本不可行)。
还有一个容易被忽略的点:DRBD 是块设备复制,从节点上的文件系统在备状态时是不可挂载的。也就是说你不能在备节点上直接读 MySQL 数据做报表查询。如果需要读写分离,这套方案满足不了,得另想办法。
2. 核心组件原理与关键参数拆解
2.1 DRBD 的工作机制与复制协议选择
DRBD 在每台节点上表现为一个块设备,比如 /dev/drbd0。它底层对应一块真实的磁盘分区或 LVM 逻辑卷,上层再放文件系统。数据流向是这样的:MySQL 写文件系统,文件系统写到 /dev/drbd0,DRBD 内核模块拦截写请求,一份写本地磁盘,一份通过网络发到对端。
复制协议有三个选项,这是选型时第一个要拍板的事:
| 协议 | 行为 | 数据安全性 | 性能影响 |
|---|---|---|---|
| 协议 A | 主节点写本地成功即返回,异步发往从节点 | 可能丢数据 | 最低 |
| 协议 B | 主节点写本地成功且从节点收到数据(未落盘)后返回 | 切换时可能丢 | 中等 |
| 协议 C | 主节点和从节点都确认落盘后才返回 | 零丢失 | 最高 |
我们选的是协议 C。代价是每次写都要等网络往返,实测在千兆内网、同机房环境下,写延迟增加约 0.3 到 0.8 毫秒。对于我们的业务完全可接受。如果你的业务对延迟极度敏感,需要慎重评估,或者考虑用 SSD 加万兆网卡来压低这个开销。
2.2 Pacemaker 的资源管理逻辑
Pacemaker 里一切皆资源。MySQL 是一个资源,DRBD 是一个资源,虚拟 IP 是一个资源。资源之间有依赖关系和启动顺序,这是配置的核心难点。
拿我们的场景举例,依赖链是这样的:DRBD 主资源必须先就绪,文件系统才能挂载;文件系统挂载后,MySQL 才能启动;MySQL 启动后,虚拟 IP 才能对外服务。Pacemaker 用约束(constraint)来描述这些关系,包括顺序约束(order)、位置约束(location)、协同约束(colocation)。
顺序约束决定"谁先谁后",比如 DRBD 提升为主之后才能挂文件系统。协同约束决定"谁和谁必须在一起",比如 MySQL 必须和 DRBD 主资源在同一节点。位置约束决定"谁倾向于在哪",比如可以设置某节点优先级更高。
我一开始没搞清协同约束,结果出现过 MySQL 在 A 节点、DRBD 主在 B 节点的诡异状态,MySQL 直接起不来。后来才明白,必须用 colocation 把 MySQL、文件系统、DRBD 主资源绑死在一起。
2.3 Corosync 的心跳与仲裁
Corosync 负责集群通信,最核心的是心跳机制。节点之间定期互发心跳包,超过设定时间收不到就判定对方失联。这里有个经典问题叫"脑裂"(split-brain):两个节点都认为对方挂了,各自把自己提升为主,数据就分叉了。
防脑裂的手段有两层。第一层是 Corosync 的冗余通信通道,我们配了两条心跳链路,一条走业务网,一条走单独的直连网线,任何一条通就不会误判。第二层是 DRBD 自己的脑裂检测,一旦发现两个节点都试图当主,会拒绝服务并记录到日志,需要人工介入恢复。
仲裁方面,两节点集群没有天然仲裁者,我们加了一个轻量的仲裁设备(quorum device),避免一个节点失联时另一个节点无法获得多数票而拒绝服务。这个细节很多教程不讲,但两节点集群不配仲裁,切换行为会很不稳定。
3. 从零搭建的完整实操过程
3.1 环境规划与基础准备
先明确我们的模拟环境。两台服务器,主机名分别叫 node-a 和 node-b,操作系统用主流的 Linux 发行版。每台机器两块网卡,一块业务网,一块心跳直连。磁盘方面,每台机器留一块独立分区或 LVM 卷专门给 DRBD 用,大小必须完全一致。
基础准备清单:
- 两台机器时间同步,用 chrony 或 ntp,时间偏差会导致集群通信异常
- 主机名和 hosts 解析配好,集群节点之间必须能通过主机名互相解析
- 关闭防火墙或放行必要端口:Corosync 用 5405 UDP,DRBD 用 7789 TCP
- 确认内核加载了 DRBD 模块,
modprobe drbd后lsmod | grep drbd能看到 - 安装软件包:drbd 相关工具、pacemaker、corosync、资源代理(resource-agents)
注意:两台机器的 DRBD 版本、Pacemaker 版本尽量保持一致,版本差异会导致协议不兼容,我吃过这个亏,排查了大半天。
3.2 DRBD 配置详解
DRBD 的主配置文件通常在 /etc/drbd.d/ 目录下,我们建一个 mysql.res 文件。核心配置分三块:resource 定义、on 节点定义、net 网络参数。
resource mysql { protocol C; device /dev/drbd0; disk /dev/sdb1; meta-disk internal; on node-a { address 10.0.0.1:7789; } on node-b { address 10.0.0.2:7789; } net { cram-hmac-alg sha1; shared-secret "your-secret-here"; after-sb-0pri discard-zero-changes; after-sb-1pri discard-secondary; after-sb-2pri disconnect; } }逐项解释关键参数。protocol C就是前面说的同步复制。meta-disk internal表示元数据存在数据盘内部,省事但会占用一点空间。cram-hmac-alg和shared-secret是节点间认证,防止未授权节点接入。
after-sb-*这几个参数是脑裂后的自动恢复策略,非常重要。after-sb-0pri discard-zero-changes表示双方都没有改动的脑裂,直接丢弃零改动方。after-sb-1pri discard-secondary表示一方有改动一方没有,丢弃没改动的那方。after-sb-2pri disconnect表示双方都有改动,这种情况最危险,直接断开不自动恢复,必须人工判断哪边数据是对的。这个策略是保守但安全的,宁可人工介入也不能自动选错。
初始化流程:
# 两台机器都执行,创建元数据 drbdadm create-md mysql # 两台机器都启动 DRBD 服务 systemctl start drbd # 在 node-a 上提升为主 drbdadm primary mysql --force # 查看状态,确认 Primary/Secondary 且 uptodate cat /proc/drbd第一次提升主用--force,因为初始状态两边都是不一致的,需要强制指定一方为主进行初始同步。同步完成后,cat /proc/drbd应该看到两个节点都是UpToDate。
3.3 文件系统与 MySQL 数据目录
DRBD 主资源就绪后,在 node-a 上格式化并挂载:
mkfs.ext4 /dev/drbd0 mkdir -p /data/mysql mount /dev/drbd0 /data/mysql然后把 MySQL 的数据目录指向 /data/mysql。如果是新装 MySQL,初始化时指定 datadir 即可。如果是迁移已有数据,先停 MySQL,把原数据目录内容 rsync 到 /data/mysql,再改配置。
实操心得:文件系统建议用 ext4 或 xfs,两者在 DRBD 上表现都稳定。不要用 btrfs,我试过在切换时出现过挂载异常。另外挂载参数加上
noatime,减少不必要的写操作,能降低 DRBD 同步压力。
3.4 Pacemaker 集群初始化与资源配置
先在两台机器上启动 Corosync 和 Pacemaker:
systemctl start corosync systemctl start pacemaker然后在任一节点上做集群初始化:
# 设置集群认证密码 passwd hacluster # 认证并启动集群 pcs cluster auth node-a node-b pcs cluster setup --name mysql_cluster node-a node-b pcs cluster start --all pcs cluster enable --all接下来配置资源。先配 DRBD 资源,用 pacemaker 自带的 drbd 资源代理:
pcs resource create drbd_mysql ocf:linbit:drbd \ drbd_resource=mysql \ op monitor interval=15s role=Master \ op monitor interval=30s role=Slave然后配置主从资源:
pcs resource master ms_drbd_mysql drbd_mysql \ master-max=1 master-node-max=1 \ clone-max=2 clone-node-max=1 \ notify=true接着配置文件系统、MySQL、虚拟 IP:
pcs resource create fs_mysql Filesystem \ device=/dev/drbd0 directory=/data/mysql fstype=ext4 pcs resource create vip_mysql IPaddr2 ip=10.0.0.100 pcs resource create mysql_service systemd:mysqld \ op monitor interval=20s最后配置约束,这是让整套系统正确运转的关键:
# 顺序:DRBD 主 -> 文件系统 -> MySQL -> VIP pcs constraint order promote ms_drbd_mysql then fs_mysql pcs constraint order fs_mysql then mysql_service pcs constraint order mysql_service then vip_mysql # 协同:文件系统、MySQL、VIP 必须和 DRBD 主在同一节点 pcs constraint colocation add fs_mysql with ms_drbd_mysql INFINITY pcs constraint colocation add mysql_service with fs_mysql INFINITY pcs constraint colocation add vip_mysql with mysql_service INFINITY配置完成后pcs status应该看到所有资源正常启动,DRBD 主在某一节点,其余资源跟随。
3.5 切换测试与验证
配置完不测试等于没配。我们做了三类测试。
第一类,手动切换。pcs node standby node-a,观察资源是否平滑迁移到 node-b。正常情况几十秒内完成,VIP 漂移,MySQL 在新节点启动。
第二类,模拟主节点宕机。直接echo b > /proc/sysrq-trigger强制重启 node-a,看 node-b 是否自动接管。这个测试最接近真实故障。
第三类,模拟 MySQL 进程崩溃。kill -9掉 mysqld 进程,看 Pacemaker 是否检测到并触发切换。
每次测试后都要验证数据完整性,在切换前后各插入一条带时间戳的记录,切换后查询确认数据没丢。
4. 常见问题排查与避坑经验
4.1 脑裂的识别与恢复
脑裂是这套方案最危险的状态。识别方法:cat /proc/drbd看到两个节点都是Primary,或者日志里出现Split-Brain detected。一旦脑裂,DRBD 会停止服务,MySQL 也起不来。
恢复步骤必须谨慎。首先确认哪边的数据是新的,可以通过对比 binlog 位置或者业务数据时间戳判断。假设 node-a 数据新,node-b 数据旧:
# 在 node-b 上放弃自己的数据 drbdadm secondary mysql drbdadm connect --discard-my-data mysql # 在 node-a 上重新连接 drbdadm connect mysql注意:
--discard-my-data会丢弃该节点所有数据,执行前务必确认清楚哪边是正确数据。我见过有人搞反了,把新数据覆盖掉,损失惨重。
4.2 切换后 MySQL 起不来的排查
切换后 MySQL 启动失败是最常见的故障。排查顺序:先看 DRBD 是否成功提升为主,cat /proc/drbd确认;再看文件系统是否挂载,df -h确认;最后看 MySQL 错误日志,通常在数据目录下的 .err 文件。
常见原因有几个。一是文件系统没挂上,MySQL 找不到数据目录。二是权限问题,切换后文件属主变了,MySQL 用户读不了。三是 MySQL 的 pid 文件或 socket 文件残留,导致启动冲突。针对第三种,可以在 MySQL 配置里把 pid 和 socket 放到非 DRBD 的本地目录,避免切换时残留。
4.3 性能调优的几个关键点
DRBD 同步复制的性能瓶颈通常在网络和磁盘。我们做过几轮调优,效果比较明显的措施:
- 网卡开启巨帧(MTU 9000),减少网络包处理开销
- DRBD 的
sndbuf-size和rcvbuf-size调大,默认值在高吞吐下不够用 - 用 SSD 做 DRBD 底层存储,机械盘在同步复制下延迟明显
- MySQL 的
innodb_flush_log_at_trx_commit保持 1,不要为了性能改成 2,否则 DRBD 的零丢失优势就没了
调优前后我们做了对比测试,同样压力下,优化后写延迟从平均 1.2 毫秒降到 0.5 毫秒左右。
4.4 常见问题速查表
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 资源无法启动 | 约束配置错误 | pcs constraint show检查依赖 |
| DRBD 一直 Inconsistent | 初始同步未完成 | 等待同步,查看同步进度 |
| 切换后 VIP 不通 | IP 资源未启动 | pcs status看 VIP 状态 |
| MySQL 反复重启 | 资源监控间隔太短 | 调大 monitor interval |
| 集群节点失联 | 心跳网络故障 | 检查 Corosync 日志和网卡 |
| 脑裂后无法恢复 | 恢复策略配置 | 检查 after-sb 参数 |
5. 这套方案的运维日常与个人体会
5.1 日常巡检要看什么
集群稳定运行后,日常巡检不能省。我每天会看几个东西:pcs status确认所有资源正常、cat /proc/drbd确认同步状态是 UpToDate、MySQL 错误日志有没有异常、DRBD 日志有没有同步延迟告警。另外定期做一次计划内切换演练,保持团队对切换流程的熟悉度,真出事的时候才不会手忙脚乱。
监控方面,我们把 Pacemaker 的资源状态、DRBD 的同步状态和延迟、MySQL 的连接数和慢查询都接入了监控系统,设置阈值告警。特别是 DRBD 的同步延迟,一旦持续升高说明底层存储或网络有问题,要提前处理。
5.2 我踩过的几个坑
第一个坑是没配仲裁设备。两节点集群在单节点故障时,剩下的节点因为拿不到多数票会拒绝服务,结果就是该切换的时候切不了。加了仲裁设备后这个问题解决。
第二个坑是 MySQL 的 socket 文件放在 DRBD 目录里。切换时旧 socket 残留,新节点启动 MySQL 报地址已占用。后来把 socket 和 pid 都挪到本地目录,问题消失。
第三个坑是没做切换演练就直接上生产。有一次真实故障触发切换,结果因为一个约束配置的小错误,切换卡住了。从那以后我坚持每次变更后都做一次完整的切换测试。
5.3 后续可以怎么扩展
这套基础架构搭好后,有几个方向可以继续完善。一是加第三个节点做仲裁和只读查询,缓解两节点的仲裁压力。二是把 DRBD 的底层存储换成 LVM,方便在线扩容。三是结合备份工具做定期全量和增量备份,毕竟 DRBD 只防硬件故障,防不了误删数据,误删会同步到两个节点。
如果数据量增长到 DRBD 扛不住的程度,可以考虑迁移到分布式存储或者商业高可用方案。但就我这些年的经验,对于中小规模、强一致要求的场景,DRBD 加 Pacemaker 加 Corosync 这套组合依然是性价比很高的选择,稳定、可控、不依赖外部服务,出了问题自己就能排查到底层。