协议栈处理中的conntrack HASH查找/Bloom过滤/CACHE查找/大包与小包/分层处理风格

1.路由CACHE的优势与劣势

分级存储体系已经存在好多年了,其精髓在于“将最快的存储器最小化,将最慢的存储器最大化”,这样的结果就使资源利用率的最大化,既提高了访问效率,又节省了资源。这是所有的CACHE设计的基本原则。

对于内存访问,几乎所有的CPU都内置了一级cache,二级cache,亲和力好的几个核心甚至设计了三级cache乃至四级cache,然后才是物理
内存,然后是经过精密优化的磁盘交换分区,最后是远程的存储器,这些存储空间逐级变大,访问开销也逐级变大,构成了一个金字塔型的存储体系。如此的设计要
想获得最大的功效,其主要是利用了局部性原则,往大了说就是自然界普遍的列维飞行原则。这个原则不但在内存访问上有效,而且人类的文明就是这种列维飞行原
则构建起来的,我不想从南方古猿说到最后的北美移民潮以及西进运动,因此本段就此打住。
      
推而广之,协议栈的路由表就是为了查找而生,且不说Cisco的Express
Forward(CEF体系),单说路由cache就够了。早期的Linux(迄至2.6.39以往,不谈3.X),在系统路由表至上又构建了一个路由
cache,每一个cache表项都是一次成功的路由查询结果,内置一个超时过期时间。Linux路由cache设计的前提是:去往一个目标地址的数据包
会连续到来。这也是局部性原理的一种推广。但是和内存访问以及单一目的地的移民浪潮不同,Linux作为一台路由器并不是为一个数据流服务的,而
Linux的路由cache中保存的是SourceIP/DestinationIP值对,因此一条路由表中的项将会扩展出N条路由cache项:
1.1.1.0/24-192.168.1.254==>{(2.2.2.1,1.1.1.1,192.168.1.254),(2.2.2.2,1.1.1.1,192.168.1.254),(2.2.2.3,1.1.1.2,192.168.1.254),(2.2.2.3,1.1.1.10,192.168.1.254)...}

果同时过境的基于源IP/目标IP的二元组数据巨大的话(特别是在骨干路由器场景下),路由cache的数目将远远超过路由表项的数目,我在想,路由
cache相较于路由表的查询优势体现在哪里呢?难道会有如此高效的路由cache查询算法吗?如果有的话,为何不直接应用于最长前缀匹配的路由表查找
呢?很明确的是,路由表查找远远没有路由cache查找更严格。
      
路由cache的思想是好的,但是确实不适合软件实现,如果有TCAM硬件的话,或许可以实现并行的多维向量精确匹配,但对于软件实现,由于协议栈天生的
多CPU核心利用的弊端,最高效的做法就是分级hash了,而即便是最高效的多级hash实现也不是免费的午餐。如果考虑到异常流量,很容易将你的路由
cache表撑到爆,比如构造海量不同的源IP地址访问不同的目标IP地址即可。
       TCAM的原理就不讲了,网上资料多的是。

2.conntrack的hash查找

Netfilter
中有一个conntrack模块,它可以建立任意五元组的连接跟踪。但是如果来了一个数据包,就必须进行一次查找,以便将它关联到某一个连接跟踪上。这个
查找的效率高低,直接影响了这台设备的品质。查找算法很简单,首先根据五元组计算一个hash,然后在该hash值定为的冲突链表上遍历,直到找到一个精
确匹配的,如果没有则新建。
      
谈到hash,如果仅仅从理论上看,它是一个极其高效的时间/空间相平衡的算法,不考虑空间浪费,它可以经过常数的时间找到结果,另一个极端,它却退化成
了一个单纯的链表,查找时间复杂度和遍历一致。真实的hash算法的时间复杂度介于二者之间。因此Linux在实现conntrack的时候,本应该像
Trie路由树那样,动态调整hashsize的大小,即进行适当的rehash操作,以将每条冲突链表上的元素的平均数量保持在一个确定的范围内,这样
就可以保持良好的伸缩性和可扩展性,而你的代价仅仅是消耗了内存。但是Linux似乎没有这么做。
      
屡次想优化nf_conntrack的查找性能,但总是受制于下面一个理想,即“理想情况下,conntrack的查找仅仅需要做一次hash计算,然后
遍历一个元素或者个位数个元素的链表”,如果实现了这个理想,还用费劲优化吗?我假设冲突链表的平均元素个数为10,那么只要10000个hash桶,就
能容纳10*10000个连接,效率已经很高了。但是理想毕竟只是理想!hash值计算的输入是五元组,算法是固定的,因此如果这个hash算法不够好的
话,计算结果的散列程度和输入是高度相关的,理论和事实均证明,涉及到查找的hash算法都无法做到理想情况,要想使得hash的输出和输入无关,就要采
用对称密钥学中的操作概念:置换,替换,混淆,扩散等,可以参考一下DES/AES的操作步骤,就可以看到这依然不是一顿免费的午餐!
       hash查找需要的是高效,如果一个算法针对输入的散列程度已经足够好了,那就到此为止了。如果散列程度很不好,那么冲突链表的长度就会有的特别长有的特别短,这对于一个公平调度系统,对性能的影响是很大的。

