DPDK简介
DPDK是X86平台报文快速处理的库和驱动的集合,不是网络协议栈,不提供二层,三层转发功能,不具备防火墙ACL功能,但通过DPDK可以轻松的开发出上述功能。
DPDK的优势在于,可以将用户态的数据,不经过内核直接转发到网卡,实现加速目的。主要架构如图所示:
基本概念
在早期OVS的版本中,为缓解多级流表查表慢的问题,OVS在内核态采用Microflow Cache方法。Microflow Cache是基于Hash的精确匹配查表(O(1)),表项缓存了多级查表的结果,维护的是每条链接粒度的状态。Microflow Cache减少了报文进用户态查多级表的次数。一条流的首报文进入用户态查表后,后续的报文都会命中内核中的Microflow Cache,加快了查表速度。但是对于大量短流的网络环境来说,Microflow Cache命中率很低,导致大部分报文仍然需要到用户态进行多级流表查找,因此转发性能提升有限。
而后,为了解决Mircoflow Cache存在的问题,OVS采用Megaflow Cache代替了Mircoflow Cache。与Mircoflow Cache的精确Hash查表不同,Megaflow Cache支持带通配的查表,所以可减少报文至用户空间查表的次数。庾志辉的博客中当时分析的就是关于megaflow的数据结构和查表流程,相关内容不在此赘述,请看上文中的链接。但是,由于OVS采用元组空间搜索(下文介绍)实现Megaflow Cache的查找,所以平均查表次数为元组表的数量的一半。假设元组空间搜索的元组表链为m,那么平均查表开销为O(m/2)。Mircoflow Cache和Megaflow Cache查表开销对比为O(1)< O(m/2)。因此,Megaflow Cache相比于Mircoflow Cache,尽管减少了报文进用户空间查表的次数,但是增加了报文在内核态查表的次数。
为此,OVS当前版本采用Megaflow Cache+Microflow Cache的流Cache组织形式,仍保留了Microflow Cache作为一级Cache,即报文进入后首先查这一级Cache。只不过这个Microflow Cache含义与原来的Microflow Cache不同。原来的Microflow Cache是一个实际存在的精确Hash表,但是最新版本中的Microflow Cache不是一个表,而是一个索引值,指向的是最近一次查Megaflow Cache表项。那么报文的首次查表就不需要进行线性地链式搜索,可直接对应到其中一张Megaflow的元组表。
如果在短连接很多的场景下,microflow cache就会遇到很严重的性能问题,因为不仅要将miss事件上报userspace,还要在userspace查找openflow的多个table。所以就有了megaflow,用来替换microflow。megaflow cache采用模糊匹配的方法,这样就能大大减少miss事件。megaflow流表和openflow流表很类似,因为megaflow也支持根据报文任意字段匹配。但是megaflow相比openflow流表更简单,更轻量,
有两个原因:
a. megaflow不支持优先级,这样在查到megaflow流表后,就能立即停止查找,而不用担心错过更高优先级的流表而查找所有流表。
b. megaflow只使用一个流分类器,而不像openflow支持最多255个分类器。userspace下发megaflow流表时,需要将查找255个分类器用到的所有字段全部下发到megaflow。
尽管megaflow只有一个分类器,但是它需要为每个mask生成一个subtable,报文查找megaflow流表时,也需要查找多个subtable才能匹配到。如果有n个subtable表,最坏的情况下需要查找n次,这样看来基于分类器的megaflow相比于microflow,需要查找更多hash表。
ovs解决这个问题的方法是使用microflow作为第一级缓存,用来保存到megaflow流表的映射。这样的话,第一个报文查找microflow和megaflow失败后,上送miss事件到userspace查找openflow流表,然后下发megaflow流表,并在microflow建立映射,后续的报文就能在microflow命中,根据映射关系直接找到megaflow进行处理。
openflow
第三级流表,由最多255个table组成,每个table包含一个分类器,所有流表都插入到分类器中,具体实现可参考这里
三级流表的查找顺序
microflow在ovs+dpdk代码中,又被称为EMC(exact match cache)。
megaflow在ovs+dpdk代码中,又被称为dpcls(datapath classifer)。
从网卡接收到报文后,首先查找EMC表项,如果命中则直接执行action,如果miss则查找dpcls。
如果查找dpcls命中,则将规则插入EMC,并且执行action。还是miss的话,就查找openflow流表。
如果查找openflow命中,则将规则插入dpcls和EMC,并且执行action。还是miss的话,就丢包或者发给controller。
数据结构
主要看一下第一级和第二级流表的数据结构。
struct dp_netdev
代表一个datapath。在ovs+dpdk下,datapath也位于用户空间,所有netdev类型的网桥共享同一个datapath。
ports: 用于保存datapath下的所有端口,包括网桥的所有物理端口(不包含patch口)。
poll_threads: 用于保存datapath下的所有pmd线程相关信息。pmd线程的个数由如下逻辑决定:
//如果没有 pmd 类型的port,则pmd_cores为空
if (!has_pmd_port(dp)) {
pmd_cores = ovs_numa_dump_n_cores_per_numa(0);
//如果有pmd类型的port,并且指定了 pmd-cpu-mask,则按照指定的maks启动pmd线程
} else if (dp->pmd_cmask && dp->pmd_cmask[0]) {
pmd_cores = ovs_numa_dump_cores_with_cmask(dp->pmd_cmask);
//如果没指定 pmd-cpu-mask,则默认每个numa节点上启动一个pmd线程
} else {
pmd_cores = ovs_numa_dump_n_cores_per_numa(NR_PMD_THREADS);
}
struct dp_netdev_pmd_thread
表示一个pmd线程相关信息。
flow_cache: 这个就是EMC流表,每个pmd线程有一个EMC。
stats: 用来统计EMC和dpcls的命中和miss情况。
enum dp_stat_type {
//命中EMC流表次数
DP_STAT_EXACT_HIT, /* Packets that had an exact match (emc). */
//命中dpcls流表次数
DP_STAT_MASKED_HIT, /* Packets that matched in the flow table. */
//miss dpcls流表次数
DP_STAT_MISS, /* Packets that did not match. */
//查找dpcls过程中丢包个数,包括查找openflow流表后下发megaflow时超出流表最大限制的丢包
DP_STAT_LOST, /* Packets not passed up to the client. */
//查找dpcls时,查了subtable的个数
DP_STAT_LOOKUP_HIT, /* Number of subtable lookups for flow table
hits */
DP_N_STATS
};
struct cmap classifiers: 用来保存dpcls。每个port都对应一个dpcls。
struct emc_cache
用来保存8k个emc表项。
flow: 类型struct dp_netdev_flow,主要包含流表的action。
key: 类型struct netdev_flow_key,主要包含匹配项。key是从报文提取出来的内容,精确表示一条数据流。
struct dpcls
表示一个datapath分类器,使用TSS查找算法,将相同mask的流表插入同一个subtable。
in_port: 此分类器所属端口。
subtables: subtable数组,用于查找时遍历subtable。
struct dpcls_subtable
具体相同mask的流表集合。
rules: 保存流表。
mask: 类型struct netdev_flow_key,subtable的mask。
struct dpcls_rule
表示一个dpcls流表的匹配域。
struct dp_netdev_flow
表示一条datapath流表,包含匹配域和动作。
actions: 指向此流表的动作。
cr: 指向struct dpcls_rule,表示此流表的匹配域。
batch: 类型struct packet_batch_per_flow,将匹配的此流表的报文插入batch,用于批量处理报文,这也是dpcls的一个优化。