服务器入坑指北:异地复制的积压与带宽换算
看到"延迟 3600 秒"就以为要赶 1 小时;不知道积压该看哪个数;限速值凭感觉拍。
在你动手去读之前,先把两条前提看清:① 延迟与积压不是一回事——延迟是时间(秒,而且可以是空值),积压是量(字节,两个位点相减);② 空值不等于零——把"量不出来"当 0,会得出"已经追平"这种反向结论。
这篇给三段可直接粘贴的内容:读两类量 → 算两式 → 改与读回(最后一段改限速是写操作)。取值用占位符,字段名、参数名与路径照写,每个数字都连着单位;每条命令带它自己的环境前提。
积压和延迟不是一回事
延迟回答"慢了多久",积压回答"还欠多少":
- 延迟:
Seconds_Behind_Source——单位是秒,且可以为NULL(空表示"当前量不出来",不是 0、也不是很大); - 积压:两个位点相减——
Read_Source_Log_Pos减去Exec_Source_Log_Pos,单位是字节位点。
判据:先问"我要的是等多久、还是欠多少"——拿秒数当积压,量纲从一开始就不成立(下面第二式会把它演示出来)。
一处写法要标清:Seconds_Behind_Master是旧名——官方文档说明该写法自 8.0.22 起改名、8.4 起不再接受旧别名(确切的版本与措辞以官方文档现读为准);看到它要标出"旧名",不要当可用写法引用。
积压怎么量
SHOW REPLICA STATUS\G # 第一现场:读复制状态的全部字段(需数据库客户端与权限;本机没有,未实跑)
mysql -u <用户名> -e 'SHOW REPLICA STATUS\G' # 脚本化读法(需先装 mysql 客户端)
cat /proc/net/dev # 带宽读入口之一:这些字节数是自接口启动以来的累计值,不是速率
grep -E -e 'eth0' /proc/net/dev # 只看那一条接口的累计计数:这些字节数自接口启动以来一直累加,不是速率
ip -br link # 先看本机接口叫什么(需先装 iproute2;接口名可能是 eth1 / ens5 / enp3s0)
grep -E -e 'eth0' /proc/net/dev # 占位写法:eth0 只是接口名占位,先按上面那行确认本机接口名;名字不对时它零输出、exit 1,不是"读不出来"
sar -n DEV <间隔> <次数> # 带宽读入口之二:判据列是 rxkB/s 与 txkB/s(已是每秒速率;需先装 sysstat)
ss -tnp # 复制链路还连着吗(判"停了"还是"慢";需先装 iproute2)
date +%s # 两次取值相减=Δ时间(秒)
判据(这个数是量还是时间、什么口径):
- 位点:
Read_Source_Log_Pos(已从主库读到的位点)与Exec_Source_Log_Pos(已应用到从库的位点)——同为字节位点,相减就是积压; - 中继位点
Relay_Log_Pos:用来判**“卡在读还是卡在应用”**; - 累计:
cat /proc/net/dev的字节数自接口启动以来一直累加(要区间量就取两次差值再除间隔); - 速率:
sar -n DEV <间隔> <次数>的rxkB/s/txkB/s已经除过时间。
限速参数名要写对:rsync的限速参数名是--bwlimit=<RATE>,它的单位是每秒千字节(换算成字节每秒要 ×1024);旧资料里常写成--bwlimit=KBPS——那是把单位误当成了参数名,KBPS不是参数名。
三种读数读出来长这样(值用占位符):
Read_Source_Log_Pos: <字节位点>
Exec_Source_Log_Pos: <字节位点>
Seconds_Behind_Source: <秒 或 NULL>
eth0: <接收字节累计> ... <发送字节累计> ... # /proc/net/dev:累计值,不是速率
... rxkB/s <值> txkB/s <值> ... # sar -n DEV:已是每秒速率
"空"要单独判:Seconds_Behind_Source 取 NULL 的确切条件以官方文档现读为准(本机没有数据库客户端、我无法自证)——但读法不变:空不等于 0。
追赶时间怎么算
追赶时间 = 积压 / 可用带宽——两个量必须同单位、同一时间窗(积压用字节、带宽用字节每秒)。
算一遍(数值为演示值):Read_Source_Log_Pos 取 10737418240、Exec_Source_Log_Pos 取 1073741824 ⇒ 积压 = 10737418240 - 1073741824 = 9663676416(字节);限速 --bwlimit=<KB每秒> 取 8192(每秒千字节)⇒ 可用带宽 = 8192 * 1024 = 8388608(字节每秒)⇒ 追赶时间 = 9663676416 / 8388608 = 1152(秒,19.2 分钟)。
第二式(反例):为什么不能拿秒数当积压。
把秒数代进同一个式子:延迟按 3600 秒算 ⇒ 3600 / 8388608 ≈ 0.000429——这个数的量纲是"秒的平方每字节",它不是时间;换任何带宽进去,都得不到一个"追赶时间"。积压用字节位点差、带宽用字节每秒,两者才是可除的。
判据:先算这个数,再决定限速给多少——它比"延迟多少秒"更能指导动作。
再补一式:积压增长速度 = 产生速率 − 应用速率。
- 产生速率 =
Read_Source_Log_Pos的两次差值 ÷ 间隔(主库产生日志的速度); - 应用速率 =
Exec_Source_Log_Pos的两次差值 ÷ 间隔(从库应用的速度)。
算一遍(数值为演示值):产生速率 4194304(字节每秒)、应用速率 3145728(字节每秒)⇒ 积压增长速度 = 4194304 - 3145728 = 1048576(字节每秒,也就是 1 MiB 每秒)。
判据:这个数为正 ⇒ 越欠越多——此时"追赶时间"那一式给不出有限值(限速放开多少都追不上,得先让应用速度提上来);为负 ⇒ 在追,再用第一式算"还要多久追平"。两式要一起用:先看积压是在涨还是在消,再谈限速给多少。
判据:先算这个数,再决定限速给多少——它比"延迟多少秒"更能指导动作。
三个口径别混(位点 / 累计 / 速率)
三个口径量的不是一回事:位点(两个位置相减 = 积压,字节)· 累计计数器(/proc/net/dev 的字节数,自启动至今的总量)· 速率(sar -n DEV <间隔> <次数> 的 rxkB/s / txkB/s,已经除过时间)——三者的读入口与口径上面都逐条给过,这一节只留最值钱的一条:
混用的典型错数:拿累计值当速率用(或反过来),会得出"整个运转时长的平均吞吐"——那个数既不描述当下、也不描述区间。判据:先问"我要的是还欠多少、总量、还是当前速度";答错就一定会读到对不上的数(例如把 /proc/net/dev 的累计值直接当成"当前吞吐"报出去)。
取舍:限速保守还是激进
- 限速低:对业务影响小;代价是追赶时间长、积压可能持续增长;
- 限速高:追得快;代价是挤占业务带宽、可能影响在线请求;
- 不限速:最快;代价是峰值期把链路打满。
判据:先把"追赶时间"算出来,再看它是否落在你能接受的时间窗内——"还欠多少 ÷ 能给多少"这一步,比看延迟秒数更能定限速值。
改与读回
rsync --bwlimit=<KB每秒> <源> <目标> # 限速侧:把可用带宽压到不影响业务(单位是每秒千字节;×1024 得字节每秒)
du -sb <目录> # 待传量:-b 是表观字节数;du 默认按 inode 去重、硬链接只计一次(-l 才重复计)
sar -n DEV <间隔> <次数> # 读回:rxkB/s / txkB/s 是不是你限的那个量级(需先装 sysstat)
ss -tnp # 读回:复制链路还在不在(需先装 iproute2)
先记原值(限速设定、现读位点各记一份),改完回读确认;改限速是写操作、需要相应权限,要跨重启就写进它的持久化落点(不是只改本次运行值)。
现象到动作的判定表
| 现象 | 结论 | 动作 |
|---|---|---|
| 延迟很大 | 先换算成积压再看 | 用两个位点相减得字节积压,再算追赶时间 |
| 延迟是空的 | 量不出来,不是 0 | 别当"已追平";改用位点差判断 |
| 两个地方吞吐对不上 | 累计对速率 | 要速率用 sar -n DEV,要区间量用两次差值 ÷ 间隔 |
| 积压一直不降 | 带宽不够 | 算追赶时间,再看限速能放到多少 |
| 改了限速没变化 | 可能改的不是当前生效的那一层 | 核限速的持久化落点与本次生效值 |
这篇不解决什么
- 讲性能与掉电窗口的取舍的那篇管掉电窗口与性能的总取舍(积压在那里只是邻接词);本篇管积压与带宽的换算。判定入口:“掉电能丢多少、性能如何权衡”→ 那篇;“还欠多少、多久能追平”→ 本篇。
- 讲网卡中断与软中断分布的那篇管中断分布与单核打满;本篇管应用层复制的积压——一个在链路层、一个在复制链路的两端。
- 讲发送缓冲与背压的那篇管单连接的发送侧背压;本篇管跨站点复制的累计欠量。
踩坑清单
- 拿延迟秒数当积压:量纲不成立。
- 把
NULL当 0:会得出"已经追平"的反向结论。 - 用
Seconds_Behind_Master这个已废弃写法:新版已改成 Source。 - 拿
sar的速率再除一次时间:rxkB/s/txkB/s已经是速率。 - 拿
/proc/net/dev的累计当区间量:那是自接口启动以来的总量。 - 只看延迟不看位点差:分不清"慢"和"欠"。
- 在没装数据库客户端或
sysstat/iproute2的环境上照敲:先装对应包。 - 改了限速不写持久化:重启就回到原值。
改完怎么验证(命令 + 期望)
- 重算一次积压:
SHOW REPLICA STATUS\G读两个位点 ⇒ 期望:Read_Source_Log_Pos的数值与Exec_Source_Log_Pos可相减,差值即积压(若差为负,先怀疑读错了字段); - 核对带宽口径:
sar -n DEV <间隔> <次数>与cat /proc/net/dev各读一次 ⇒ 期望:前者是速率(数量级与链路容量同阶),后者是累计大数(隔几秒再看会变大); - 跑一次反例:把延迟秒数除以带宽字节每秒 ⇒ 期望:得到一个约 10⁻⁴ 量级的数,它不是时间(量纲不成立);
- 看积压是在涨还是在消:两次位点差 ÷ 间隔各求一次 ⇒ 期望:两个速率相减得到积压增长速度,符号说明方向;
- 看限速是否生效:改完限速后再读一次速率 ⇒ 期望:
txkB/s落回你限的那个值附近; - 回滚可用:按改前记下的原值改回 ⇒ 期望:限速值与生效口径都回到原样。
- 点赞
- 收藏
- 关注作者
评论(0)