3.基于Bloom过滤的转发表


果说Linux的路由cache必然下课,那么总得有个替代方案了。事实上,我并不仅针对Linux,而是在讨论一般场景。只要是软件实现的路由
cache,必然要考虑cache查找的效率问题。cache仅仅在条目数目固定且数目很小的前提下,才会体现出对慢速路径的优势,否则剩下的就只是思想
了。
       如果扯到硬件,那怎么折腾都是OK的,但是此时我肯定不谈硬件。我希望使用纯软件来优化转发效率。
       记住一件事是重要的,那就是:你不能将整体的每一个部分的性能都提升,正如永动机不可能一样,你必须将不重要的部分能力倾注到需要优化的部分。

优化的基准就是,区分一个快速路经和慢速路径,首先查找快速路经,若失败则进入慢速路径查询,同时将结果加入快速路经。快速路经很快,前提是它是排它的,
容量小且有限的,就像罗马共和国时期的公民权一样,是要靠奋斗而取得的,到了卡拉卡拉时期,公民权成了既得权,优势自然就没了。使用Bloom可以做到传
统路由cache的高效替代方案。
       对每一个下一跳维护一个固定大小(但是运行期可以动态调整)的Bloom过滤器,数据包到来,首先遍历所有下一跳的Bloom过滤器,结果无非有三种:
a.只有一个Bloom过滤器返回值为1;
b.有多个Bloom过滤器的返回值为1;
c.没有一个Bloom过滤器的返回值为1;

于a而言,直接发往那个下一跳即可,对于b而言,说明存在False
Positive,此时说明凡是返回1的Bloom过滤器都有可能指示正确的结果,要么退回慢速路径,要么在所有可能的结果之间广播,对于c而言,此时必
须退回到慢速路径。请注意,我们当然希望结果是a,此时的计算非常之快,N个hash计算即可,至于计算方式,可以在一个处理器串行,也可以多个处理器并
行。
      
对于Bloom过滤器而言,为了方便删除元素,故使用带有counter的int型占位而不是仅仅是一个表示0和1的bit位。为了防止导致False
Positive的结果一直会False
Positive,所有的Bloom过滤器在内存中都要有一个后备存储,保存元素的链表,每间隔一段时间,在后台更新hash算法,进行Bloom的
update操作。我并不赞成再引入新的层次,比如再引入一个cache层,因为那会增加维护量,需要进行复杂性管理。解铃还需系铃人,既然为了避免同一
IP地址对的计算结果相同,那就改变算法而不是引入新层,在同一层次,代偿是不必要的。
       总而言之,Bloom过滤器的hash算法一定要保持精巧,所占空间一定要小巧。

4.大包还是小包

OpenVPN在虚拟网卡模拟了巨型帧,为了提高加密/解密的吞吐和减少系统调用开销。但是将巨型帧用于真实的物理链路一定好吗?
       小包灵活,这也是分组交换的真谛,那么大包呢?尾大不掉,一个无限大的包就是电路交换流了,它会长期占据链路。就像集卡或者挖掘机一样。好在现实中使能巨型帧的链路它真的有能力传输巨型帧(说明它的路比较宽!),这就没有什么问题。

但是,如果拥有很宽的路,就一定要传输巨型帧吗?如果跑小帧岂不效率更好?双向10车道对于跑集卡是没有什么问题,但是跑轿跑估计更好,如果非要运货,那
么运输机的吞吐量虽然不比货轮,但是延时却小很多。事实上,在一段出发后的链路上,即便你传输了巨型帧,也不保在途中被拆分。因此基于能耗,分组交换效
率,分片/重组开销考虑,巨型帧算是没有什么优势了,我之所以在OpenVPN中模拟了巨型帧,是因为在OpenVPN链路上我可以完全控制一切,虽然这
需要途径物理网卡以及虚拟网卡的数据帧均是巨型帧,但是我能通过测试证明,针对这些巨型帧的分片/重组开销远远大于OpenVPN的小包加密/解密以及系
统调用开销。
      
那么,巨型帧的初衷是什么?仅仅为了抵消掉分组交换的好处??事实上,这是针对主机的优化,特别是运行Windows主机,这种主机一般都是处在网络的边
缘,作为端到端的终点处理,因此对于到来的数据帧,如果中断过于频繁,势必会降低应用层的处理性能,毕竟总的资源是有限的!而巨型帧可以减少中断的数量。

