弹性下一跳组¶
弹性组是一种下一跳组,旨在最大限度地减少在构成下一跳的组成部分和权重发生变化时,流量路由的中断。
弹性哈希组背后的思想最好通过与传统的、使用 RFC 2992 中描述的哈希阈值算法的多路径下一跳组进行对比来解释。
为了选择下一跳,哈希阈值算法首先为组中的每个下一跳分配一个哈希范围,然后通过比较 SKB 哈希值与各个范围来选择下一跳。当从组中删除下一跳时,将重新计算范围,这会导致将部分哈希空间从一个下一跳重新分配给另一个下一跳。RFC 2992 如此说明:
+-------+-------+-------+-------+-------+
| 1 | 2 | 3 | 4 | 5 |
+-------+-+-----+---+---+-----+-+-------+
| 1 | 2 | 4 | 5 |
+---------+---------+---------+---------+
Before and after deletion of next hop 3
under the hash-threshold algorithm.
请注意,下一跳 2 如何放弃部分哈希空间以支持下一跳 1,以及 4 如何放弃以支持 5。虽然之前的分配和新的分配之间通常会有一些重叠,但某些流量会更改它们解析到的下一跳。
如果多路径组用于在多个服务器之间进行负载均衡,则此哈希空间重新分配会导致一个问题,即来自单个流的数据包突然最终到达不期望它们的服务器。这可能导致 TCP 连接被重置。
如果多路径组用于在同一服务器的可用路径之间进行负载均衡,问题在于沿途的不同延迟和重新排序会导致数据包以错误的顺序到达,从而导致应用程序性能下降。
为了缓解上述流量重定向,弹性下一跳组在哈希空间与其组成的下一跳之间插入另一层间接层:哈希表。选择算法使用 SKB 哈希值选择一个哈希表桶,然后读取该桶包含的下一跳,并将流量转发到那里。
这种间接性带来了一个重要特性。在哈希阈值算法中,与下一跳关联的哈希范围必须是连续的。使用哈希表,哈希表桶和各个下一跳之间的映射是任意的。因此,当删除下一跳时,保存它的桶只需重新分配给其他下一跳
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|1|1|1|1|2|2|2|2|3|3|3|3|4|4|4|4|5|5|5|5|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
v v v v
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|1|1|1|1|2|2|2|2|1|2|4|5|4|4|4|4|5|5|5|5|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Before and after deletion of next hop 3
under the resilient hashing algorithm.
当组中下一跳的权重被更改时,可以选择当前未用于转发流量的桶的子集,并使用这些桶来满足新的下一跳分配需求,保持“繁忙”的桶不变。这样,理想情况下,已建立的流量会像下一跳组更改之前一样,通过相同的路径转发到相同的端点。
算法¶
简而言之,该算法的工作原理如下。根据其权重和哈希表中的桶数,每个下一跳都应获得一定数量的桶。根据源代码,我们将此数字称为下一跳的“需求计数”。如果发生可能导致桶分配更改的事件,则会更新各个下一跳的需求计数。
桶数少于其需求计数的下一跳称为“权重不足”。那些拥有更多桶的则称为“权重超额”。如果组中没有权重超额(因此也没有权重不足)的下一跳,则称其为“平衡”。
每个桶都维护一个上次使用计时器。每次通过桶转发数据包时,此计时器都会更新为当前的 jiffies 值。弹性组的一个属性是“空闲计时器”,它是桶必须不受流量影响的时间量,才能被视为“空闲”。非空闲的桶是繁忙的。
在为下一跳分配需求计数后,会运行“维护”算法。对于桶
没有分配下一跳,或者
其下一跳已被删除,或者
空闲且其下一跳权重超额,
维护将桶引用的下一跳更改为权重不足的下一跳之一。如果以这种方式考虑所有桶之后,仍然存在权重不足的下一跳,则会安排在未来某个时间再次运行维护。
可能没有足够的“空闲”桶来满足所有下一跳的更新需求计数。弹性组的另一个属性是“不平衡计时器”。可以将此计时器设置为 0,在这种情况下,表将保持不平衡状态,直到空闲桶出现,可能永远不会出现。如果设置为非零值,则该值表示允许表保持不平衡状态的时间段。
考虑到这一点,我们在上述条件列表中添加一个项目。因此,桶
其下一跳权重超额,并且表保持不平衡的时间量超过了不平衡计时器(如果该计时器非零),
... 也会被迁移。
卸载和驱动程序反馈¶
当卸载弹性组时,在下一跳之间分配桶的算法仍然是软件中的算法。驱动程序通过以下三种方式收到下一跳组更新的通知
类型为
NH_NOTIFIER_INFO_TYPE_RES_TABLE
的完整组通知。这在组创建后和首次填充桶时使用。类型为
NH_NOTIFIER_INFO_TYPE_RES_BUCKET
的单桶通知,用于通知已建立的组内的单个迁移。预替换通知,
NEXTHOP_EVENT_RES_TABLE_PRE_REPLACE
。这在替换组之前发送,是驱动程序在将任何内容提交到硬件之前否决该组的方式。
某些单桶通知是强制的,如通知中的“force”标志所示。这些用于例如与桶关联的下一跳被删除,并且桶确实必须迁移的情况。
驱动程序可以通过返回错误代码来覆盖非强制通知。这种情况的用例是驱动程序通知硬件应该迁移一个桶,但硬件发现该桶实际上已被流量命中。
硬件报告桶繁忙的第二种方式是通过 nexthop_res_grp_activity_update()
API。以这种方式识别为繁忙的桶被视为流量命中了它们。
卸载的桶应标记为“卸载”或“陷阱”。这是通过 nexthop_bucket_set_hw_flags()
API 完成的。
Netlink UAPI¶
弹性组替换¶
弹性组使用 RTM_NEWNEXTHOP
消息配置,方式与其他多路径组相同。以下更改适用于 netlink 消息中传递的属性
NHA_GROUP_TYPE
对于弹性组,应为
NEXTHOP_GRP_TYPE_RES
。
NHA_RES_GROUP
包含特定于弹性组的属性的嵌套。
NHA_RES_GROUP
有效负载
NHA_RES_GROUP_BUCKETS
哈希表中的桶数。
NHA_RES_GROUP_IDLE_TIMER
以 clock_t 为单位的空闲计时器。
NHA_RES_GROUP_UNBALANCED_TIMER
以 clock_t 为单位的不平衡计时器。
获取下一跳¶
获取弹性下一跳组的请求使用 RTM_GETNEXTHOP
消息,方式与其他下一跳获取请求完全相同。响应属性与上面引用的替换属性匹配,但 NHA_RES_GROUP
有效负载将包含以下属性
NHA_RES_GROUP_UNBALANCED_TIME
以 clock_t 为单位,弹性组处于不平衡状态的时间。
获取桶¶
不带 NLM_F_DUMP
标志的消息 RTM_GETNEXTHOPBUCKET
用于请求单个桶。获取请求中识别的属性是
NHA_ID
桶所属的下一跳组的 ID。
NHA_RES_BUCKET
包含特定于桶的属性的嵌套。
NHA_RES_BUCKET
有效负载
NHA_RES_BUCKET_INDEX
弹性表中的桶的索引。
桶转储¶
带有 NLM_F_DUMP
标志的消息 RTM_GETNEXTHOPBUCKET
用于请求转储匹配的桶。转储请求中识别的属性是
NHA_ID
如果指定,则将转储限制为仅具有此 ID 的下一跳组。
NHA_OIF
如果指定,则将转储限制为包含使用此 ifindex 的设备的下一跳的桶。
NHA_MASTER
如果指定,则将转储限制为包含使用 VRF 中具有此 ifindex 的设备的下一跳的桶。
NHA_RES_BUCKET
包含特定于桶的属性的嵌套。
NHA_RES_BUCKET
有效负载
NHA_RES_BUCKET_NH_ID
如果指定,则将转储限制为仅包含具有此 ID 的下一跳的桶。
用法¶
为了说明用法,请考虑以下命令
# ip nexthop add id 1 via 192.0.2.2 dev eth0
# ip nexthop add id 2 via 192.0.2.3 dev eth0
# ip nexthop add id 10 group 1/2 type resilient \
buckets 8 idle_timer 60 unbalanced_timer 300
最后一个命令创建一个弹性下一跳组。它将有 8 个桶(这是一个异常低的数字,这里仅用于演示目的),当没有流量命中它至少 60 秒时,每个桶将被视为空闲,并且如果表保持不平衡 300 秒,则将强制使其平衡。
更改下一跳权重会导致桶分配发生变化
# ip nexthop replace id 10 group 1,3/2 type resilient
可以通过查看单个桶来确认这一点
# ip nexthop bucket show id 10
id 10 index 0 idle_time 5.59 nhid 1
id 10 index 1 idle_time 5.59 nhid 1
id 10 index 2 idle_time 8.74 nhid 2
id 10 index 3 idle_time 8.74 nhid 2
id 10 index 4 idle_time 8.74 nhid 1
id 10 index 5 idle_time 8.74 nhid 1
id 10 index 6 idle_time 8.74 nhid 1
id 10 index 7 idle_time 8.74 nhid 1
请注意,有两个桶的空闲时间较短。这些是在下一跳替换命令后迁移的桶,以满足下一跳 1 应获得 6 个桶而不是 4 个桶的新需求。
Netdevsim¶
netdevsim 驱动程序实现了弹性组的模拟卸载,并公开了 debugfs 接口,该接口允许将单个桶标记为繁忙。例如,以下命令将下一跳组 10 中的桶 23 标记为活动状态
# echo 10 23 > /sys/kernel/debug/netdevsim/netdevsim10/fib/nexthop_bucket_activity
此外,还可以使用另一个 debugfs 接口来配置下一次尝试迁移桶应该失败
# echo 1 > /sys/kernel/debug/netdevsim/netdevsim10/fib/fail_nexthop_bucket_replace
除了作为示例之外,netdevsim 公开的接口在自动化测试中也很有用,并且 tools/testing/selftests/drivers/net/netdevsim/nexthop.sh
利用它们来测试该算法。