在跨国、高延迟、丢包环境下,开启BBR(Bottleneck Bandwidth and Round-trip propagation time)拥塞控制算法能够显著提升TCP连接的吞吐量,这对于很多业务,比如Elasticsearch(ES)的跨集群复制(Cross-Cluster Replication, CCR),听起来是个福音。然而,BBR的设计目标是尽可能跑满可用带宽,这种“侵略性”有时会带来副作用:它可能会挤压掉同一链路上其他同样重要、但可能流量模式不同的业务,甚至包括那些对延迟和稳定性要求极高的服务,比如CCR本身的心跳或小批量关键同步。
想象一下,你部署了ES CCR,希望能实时地将数据从一个地域复制到另一个地域。链路质量不佳,你明智地启用了BBR。一开始效果拔群,大批量数据同步速度飞快。但过了一段时间,你发现CCR偶尔会延迟增大,甚至出现短暂的连接中断警告,而同时链路上可能还有其他非紧急的大文件传输或者备份任务也在跑。问题来了:BBR在努力提升整体吞吐时,并没有内在机制去区分“哪个TCP流更重要”。它可能会为了最大化某个大文件传输的速率,而牺牲掉CCR那相对流量小但至关重要的心跳包或小批量更新的及时性。这就像高速公路上,所有车道都被追求极限速度的卡车占满了,小型急救车反而可能被堵在后面。
这时候,我们就需要更精细化的流量控制手段介入,告诉系统:“嘿,虽然BBR你干得不错,但请给这位VIP(CCR流量)让条快速通道,并保证它至少有xxx的带宽,并且在拥堵时优先通行!” 这就是iptables
和tc
(Traffic Control)登场的时刻。
本文将深入探讨如何在已经启用BBR的Linux服务器上,结合使用iptables
对特定流量(以ES CCR为例)进行标记,并利用tc
配置队列规则,实现流量整形(shaping)和优先级划分,确保CCR流量获得优先保障,同时避免过度压榨其他业务。
核心思路:标记 -> 分类 -> 调度
我们的策略分为三步:
- 识别与标记 (iptables): 首先,得能准确地识别出哪些网络数据包属于ES CCR流量。识别出来后,我们给这些数据包打上特殊的“标记”,就像给VIP车辆挂上特殊牌照。常用的标记方法是修改IP头部的TOS(Type of Service)字段,或者更现代的DSCP(Differentiated Services Code Point)值。
- 分类 (tc filter): 接着,在网络设备的出队(egress)方向,我们设置“交通规则”(
tc filter
),告诉系统如何根据数据包上的标记(DSCP值)将其归入不同的“车道”(tc class
)。 - 调度与整形 (tc qdisc/class): 最后,我们为不同的“车道”配置不同的“交通策略”(
tc qdisc
和tc class
)。比如,为VIP车道(CCR流量)设置高优先级、保证一定的最低通行速率(rate),甚至允许它在需要时“借用”其他车道的空闲带宽(ceil)。同时,可以为其他车道设置较低的优先级和不同的带宽限制。
Step 1: 使用iptables标记ES CCR流量
ES CCR通常使用Elasticsearch的Transport模块进行通信,默认端口范围是9300-9399。你需要根据你的实际部署情况确定CCR通信使用的具体端口和对端IP地址。
我们将在mangle
表里进行操作,因为它专门用于修改数据包元数据(如TOS/DSCP)。
选择DSCP值:
DSCP提供了一套标准化的标记值,用于区分服务等级。常用的类别有:
- EF (Expedited Forwarding, DSCP 46, 0x2e): 最高优先级,用于低延迟、低丢包、低抖动的服务,如VoIP。非常适合CCR的心跳和关键小包。
- AFxy (Assured Forwarding, DSCP classes 1-4, drop precedence 1-3): 提供不同等级的保证转发服务。例如:
AF41
(DSCP 34, 0x22): 高优先级,低丢弃概率。适合重要的业务数据。AF31
(DSCP 26, 0x1a): 中等优先级。AF21
(DSCP 18, 0x12): 较低优先级。
- CS (Class Selector): 向后兼容旧的TOS IP Precedence。
- BE (Best Effort, DSCP 0): 默认值,尽力而为。
对于CCR,我们可以考虑使用EF
或者高优先级的AF
值,比如AF41
。这里我们选用AF41
(十进制34,十六进制0x22)作为示例,因为它既保证了高优先级,也暗示了这是需要保证交付的数据。
iptables规则示例:
假设:
- 本地ES节点IP:
LOCAL_ES_IP
- 远端CCR集群节点IP/网段:
REMOTE_CCR_IP_OR_NET
- CCR通信端口:
9300
- 需要标记的DSCP值:
AF41
(34)
# 清理旧规则 (谨慎操作!)
# iptables -t mangle -F OUTPUT
# iptables -t mangle -F PREROUTING
# 规则1: 标记从本地发往远端CCR节点的数据包 (OUTPUT链)
# 匹配目标IP和目标端口
iptables -t mangle -A OUTPUT -p tcp -d $REMOTE_CCR_IP_OR_NET --dport 9300 -j DSCP --set-dscp-class AF41
# 或者使用数值: iptables -t mangle -A OUTPUT -p tcp -d $REMOTE_CCR_IP_OR_NET --dport 9300 -j DSCP --set-dscp 34
# 规则2: 标记从远端CCR节点接收到的应答流量 (PREROUTING链,如果需要对入向流量做处理或仅为完整性,但tc主要在出向生效)
# 注意:tc主要控制出向流量,标记入向流量主要是为了后续可能的状态跟踪或本地处理。
# 对于出向优先级的控制,主要依赖OUTPUT链的标记。
# 如果你的tc filter基于源IP/端口,可能不需要标记入向流量。
# 但如果后续策略依赖于双向标记(例如connmark),则需要。
# iptables -t mangle -A PREROUTING -p tcp -s $REMOTE_CCR_IP_OR_NET --sport 9300 -j DSCP --set-dscp-class AF41
# 可选:使用CONNMARK进行状态化标记 (更健壮,避免重复标记)
# 标记新连接
iptables -t mangle -A OUTPUT -p tcp -d $REMOTE_CCR_IP_OR_NET --dport 9300 -m state --state NEW -j CONNMARK --set-mark 0x1 # 标记一个独特的值,例如1
# 对已标记连接的所有后续包设置DSCP (OUTPUT链)
iptables -t mangle -A OUTPUT -m connmark --mark 0x1 -j DSCP --set-dscp-class AF41
# 对已标记连接的入向包也设置DSCP (PREROUTING链)
iptables -t mangle -A PREROUTING -m connmark --mark 0x1 -j DSCP --set-dscp-class AF41
# 保存连接标记 (重要!)
iptables -t mangle -A POSTROUTING -m connmark --mark 0x1 -j CONNMARK --save-mark
iptables -t mangle -A PREROUTING -m connmark --mark 0x1 -j CONNMARK --save-mark
验证标记:
你可以使用tcpdump
来抓包查看标记是否生效:
# 监听指定端口和IP的流量,并显示详细IP头信息 (包括TOS/DSCP)
tcpdump -i <your_network_interface> -n -v host $REMOTE_CCR_IP_OR_NET and port 9300
在输出的IP头信息中查找tos
字段,其值应该是你设置的DSCP值左移两位(因为TOS字段的低两位是ECN位)。例如,DSCP 34 (0x22) 对应的TOS值是 0x88
(34 << 2)。
Step 2 & 3: 使用tc配置流量控制
tc
是Linux下配置内核流量控制子系统的工具。我们需要选择一个合适的队列规则(qdisc)来附加到网络接口上,并在其下创建分类(class)和过滤器(filter)。
选择Qdisc:
HTB
(Hierarchical Token Bucket): 非常强大和灵活,允许创建层次化的带宽保证和借用机制。非常适合复杂的优先级和带宽分配场景。是本文重点介绍的选择。PRIO
(Priority): 简单地将流量分为几个优先级队列,高优先级队列完全处理完之前,低优先级队列不会被处理。实现简单,但可能导致低优先级流量“饿死”。CAKE
(Common Applications Kept Enhanced): 较新的、集成了AQM(Active Queue Management,如CoDel)和流量整形、优先级(基于DSCP)的“全能”qdisc。配置相对简单,效果通常很好,特别是配合BBR时。如果你的内核支持且需求不需HTB那么复杂的层次结构,CAKE
是极佳的选择。FQ_CoDel
(Fair Queuing Controlled Delay): BBR通常推荐配合FQ_CoDel
或CAKE
使用,因为它提供流间公平性并能有效控制延迟。但FQ_CoDel
本身不直接提供基于DSCP的显式优先级划分(虽然可以通过tc filter
配合其他qdisc实现)。
方案一:使用HTB实现精细控制 (更灵活)
假设我们的网络接口是 eth0
,总出口带宽是 100mbit
。
# 网络接口和总带宽
DEV=eth0
TOTAL_BW=100mbit
# 清理旧的qdisc配置 (危险! 确保你知道自己在做什么)
tc qdisc del dev $DEV root handle 1:
# 1. 在根上添加HTB qdisc,指定默认流量类别 (例如,classid 1:30)
tc qdisc add dev $DEV root handle 1: htb default 30
# 2. 创建根类别,代表总带宽
tc class add dev $DEV parent 1: classid 1:1 htb rate $TOTAL_BW ceil $TOTAL_BW
# 3. 创建子类别
# 类别10: CCR流量 (高优先级)
# - rate 20mbit: 保证至少20mbit带宽
# - ceil 90mbit: 最多可借用到90mbit (如果总带宽允许且其他类别空闲)
# - prio 1: 最高优先级 (数字越小,优先级越高)
tc class add dev $DEV parent 1:1 classid 1:10 htb rate 20mbit ceil 90mbit prio 1
# 类别20: 其他重要业务 (中优先级)
# - rate 30mbit: 保证30mbit
# - ceil 80mbit: 最多80mbit
# - prio 2: 中等优先级
tc class add dev $DEV parent 1:1 classid 1:20 htb rate 30mbit ceil 80mbit prio 2
# 类别30: 普通/尽力而为流量 (低优先级,默认类别)
# - rate 10mbit: 保证10mbit
# - ceil 50mbit: 最多50mbit
# - prio 3: 最低优先级
tc class add dev $DEV parent 1:1 classid 1:30 htb rate 10mbit ceil 50mbit prio 3
# 4. 创建过滤器,将标记过的流量导入对应类别
# 过滤器基于DSCP值 (AF41 = 34 = 0x22, TOS = 0x88)
# 使用u32过滤器匹配IP头的TOS字段 (DSCP << 2)
# 掩码0xfc用于忽略TOS字段的ECN位
tc filter add dev $DEV parent 1: protocol ip prio 1 u32 match ip tos 0x88 0xfc classid 1:10
# (可选) 添加其他过滤器,例如将SSH流量导入中优先级类别 (假设SSH使用DSCP CS2 = 16 = 0x10, TOS = 0x40)
# tc filter add dev $DEV parent 1: protocol ip prio 2 u32 match ip tos 0x40 0xfc classid 1:20
# 注意:未被任何过滤器匹配的流量将进入 'htb default 30' 指定的默认类别 1:30
# 5. (推荐) 在每个叶子类别下添加一个流公平队列 (如fq_codel),提升类内公平性和管理延迟
tc qdisc add dev $DEV parent 1:10 handle 10: fq_codel
tc qdisc add dev $DEV parent 1:20 handle 20: fq_codel
tc qdisc add dev $DEV parent 1:30 handle 30: fq_codel
方案二:使用CAKE实现 (更简洁,需内核支持)
CAKE
内置了对DSCP的支持,可以通过diffserv
模式自动映射DSCP值到不同的优先级“tin”。常用的模式有diffserv3
, diffserv4
, diffserv8
。
diffserv8
模式提供了8个优先级tin,并将标准的DSCP值映射到这些tin中,通常能满足大部分需求。AF41
通常会被映射到较高的优先级tin。
# 网络接口和总带宽
DEV=eth0
TOTAL_BW=100mbit
# 清理旧的qdisc配置
tc qdisc del dev $DEV root handle 1:
# 添加CAKE qdisc,设置总带宽,并启用diffserv8模式
tc qdisc add dev $DEV root handle 1: cake bandwidth $TOTAL_BW diffserv8
# (可选) 如果需要更激进的优先级区分,可以考虑 'besteffort' 模式
# 这会将EF, VA (Voice-Admitted), CS5-7 放入最高优先级tin
# CS4, AF4x, CS3, AF3x, CS2, AF2x, CS1, AF1x, TOS4, TOS1 放入第二tin
# 其他放入最低tin
# tc qdisc replace dev $DEV root handle 1: cake bandwidth $TOTAL_BW diffserv8 besteffort
使用CAKE
时,你只需要确保iptables
正确设置了DSCP标记,CAKE
会自动根据diffserv8
(或其他选定模式)的映射规则来处理优先级。你无需手动创建class和filter。简单是简单,但失去了HTB
那种对每个类别的带宽进行精确rate/ceil
控制的灵活性。
如何选择HTB还是CAKE?
- 如果你的内核支持
CAKE
(>= Linux 4.19),并且diffserv
模式提供的优先级分层能满足你的需求(即你信任它对DSCP的标准映射),且不需要对每个优先级类别做非常精细的带宽保证(rate)和上限(ceil)控制,那么CAKE
因其简洁性和内置的AQM优化,是现代Linux系统上配合BBR的首选。 - 如果你需要非常复杂的层次结构,或者需要对每个业务类别设置非常具体的最低保证带宽和最高可借用带宽,或者你的内核版本较旧不支持
CAKE
,那么HTB
依然是强大可靠的选择。记得在HTB叶子节点加上fq_codel
来获得更好的类内公平性和延迟控制。
BBR与流量整形/QoS的交互
一个常见的问题是:tc
做的流量整形会不会干扰BBR的带宽探测?
- 会。 当
tc
设置的ceil
(HTB)或bandwidth
(CAKE)低于实际物理链路带宽时,tc
成为了新的瓶颈。BBR会探测到这个由tc
人为制造的瓶颈,并将其视为链路容量。这通常是我们期望的行为,因为我们就是想控制应用层面的总带宽消耗。 rate
vsceil
: 在HTB中,rate
是保证带宽,ceil
是最大允许带宽(可借用)。BBR的探测结果会受到ceil
的限制。设置合理的rate
和ceil
非常重要,rate
总和不应超过总带宽,ceil
应反映你希望该类别在理想情况下能达到的最大速率,但也不能超过总带宽。- AQM的重要性: BBR与AQM(如CoDel, FQ_CoDel, CAKE内置的)配合工作效果最佳。AQM可以智能地管理队列,减少缓冲区膨胀(bufferbloat),给BBR提供更准确的RTT和带宽信号。这就是为什么推荐在HTB叶子节点加
fq_codel
,或者直接使用CAKE
的原因。
通过tc
整形,我们实际上是在告诉BBR:“虽然物理链路可能更宽,但你(或者某个类别的流量)能用的带宽就是这么多。” 而通过优先级设置,我们确保了即使在tc
制造的瓶颈处发生拥堵,CCR流量也能优先通过。
监控与验证
配置完成后,务必进行监控和验证:
检查
tc
配置和统计:# 查看qdisc配置 tc qdisc show dev $DEV # 查看class配置和统计 (发送/丢弃的包数、字节数等) tc -s class show dev $DEV # 查看filter配置 tc filter show dev $DEV
关注高优先级类别的丢包情况(
dropped
计数),理想情况下应该为0或非常低。检查
iptables
计数器:iptables -t mangle -L -v -n
确认标记规则的
pkts
和bytes
计数器在增长,表明流量被正确匹配和标记。业务层面监控:
- 持续监控ES CCR的延迟(
elasticsearch_ccr_stats.read_exceptions
,elasticsearch_ccr_stats.operations_written
,elasticsearch_ccr_stats.time_since_last_read_millis
等指标)。 - 观察其他业务的性能是否受到不可接受的影响。
- 进行压力测试,模拟高负载场景,看优先级机制是否按预期工作。
- 持续监控ES CCR的延迟(
调整参数: 根据监控结果,可能需要调整HTB的
rate
/ceil
/prio
值,或者确认CAKE
的diffserv
模式是否符合预期。这是一个需要根据实际流量模式和业务需求反复调优的过程。
潜在问题与注意事项
- CPU消耗: 复杂的
tc
规则(尤其是HTB和复杂的过滤器)会消耗一定的CPU资源。在高流量场景下需要关注服务器CPU负载。CAKE
通常比HTB
+fq_codel
更高效。 - DSCP标记的端到端有效性: 你在服务器上设置的DSCP标记,在穿越公共互联网时,很可能被中间路由器忽略或重置。但是,在你自己控制的网络内部(例如,数据中心之间通过专线或VPN连接),或者直接点对点的连接上,DSCP标记是有效的。即使在公网上被重置,它至少能在你控制的出口设备(
tc
生效的地方)保证本地优先级。 - 精确的带宽估计:
HTB
的rate/ceil
或CAKE
的bandwidth
设置需要基于对链路可用带宽的相对准确估计。设置过高可能导致整形无效,设置过低则浪费带宽。 - 配置持久化:
iptables
和tc
规则默认在重启后会丢失。你需要使用iptables-save
/iptables-restore
以及相应的tc
脚本管理工具(如tc-script
或写入网络启动脚本)来确保持久化。
结论
BBR是提升高延迟、有损链路上TCP吞吐量的利器,但其“尽力而为跑满带宽”的特性可能威胁到关键业务的服务质量。通过结合iptables
对目标流量(如ES CCR)进行DSCP标记,并利用tc
配置HTB
或CAKE
等队列规则进行流量整形和优先级调度,我们可以有效地解决这个问题。
这种“标记+分类+调度”的策略,赋予了我们在BBR带来的高吞吐基础上,叠加精细化QoS控制的能力,确保像ES CCR这样的关键服务即使在网络拥堵时也能获得所需的带宽和低延迟保障,从而实现“速度”与“稳定”的平衡。记住,没有一劳永逸的配置,持续的监控和根据实际情况的调优是保障系统长期稳定运行的关键。