我曾经说,巨型帧是合时宜的,此话也不绝对,对于高性能单一链路而言,这是对的。对于途径窄带链路的数据网络而言,巨型帧平添了开销,且在最后一公里的链
路上堵路。中间节点的分片/重组可能并不仅仅一次,只要需要分析协议头的地方,均需要重组分片,比如状态NAT,比如防火墙,比如数据流分类...对于中
断的频度,目前的千兆卡,万兆卡均可以在网卡芯片内进行分片,重组,然后再中断CPU,同时也可以积累数据帧到一定量,然后中断CPU一次,接下来改中断
为轮询,细节请参看Intel IGB的e1000e驱动程序readme。

5.协议栈的分层处理风格

协议栈的设计是分层的,这并不意味着协议栈的实现也必须是分层的。

早期的实现,或者受早期实现影响的实现,比如UNIX的流模块,比如Windows的NDIS框架,都是严格按照分层原则实现的,这种实现的优势是调用者
可以在任何既有的点插入任何处理逻辑,不用显式的设置HOOK点,对于Windows的NDIS框架,只要你调用的API复合NDIS框架的约定,过滤驱
动可以任意实现。不仅仅在内核态处理,即便是socket层次,Windows也提供了SPI的LSP机制,除了大家都认可的BSD
socket接口,TCP实现,UDP实现,IP实现等是内置的外,其它的你都是可以任意外接的,即便是TCP/IP栈,也不是必须的,如果你看网卡的属
性,你会发现TCP/IP协议是可以单独安装卸载的,如果你安装了VMWare,那么就会自动安装VMWare桥接协议。总之,一切都是可以灵活安装的,
没有内置的HOOK点,你可以任意在层间插入自己的处理模块。
      
这种设计旨在方便开发者实现自己的逻辑,开发者只需要了解相关接口就可以在任意位置插入自己的逻辑,开发者甚至可以根本就不懂网络协议栈处理逻辑。当然,
这种设计的缺点也是显而易见的,由于只开放了相关的API而并未开放细节,那么你便无法对未开放但是你真的需要的接口进行调用,因此开发者很难实现协议栈
层次之间的任意组合。
      
Linux的协议栈设计就完全不是分层的,而是完全基于回调的,它并不区分协议处理,小端口驱动等,由于所有的回调都是注册好排好序的,因此如果你想在两
个回调之间插入一个HOOK,你就必须先解除之前的链接关系。Linux采用了另外一种方式来处理这种情形,它允许开发者自行组织各个层次之间的相互调
用。比如你可以在一个hard_xmit回调函数内部调用另一个hard_xmit回调,你也可以在一个recv里面调用xmit或者任意的xmit..
这种任意组合是递归的,看似混乱,实则灵活。你会发现,Linux的bridge,bonding,vlan都是这种方式实现的,这些机制并没有像
NDIS那样插入一个过滤层NDIS驱动,而是直接组合各种xmit。Linux的这种实现风格要求开发者对网络协议栈处理逻辑细节十分熟悉,他们在一个
函数中拿到的是一个拥有网络协议栈语义的数据单元而不仅仅是一段buffer。
      
除了协议层的边界,在协议层处理的内部,Linux定义了若干个HOOK点,Netfilter是一个很重要的框架,基于它可以实现非常棒的防火墙。注意
Netfilter并非仅仅用于防火墙,而是协议栈内置的一套框架,它可以实现任意的数据包QUEUE,STOLEN等,在此基础上可以实现IPSec
VPN而无需插入任何层次。美中不足的是,Linux对于socket的HOOK机制比较少。如果你想HOOK住connect操作,就必须在
OUTPUT chain上去match TCP的syn标志...
       以负载均衡的实现为例子,对于NDIS,需要实现一个中间层过滤驱动,而对于Linux,除了IPVS,使用bonding的lb模式也是很方便的。

6.大便骑沟便沟内

针对早期的水冲式公共厕所,此处从略...几种答案,几多理由。

时间: 2024-10-19 21:32:31

协议栈处理中的conntrack HASH查找/Bloom过滤/CACHE查找/大包与小包/分层处理风格的相关文章

一个Netfilter nf_conntrack流表查找的优化-为conntrack增加一个per cpu cache

独悲需要忍受,快乐需要分享对Linux协议栈多次perf的结果,我无法忍受conntrack的性能,然而它的功能是如此强大,以至于我无法对其割舍,我想自己实现一个快速流表,但是我不得不抛弃依赖于conntrack的诸多功能,比如state match,Linux NAT等,诚然,我虽然对NAT也是抱怨太多,但不管怎样,不是还有很多人在用它吗.       曾经,我针对conntrack查找做过一个基于离线统计的优化,其思路很简单,就使用动态的计算模式代替统一的hash算法.我事先会对经过该BOX

批处理中set截取字符详解

set截取字符详解  在批处理中,set的功能有点繁杂:设置变量.显示环境变量的名及值.做算术运算.等待用户的输入.字符串截取.替换字符串,是我们常用的命令之一. 在字符串截取方面,新手因为没能注意到偏移量的问题,很容易提取到错误的字符串,因此,特开此帖,详细解释 set 截取字符的用法. 我们先来看一个例子: set str=123456789 现在,我需要提取变量 str 中的第一个字符,那么,该怎么写命令呢? set var=%str:~1,1%?我想,这很可能是很多粗懂set用法的新手们

PO状态为“处理中”的处理方法

EBS中经常会出现PO提交审批后状态为"处理中"的情况,此时PO创建人无法打开,审批人也无法打开,工作流等查看也无异常,可以使用一下SQL处理再进行审批: --set serveroutput on size 100000DECLARE CURSOR POTORESET IS SELECT WF_ITEM_TYPE, WF_ITEM_KEY, PO_HEADER_ID, SEGMENT1, REVISION_NUM, TYPE_LOOKUP_CODE FROM PO_HEADERS_A

批处理中Copy与Xcopy命令的区别(转自:www.jbxue.com)

有关批处理脚本中,copy命令与xcopy命令在用法上的一些区别.原文地址:http://www.jbxue.com/article/14536.html Copy与Xcopy的区别一,copy不能在有子目录存在的文件中拷贝文件的同时重命名此文件名说明,这里C:为根目录,bat为子目录),而xcopy能,不过会出现提示,当然可以加参数而不使它提示. C:\>copy c:\bat\hai.bat d:\bat\hello.bat提示:系统找不到指定的路径. 已复制 0 个文件.可以这样: C:\

关于cobol batch处理中的restart

批量文件处理中,如果因为某条数据错误导致了job abend,重新启动job再处理,几十分钟甚至几个小时的作业丢失对于分秒必争的batch cycle就是个噩梦! 尤其是对DB2和file同时处理的关键程序,job abend,DB2 roll back,但是VSAM file却是已写的已经写进去,已更新的已经更新,无法保持同步,再re-run,数据一片狼藉. 有一个很好的practice能够解决上述问题.注意,一下三部分是协同运作,从而保证DB2, VSAM, Sequential file保

批处理中set截取字符具体解释

set截取字符具体解释  在批处理中,set的功能有点繁杂:设置变量.显示环境变量的名及值.做算术运算.等待用户的输入.字符串截取.替换字符串,是我们经常使用的命令之中的一个. 在字符串截取方面,新手由于没能注意到偏移量的问题,非常easy提取到错误的字符串,因此,特开此帖,详解 set 截取字符的使用方法. 我们先来看一个样例: set str=123456789 如今,我须要提取变量 str 中的第一个字符,那么,该怎么写命令呢? set var=%str:~1,1%?我想,这非常可能是非常

异常处理中throws和throw的区别?

异常处理中throws和throw的区别: 1.作用不同:throw用于程序员自行产出并抛出异常:throws用于声明在该方法内抛出了异常 2.使用的位置不同:throw位于方法体内部,可以作为单独语句使用:throws必须跟在方法参数列表的后面,不能单独使用. 3.内容不同:throw抛出一个异常对象,且只能是一个:throws后面跟异常类,而且可以有多个 原文地址:https://www.cnblogs.com/suger-4/p/12192418.html

14.查找概论与顺序查找

一.查找概论 1.查找:即根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素(或纪录).若表中不存在关键字等于给定值的纪录,则称查找不成功,此时查找的结果可给出一个"空"纪录或"空"指针.另外,查找表按照操作方式来分有两大种:静态查找表和动态查找表. (1)查找表(Search table):是由同一类型的数据元素(或纪录)构成的集合: (2)关键字(Key):是数据元素中某个数据项的值,又称为键值,用它可以标志一个数据元素,也可以标志一个纪录的某个数

2. C#数据结构与算法 -- 查找算法(顺序查找,哈希查找,二分查找(折半),索引,二叉)

1. 顺序查找算法 ===================================================== 算法思想简单描述: 最突出的查找类型就是从记录集的开始处顺次遍历每条记录,直到找到所要的记录或者是 到达数据集的末尾.这就是所谓的顺序查找.顺序查找(也被称为线性查找)是非常容易实现 的.从数组的起始处开始,把每个访问到的数组元素依次和所要查找的数值进行比较.如果找 到匹配的数据项,就结束查找操作.如果遍历到数组的末尾仍没有产生匹配,那么就说明此数 值不在数组内. ==