NFD开发指南-3.Tables

tables 模块提供NFD的主要数据结构( data structures )。

转发信息库 (FIB,Forwarding Information Base )(第3.1节)用于将兴趣包转发到匹配数据( matching Data )的潜在源( potential source(s) )。它与IP的FIB表类似,只是它允许列出传出的 face 是一个 face 列表而不是单个 face

网络区域表The Network Region Table ,第3.2节)包含用于移动性支持( mobility support )的生产者区域名称的列表。

内容存储 (CS,Content Store ,第3.3节)是一个对数据包( Data packet )的缓存。为了满足将来请求相同数据的兴趣,将到达数据包尽可能长地放置在此缓存中。

兴趣表 (PIT,Pending Interest Table ,第3.4节)跟踪( track )向上游内容源转发的兴趣( Interest ),构造一条反向路劲,以便可以将数据向下游发送到其请求者。它还包含用于 环路检测测量目的 的最近满足的兴趣( recently satisfied Interests )。

Dead Nonce List (第3.5节)作为未决兴趣表的补充,以进行循环检测。

策略选择表The Strategy Choice Table ,第3.6节)包含为每个名称空间( namespace )选择的转发策略(第5节)。

转发策略使用 测量表The Measurements Table ,第3.7节)来存储有关名称前缀的测量信息。

FIBPIT策略选择表测量表 在其索引结构上有很多共性。为了提高性能并减少内存使用,我们设计了 名称树Name Tree 第3.8节)结构,用于在这四个表之间共享一个通用索引。

3.1 转发信息库(FIB)

转发信息库(FIB)用于将兴趣包转发到匹配数据的潜在源[9]。对于需要转发的每个兴趣,将在FIB上执行最长的前缀匹配( longest prefix match )查找,并且存储在找到的FIB条目中的传出 face 列表是转发( forwarding )的重要参考。

第3.1.1节概述了FIB的结构( structure )、语义( semantic )和算法( algorithm )。第3.1.2节介绍了NFD其余的部分是如何如何使用FIB的。FIB算法的实现将在3.8节中讨论。

3.1.1 结构和语义

图5 FIB及其相关的条目
  • FIB条目和下一跳记录

    FIB条目(nfd::fib::Entry)包含 名称前缀Name prefix )和 下一跳记录Nexthop record )的非空集合。FIB表中有某个前缀的FIB条目意味着,如果收到以该名称前缀开头的兴趣包,则可以通过此FIB条目中的下一跳记录所提供的 face 来找到匹配数据的潜在来源。

    每个下一跳记录(nfd::fib::NextHop)包含面向潜在内容源的传出 face 及其路由成本( cost )。 一个FIB条目最多可以包含一个朝向同一出口( outgoingface 的下一跳记录。在FIB条目中,下一跳记录按升序排序。路由成本是下一跳记录之间的相对成本,具体值为多少无关紧要。

    与RIB(第7.3.1节)不同,FIB条目之间没有继承( inheritance )。FIB条目中的下一跳记录列表是此FIB条目仅有的“有效”下一跳列表。

  • FIB

    FIB(nfd::Fib)是FIB条目的集合,按名称前缀索引。支持通常的插入、删除及完全匹配( exact match )操作。FIB条目可以在前向迭代器中以未指定的顺序进行迭代。

    最长前缀匹配算法(Fib::findLongestPrefixMatch)找到FIB条目,该条目应用于指导兴趣的转发。 它以名称作为输入参数。此名称应该是兴趣( Interest )中的名称字段。返回值是FIB条目,其名称前缀为:

    • (1)输入参数参数的前缀
    • (2)在满足条件(1)的结果中的的最长者,如果没有FIB条目满足条件(1),则返回NULL。

3.1.2 使用( Usage

FIB仅通过使用FIB管理协议来更新 ,该协议在NFD方面由FIB管理器( FIB manager )操作(第6.5节)。通常, FIB managerRIB Management(第7节)获取命令,RIB Management既接收手动配置( configured manually )或由应用程序注册的静态路由,也接收来自路由协议的动态路由。由于大多数FIB条目最终都来自动态路由,因此,如果网络具有少量的公告前缀,则FIB预计将包含少量条目。

FIB预计会相对稳定。FIB更新由RIB更新触发,而RIB更新又是由手动配置、应用程序启动或关闭以及路由更新引起的。在稳定的网络中,这些事件很少发生。但是,每个RIB更新都会导致大量FIB更新,因为一个RIB条目的更改可能会由于继承而影响其后代。

最长前缀匹配算法用于传入兴趣管道( incoming Interest pipeline )中的转发(第4.2.1节)。对于每个传入兴趣最多调用一次。

cleanupOnFaceRemoval函数(在daemon/table/cleanup.hpp中声明)是一个在 face 移除时用于FIB-PIT清理的函数。当一个 face 销毁( destroyed )后会调用此函数。它访问所有FIB和PIT条目,并删除FIB下一跳记录,PIT入记录和PIT出记录,这些记录指向已删除的 face 。丢失最后一条下一跳记录的FIB条目也将被删除。不同的是,丢失所有其入记录和出记录的PIT条目被保存在表中。FIB和PIT的清除在同一函数中执行,因为两个表都存储在NameTree中(第3.8节),因此此函数可以在一次NameTree枚举中清除两个表。

This is pending discussion in https://redmine.named-data.net/issues/3685#note-7.

3.2 网络区域表(Network Region Table)

网络区域表(nfd::NetworkRegionTable)用于移动性支持(第4.2.4节)。 它包含一组无序的生产者区域名称,这些名称取自NFD配置文件(第6.7.2节)。如果兴趣的转发提示( forwarding hint )中的任何委托名称( delegation name )是此表中任何区域名称的前缀,则表示兴趣已到达生产者区域,应根据其名称而不是委托名称进行转发。

3.3 内容存储(CS)

内容存储(CS)是用作数据包的缓存。转发管道( Forwarding pipeline ,第4节)将到达的数据包放在CS中,这样就可以满足将来请求相同数据的兴趣,而无需进一步转发。

CS提供了插入数据包,查找与兴趣匹配的缓存数据以及枚举缓存数据的过程。第3.3.1节描述了这些过程的语义及其用法。

CS在(nfd::cs::Cs)类中实现。该实现由两部分组成:查找表( lookup table )和缓存替换策略( cache replacement policy )。查找表(第3.3.2节)是CS条目的基于名称的索引,其中存储了缓存的数据包。缓存替换策略(第3.3.3节)负责将CS保持在容量限制之下。它单独维护一个清理索引,以便确定在CS满( full )时删除哪个条目。 NFD提供了多种缓存替换策略,包括优先级FIFO策略和LRU策略,可以在NFD配置文件中选择它们。

3.3.1 语义和使用(Semantic and Usage)

  • 插入(Insertion)

    不管是在 incoming Data pipleline (第4.3.1节)还是在 Data unsolicited pipleline (第4.3.2节)中,对于任意的数据包,只要 forwarding 可确保其完全符合要处理的数据包的条件,该数据包就会被插入CS(Cs::insert)中 。(例如,数据包不违反基于名称的范围控制[10])。

    在存储数据包之前,先评估准入策略( admission policy )。本地应用程序可以通过附加到数据包的NDNLPv2 [5] CachePolicy字段来提示接纳策略,这些提示被认为是建议性的。

    通过准入策略后,数据包( Data packet )将被存储下来,同时保存该数据包过时( stale )的时间点( time point ),并且不会去满足带有MustBeFresh Selector的兴趣( Interest )。

    CS配置有容量限制,此时将对其进行检查。如果此新数据包的插入导致CS超过容量限制,则缓存替换策略将驱逐过多的条目以使CS处于容量限制之下。

  • 查找(Lookup)

    CS提供了一个异步查找API(Cs::find)。 incoming Interest pipleline (第4.2.1节)使用传入兴趣( Interetst )来调用此API。搜索算法会将与兴趣( Interest )最匹配的数据包( Data packet )提供给 ContentStore hit pipleline (第4.2.3节),或者如果没有匹配项,则通知 ContentStore miss pipleline (第4.2.4节)。

  • 枚举和条目抽象(Enumeration and Entry Abstraction)

    可以通过正向迭代器( forward iterators )枚举 Content Store 。此功能未在NFD中直接使用,但在仿真环境中可能很有用。

    为了保持稳定的枚举接口,且仍允许替换CS实现,将迭代器取消引用( dereferenced )为nfd::cs::Entry类型,这是CS条目的抽象。这种类型的公共API允许调用者获取数据包,无论它是否是自发的,以及何时该数据包会过时。内容存储实现可以定义自己的具体类型,并在枚举期间转换为抽象类型。

3.3.2 查询表(Lookup Table)

Table 是一个有序的容器,用于存储具体的条目(nfd::cs::EntryImpl,条目抽象类型的子类)。该容器按具有隐式摘要( implicit digest )的数据名称排序。

隐式摘要计算会占用大量CPU。该表进行了优化,可以在大多数情况下避免隐式摘要计算,同时仍保证正确的排序顺序。

查找完全使用表来完成。NFD优化了查找过程(Cs::find*),以最大程度地减少预期情况下访问的条目数。在最坏的情况下,查找将访问所有以兴趣名称为前缀的条目。

尽管查找API是异步的,但当前实现会同步进行查找。

该表使用std::set作为基础容器,因为它在基准测试中表现良好。先前的CS实现使用 skip list,但其性能比std :: set差,这可能是由于算法复杂性和代码质量所致。

3.3.3 缓存替换策略(Cache Replacement Policy)

缓存替换策略将CS保持在其容量限制内。主要容量限制是根据缓存的数据包的数量来衡量的。选择此度量标准而不是选择数据分组大小,因为表索引的内存开销对于小数据包可能很重要,因此数据包大小度量标准将不准确。可以在NFD配置文件table.cs\_max\_packets项中配置此容量限制,也可以在模拟环境中通过Cs::setLimitAPI进行配置。 允许运行时更改容量限制。

NFD通过实现nfd::cs::Policy的多个子类提供多种策略实现 可以在NFD配置文件table.cs\_policy项中配置有效策略,也可以在模拟环境中通过Cs::setPolicyAPI进行配置。此配置只能在初始化期间应用,不允许在运行时更改。

  • 策略类API(Policy class API)

    nfd::cs::Policy是所有缓存替换策略实现的基类。

    容量限制存储在Policy类上,可以通过Policy::setLimit公共方法进行更改。策略实现必须提供基类中纯虚函数evictEntries的实现,以便基类Policy::setLimit公共方法通过驱逐足够的条目使CS重新回到新的容量限制下,来处理容量限制的降低。除了主要容量限制(称为“硬限制-hard limit ”)之外,策略还可以提供其他容量限制(例如证书的单独限制或基于数据包大小的限制)。

    为了使策略维持容量限制,它需要知道哪些数据包已添加到缓存及其访问模式。 因此,策略类公开了四个公共方法来接收这些信息:

    • 插入新条目后,将调用Policy::afterInsert
    • 在同一数据刷新现有条目后,将调用Policy::afterRefresh
    • Policy::beforeErase将在通过管理命令删除条目之前被调用(当前未使用);
    • 当通过查找找到某个条目并将其用于转发之前,将调用Policy::beforeUse

    这些公共方法调用相应的纯虚函数:doAfterInsertdoAfterRefreshdoBeforeErasedoBeforeUse。 这些纯虚函数以及evictEntries应该在子类中重写( overriden )。

    根据通过上述API收到的信息,策略会维护一个内部清除索引,该索引用于确定在CS超过容量限制时应清除哪个数据包。在每个策略实施中都定义了此内部清理索引的结构。它应该通过迭代器(nfd::cs::iterator)引用CS条目(存储在表中)。当策略决定逐出一个条目时,它应该发出beforeEvict信号来通知CS从表中删除该条目,然后删除该策略的内部清除索引中的相应项目。请注意,对于通过beforeEvict信号驱逐的条目,不会调用beforeErase

    每个函数实现时的建议步骤如下:

    • doAfterInsert中,策略决定是否接受新条目。如果接受,则应将新条目的迭代器插入内部清除索引;否则,将调用cs::Policy::evictEntries通知CS进行清理。然后,该策略应检查CS大小是否超过容量限制,如果是,则逐出一个条目(可能通过调用evictEntries函数);
    • doAfterRefresh中,策略可能会更新其清理索引,以处理同一数据再次到达;
    • doBeforeErase中,策略应删除其清理索引中的相应条目;
    • doBeforeUse中,策略可以更新其清除索引,以标记指定的条目满足了一个传入的兴趣。
    • evictEntries中,策略应逐出足够的条目,以使CS不会超过容量限制。
  • 优先级FIFO缓存策略(Priority FIFO cache policy)

    Priority-FIFO是默认的cs::Policy。优先级FIFO会在每次插入时逐出,因为其性能更可预测,否则,定期清除一批条目可能会导致数据包转发中的抖动。Priority-FIFO使用三个队列来跟踪CS中的数据包:

    • 未经请求的队列( unsolicited queue 包含具有未经请求的数据的条目;
    • 过时队列( stale queue包含具有过时数据的条目;
    • FIFO队列包含带有新数据的条目。

    在任何时候,一个条目完全属于一个队列,并且在该队列中只能出现一次。优先级FIFO保持每个条目所属的队列。

    这些字段与存储在队列中的Table迭代器一起,在Table和队列之间建立双向关系。

    变异操作Mutation operations )时必须保持这种关系:

    • 插入条目时,该条目将放置在表格中;
    • 逐出一个条目时,其表迭代器将从其队列的开头删除,并且该条目也从表中删除;
    • 当新条目变为陈旧时(由计时器控制)时,其表迭代器将从FIFO队列移至过时队列,并且条目上的队列指示符和迭代器也将更新;
    • 使用请求的数据更新主动/过期条目时,其表迭代器从主动/过期队列移至FIFO队列,并且条目上的队列指示符和迭代器也更新。

    尽管名为队列( queue ),但这里的队列并不是真正的先进先出队列,因为条目可以在队列之间移动(请参阅上面的变异操作)。移动条目时,其表迭代器将从旧队列中分离出来,并附加到新队列中。std::list用作基础容器,std::queuestd::deque不适合,因为它们无法有效地分离节点。

  • LRU缓存策略(LRU cache policy)

    LRU缓存策略实现了最近最少使用的缓存替换算法,该算法首先丢弃最近最少使用的项目。LRU每次插入都会被逐出,因为它的性能更可预测,否则,定期清除一批条目可能会导致数据包转发中的抖动。

    LRU使用一个队列来跟踪CS中的数据使用情况,表迭代器存储在队列中。在任何时候,使用或刷新条目时,其表迭代器都会重定位到队列的尾部。同样,当新插入一个条目时,其表迭代器会被推入队列的尾部。当需要逐出一个条目时,将从其队列的开头删除其表迭代器,并从表中删除该条目。

    队列使用boost::multi_index_container[11]作为基础容器,因为它在基准测试中表现良好。boost::multi_index_container::sequenced_index用于插入,更新使用和刷新,boost::multi_index_container::ordered_unique_index用于通过Table::iterator擦除。

3.4 兴趣表(PIT)

兴趣表(PIT)跟踪向上游内容源转发的兴趣,以便可以将数据向下游发送到其请求者[9]。它还包含用于 环路检测测量目的 的最近满足的兴趣。这种数据结构在NDN文献中称为 未决兴趣表pending Interest table ) ; 但是,NFD的PIT包含未满足的兴趣和最近满足的兴趣,因此“兴趣表”是一个更准确的术语,但缩写为“ PIT”。

PIT是PIT条目的集合,仅用于转发( forwarding ,第4节)。 3.4.1节介绍了PIT条目的结构和语义,以及转发如何使用它。3.4.2节介绍了PIT的结构和算法,以及转发如何使用它。PIT算法的实现在3.8节中讨论。

3.4.1 PIT条目(PIT Entry)

图6显示了PIT,PIT条目、入记录( in-records )、出记录( out-records )以及它们的关系。

图6 PIT及其相关的entities
  • PIT条目(PIT entry)

    PIT条目(nfd::pit::Entry)表示未决兴趣或最近满足的兴趣。如果两个兴趣包具有相同的名称( Name )和选择器( Selectors )[1],则它们是相似的( similar ),多个相似兴趣共享同一PIT条目。

    每个PIT条目均由一个兴趣标识。除名称( Name )和选择器( Selectors )外,此兴趣中的所有字段均无关紧要。

    每个PIT条目包含一个入记录( in-records )、一个出记录( out-records )和一个计时器( timer ),如下所述。另外,允许转发策略存储有关PIT条目,入记录和出记录的任意信息(第5.1.3节)。

  • 入记录(In record)

    入记录(nfd::pit::InRecord)表示兴趣的下游 facedownsream face )。下游 face 是内容的请求者 :兴趣来自下游( downstream ),且数据也会流向下游。

    入记录( in-record )中存储有下列信息:

    • face 的引用
    • 来自此 face 的最后一个兴趣包中的Nonce
    • 来自此 face 的最后一个兴趣包到达的时间戳( timestamp
    • 最后一个兴趣包

    ps: 上述提到的最后一个兴趣包,即为包含入记录的PIT条目中的最新插入的一个兴趣包

    incoming Interest pipeline (第4.2.1节)会插入或更新入记录( in-record )。当一个未决兴趣时被满足时,所有的入记录( 每个PIT条目中都会维护一个入记录列表,这边说的所有的入记录指的是被满足兴趣所在的PIT条目中保存的入记录列表 )都会被 incoming Data pipeline 删除(第4.3.1节)。

    在最后一个来自同个 face 的相同兴趣包到达后,经过LifetyLifetime,对应的入记录将过期。当所有的入记录都到期时,PIT条目也会到期。如果一个PIT条目包含至少一个未过期的入记录,则称其为未决。

  • 出记录(Out record)

    出记录(nfd::pit::OutRecord)表示兴趣的上游 faceupstream face ) 。上游 face 是潜在的内容来源:兴趣被转发到上游( upstream ),且而数据来自上游。
    出记录( out-record )中存储有下列信息:

    • face 的引用
    • 发送给此 face 的最后一个兴趣包中的Nonce
    • 发送给此 face 的最后一个兴趣包的时间戳( timestamp
    • Nacked字段:指示最后一个发出的( outgoing )的兴趣已被Nacked,此字段还记录了Nack的原因( Nack reason

    outgoing Interest pipeline (第4.2.5节)会插入或更新出记录( out-record )。当来自该 face 的数据满足未决兴趣时,出记录将由 incoming Data pipeline 删除(第4.3.1节)。

    发送最后一个兴趣包后,如果经过了LifetyLifetime,则记录过期( expires )。

  • 计时器(Timer)

    每个PIT条目都有一个计时器,即到期计时器( expiry timer )。该计时器由 forwarding pipeline 使用(第4节),并且在PIT条目到期时触发(第4.2.1节)。

3.4.2 PIT

PIT(nfd::Pit)是一个包含PIT条目的表,由 tuple 索引。支持通常的插入和删除操作。 Pit::insert方法首先查找具有相似兴趣的PIT条目,并仅在不存在的情况下插入一个。没有完全匹配的单独方法,因为 forwarding 不需要插入PIT条目就可以确定它的存在。PIT是不可迭代的,因为 forwarding 不需要这样做。

数据匹配算法(Pit::findAllDataMatches)查找数据包可以满足的所有兴趣。它以数据包作为输入参数。返回值是此数据包可以满足的PIT条目的集合。此算法不会删除任何PIT条目。

cleanupOnFaceRemoval函数是去除 face 时的联合FIB-PIT清理功能。 有关更多信息,请参见第3.1.1节。

3.5 Dead Nonce List

Dead Nonce List 是为 循环检测 目的 补充PIT的数据结构

2014年8月,当InterestLifetime较短时(Bug#1953),我们发现了一个持久循环问题。循环检测以前仅使用存储在PIT条目中的随机数。如果在InterestLifetime内未满足兴趣,则将删除PIT条目。当网络包含一个延迟时间长于InterestLifetime的循环时,由于在PIT条目在Interest循环返回之前就消失了,因此无法检测到围绕此循环的循环Interest。

对于此持久循环问题的朴素解决方案是将PIT条目保留更长的时间。但是,这样做的内存消耗将太高,因为PIT条目包含了除Nonce之外的许多其他内容。因此,引入了 Dead Nonce List 以从存储PIT中“dead”的 Nonce

Dead Nonce List 是NFD中的全局容器。此容器中的每个条目都存储一个Name和Nonce元组。可以有效地查询条目的存在。条目会保留一段时间,在此期间,兴趣不太可能回滚。

3.5.1节介绍了 Dead Nonce List 的结构和语义,以及 fowarding 是如何使用它的。第3.5.2节讨论了如何维护 Dead Nonce List 的容量( capacity )。

3.5.1 结构、语义和使用(Structure, Semantics and Usage)

incoming Data pipeline (第4.3.1节)和 Interest finalize pipeline (第4.2.6节)处理过程中,出记录( out-record )被删除之前,一个 tuple 被添加进 Dead Nonce ListDeadNonceList::add)。

incoming Interest pipeline 处理过程中会通过调用DeadNonceList::has去查询 Dead Nonce List 。如果存在具有相同 NameNonce 的条目,则传入的兴趣是被判定为循环兴趣。

Dead Nonce List 是一个概率数据结构( probabilistic data structure ):每个条目都存储为 NameNonce 的64位哈希,这大大减少了数据结构的内存消耗。同时,哈希冲突的可能性非零,这不可避免地导致误报:非循环兴趣被误认为循环兴趣。这些误报是可以恢复的:消费者可以用新的Nonce重新传输兴趣,这很可能会产生与现有哈希不冲突的其他哈希。我们认为,节省内存可带来的好处胜过误报的危害。

3.5.2 容量维护(Capacity Maintenance)

条目将保留在 Dead Nonce List 中以保持可配置的生命周期。条目的生存期是在循环检测的有效性,容器的内存消耗和误报率之间进行权衡。较长的生存时间可提高环路检测的效率,因为循环的兴趣只有在删除条目之前到达才能被检测到,因此,较长的生存时间允许在延迟更大的网络中检测循环兴趣。另一方面,较长的条目生存期导致要存储更多条目,因此增加了容器的内存消耗,同时保留更多条目也意味着哈希冲突的可能性更高,因此也就产生了误报。默认条目生存期设置为6秒。

实现条目生命周期的朴素方法是在每个条目中保留时间戳,但这种方法消耗太多内存。由于 Dead Nonce List 是一个概率数据结构,因此输入生存期不需要精确。因此,我们将容器索引为先进先出队列,并通过调整容器的容量将条目生存期近似为配置的生存期。

静态配置容器的容量是不可行的,因为添加条目的频率与兴趣到达率相关,而操作员无法准确估算。因此,我们使用以下算法动态调整容量近似预期的( expected )生存期L:

  • 每间隔M时间,我们向容器添加一个称为 mark 的特殊条目。mark 不是一个独特的类型,它是具有特定值的条目( 也是一个hash值,不过是一个预设的特定hash值,有可能被碰撞,但概率极低 ),并假设哈希函数不可逆,因此与通过 NameNonce 计算得出的哈希值冲突的可能性很低;
  • 每间隔M时间,我们计算容器中 mark 的数量,并记住该计数。添加 mark 与计数 mark 之间的顺序无关紧要,但应保持一致;
  • 每间隔A时间,我们查看最近的计数。当容器的容量最佳时( optimal ),容器中应始终有个 mak 。如果所有最近的计数都高于,那么容量将减少。如果所有最近的计数都低于,那么容量将增加。

此外,容量还有严格的上限和下限,以避免内存溢出并确保正确的操作。当将容量调低至受限的算法执行时间时,多余的条目不会立即全部清除,而是在以后的插入操作期间成批清除。

3.6 策略选择表(Strategy Choice Table)

策略选择表Strategy Choice Table )包含为每个名称空间( namespace )选择的转发策略(第5节)。该表是NDN架构的新增功能。从理论上讲,转发策略是一种应该存储在FIB条目中的程序[9]。实际上,由于以下原因,我们发现将转发策略保存在单独的表中而不是将其与FIB条目一起存储更方便:

  • FIB条目来自由NFD-RIB服务管理的RIB条目(第7节),将策略存储在FIB条目中将要求RIB服务在操作FIB时创建/更新/删除策略,因此,这将增加RIB服务的复杂性;
  • 当删除最后一个NextHop记录时(包括最后一个上游 face 失败时),FIB条目将自动删除,但是,我们不想丢失已配置的策略;
  • 策略配置的粒度不同于RIB条目或FIB条目的粒度,将两者放在同一个表中会使继承处理更加复杂。

3.6.1节概述了策略选择表的结构,语义和算法。第3.6.2节介绍了NFD其余部分如何使用策略选择表。策略选择表算法的实现在3.8节中讨论。

3.6.1 结构和语义(Structure and Semantics)

  • 策略选择条目(Strategy Choice entry)

    策略选择条目(nfd::strategy_choice::Entry)包含名称前缀和为该名称空间选择的转发策略。在运行时会创建一个nfd::fw::Strategy子类实例,并将其存储在每个策略选择条目内。

  • 策略选择表(Strategy Choice Table)

    策略选择表(nfd::StrategyChoice)是策略选择条目的集合。每个名称空间只能设置一个策略,但是子名称空间可以对策略有自己的选择

    为了确保每个名称空间都有一个策略,NFD始终在初始化期间将/名称空间的根条目插入到策略选择表中( 这个根条目默认可以匹配所有的名称空间 )。为此条目选择的策略称为默认策略,由daemon/fw/forwarder.cpp中的硬编码getDefaultStrategyName free 函数定义。可以替换默认策略,但是策略选择表中的根条目永远不能删除( 这样才能保证所有的名称空间都有一个默认的策略 )。

    free function :自由函数,定义在类外的函数,非成员函数

    插入操作(StrategyChoice::insert)插入“ 策略选择Strategy Choice )”条目,或在现有条目上更新所选策略。此操作接受应用 策略选择 的名称前缀和策略实例名称。策略实例名称以表示策略“ 程序program )”的名称前缀开头,例如ndn:/localhost/nfd/strategy/best-route。除此之外,它可能包含一个可选的版本号以选择策略程序的特定版本,以及其他名称组件(作为“参数”),这些名称组件将传递给策略构造器。策略实例名称用于在策略注册表中定位策略类型(在nfd::fw::Strategy类内)。如果找到策略类型,则将其实例化并存储到“ 策略选择 ”条目中。

    删除操作(StrategyChoice::erase)删除策略选择条目。删除所覆盖的名称空间将继承在父名称空间上定义的策略,不允许删除根条目。

    支持通常的精确匹配( exact match )操作。可以使用前向迭代器以未指定的顺序迭代“策略选择”条目。
    查找有效策略算法(StrategyChoice::findEffectiveStrategy)查找应用于转发兴趣的策略。名称空间的有效策略可以定义如下:

    • 如果名称空间与策略明确关联,则这是有效的策略;
    • 否则,为其显式设置策略的第一个父名称空间定义有效策略。

    查找有效策略算法将名称,PIT条目或测量条目作为输入参数。该算法的返回值是一种转发策略,使用提供的名称通过 最长前缀匹配 找到该策略。 此返回值始终是有效条目,因为每个名称空间都必须具有策略。

3.6.2 使用(Usage)

策略选择表仅通过管理协议进行更新。策略选择管理器(第6.6节)直接负责更新策略选择表。

由于本地NFD管理员(个人计算机用户或网络路由器的系统管理员)将手动选择策略,因此“策略选择”有望保持稳定。

有效的策略搜索算法被 forwarding 用于 ContentStore miss pipeline (第4.2.4节), incoming Data pipeline (第4.3.1节)和 incoming Nack pipeline (第4.4.1节)。每个传入数据包( packet )最多调用两次。

3.7 测量表(Measurement Table)

转发策略使用“测量表”来存储有关名称前缀的测量信息。策略可以在 PITMeasurements 中存储任意信息(第5.1.3节)。Measurements Table 按名称空间建立索引,因此适合存储与名称空间相关联但不特定于兴趣的信息。

测量表的结构和算法在第3.7.1节中概述。 第3.7.2节介绍了NFD其余部分如何使用“测量表”。 在3.8节中讨论了 Measurements Table 算法的实现。

3.7.1 结构(Structure)

  • Measurements entry

    Measurements 条目(nfd::measurements::Entry)包含一个名称,以及给策略( strategy )提供的存储和检索任意信息的API(nfd::StrategyInfoHost,第5.1.3节)。虽然添加一些策略之间可以共享的标准指标( standard metrics )也是可以的,例如往返时间( trip time ),延迟( delay ),抖动( jitter )等。但是,我们认为每种策略都有其独特的需求,如果有效的策略没有使用到这些标准指标的话,添加这些标准指标将成为不必要的开销。因此,当前Measurements条目不包含标准指标。

  • Measurements Table

    Measurements 表(nfd::Measurements)是 Measurements 条目的集合。

    Measurements::get方法用于查找或插入一个 Measurements 条目。参数是名称、FIB条目或PIT条目。由于 Measurements 表的实现方式,传递FIB条目或PIT条目比使用名称更有效。Measurements::getParent方法用于查找或插入父名称空间的Measurements条目。

    与其他表不同,Measurements 表没有删除操作。而是,每个条目都有有限的生存期,并且在其生存期结束时会自动删除。如果需要的话,策略可以调用Measurements::extendLifetime来请求延长条目的生存期。

    Measurements 表支持精确匹配和最长前缀匹配查找,以检索现有条目。

3.7.2 使用(Usage)

Measurements 表仅由转发策略使用。Measurements 表中有多少条目以及访问它们的频率由转发策略决定。一个编写良好的转发策略存储不超过O(log(N))个条目,并且执行不超过O(N)个查找,其中N是传入数据包的数量加上传出数据包的数量。

  • 测量访问器(Measurements Accessor)

    回顾一下NFD具有按命名空间选择策略(第3.6节),允许每个转发策略访问该策略管理的名称空间下的 Measurements 表的部分。此限制是由测量访问器( Measurements Accessor )实现的。

    测量访问器(nfd::MeasurementsAccessor)是访问 Measurements 表的策略的代理。其API与“测量表”相似。在返回任何度量条目之前,访问者将查找“策略选择表”(第3.6节),以确认发出请求的策略是否拥有该度量条目。如果检测到访问冲突,则返回null而不是条目。

3.8 NameTree

NameTree是FIB(第3.1节)、PIT(第3.4节)、策略选择表(第3.6节)和测量表(第3.7节)的通用索引结构。使用通用索引是可行的,因为这四个表的索引有很多共性:FIB、策略选择和度量均按名称索引,而PIT则按名称和选择器索引[1]。使用公用索引是有益的,因为在这四个表上的查找通常是相关的( 例如,在插入PIT条目后,在 incoming Interest pipeline (第4.2.1节)中调用FIB最长前缀匹配),并且使用公共索引可以减少数据包处理期间索引查找的次数,索引占用的内存也相对减少。

NameTree数据结构在3.8.1节中介绍。NameTree的操作和算法在3.8.2节中介绍。第3.8.3节介绍了NameTree如何通过在表之间添加快捷方式来帮助减少索引查找的次数。

3.8.1 结构(Structure)

NameTreeNameTree entry 的集合,按 Name 索引并以树结构组织。FIB、PIT、策略选择和度量值条目被附加到NameTree条目上。

  • NameTree entry

    一个 NameTree entrynfd::name_tree::Entry)包含以下信息:

    • 名称前缀( the name prefix
    • 指向父条目的指针( a pointer to the parent entry
    • 指向子条目的指针( pointers to child entries
    • 零或一个FIB条目( zero or one FIB entry
    • 零个或多个PIT条目( zero or more PIT entry
    • 零或一个策略选择条目( zero or one Strategy Choice entry
    • 零个或一个测量条目( zero or one Measurements entry

    NameTree条目通过父和子指针形成树结构。树形结构遵循名称层次结构:父条目的名称前缀是其子条目的名称前缀减去最后一个 component

    附加到同一 NameTree条目的FIB、Strategy Choice、Measurements条目与NameTree条目具有相同的名称。在大多数情况下,附加到NameTree条目的PIT条目可以与NameTree条目具有相同的名称,仅在选择器中有所不同。作为一种特殊情况,将其兴趣名称以隐式摘要( implicit digestcomponent 结尾的PIT条目附加到与兴趣名称减去隐式摘要 component 对应的NameTree条目上,以使全匹配( all match )算法(第3.8.2节) 根据到来的数据包( Data packet )的名称(不计算其隐式摘要)可以找到此PIT条目。

  • NameTree hash table

    除了树结构之外,NameTree条目还被组织到哈希表中,以实现更快的基于名称的查找。具体地说,哈希表使我们无需树的根即可定位深层条目。我们决定从头开始自己实现哈希表(nfd::name_tree::Hashtable),而不是使用现有的库,以便我们可以更好地控制性能调整。

    哈希表包含许多存储桶( buckets )。要插入一个条目,我们使用CityHash [12]计算其名称前缀的哈希值。选择此哈希函数是因为其速度很快。具体来说,哈希值是通过名称成分的TLV表示来计算的,但不覆盖外部的NAME-TYPE TLV-LENGTH字段,这使我们可以在需要时一起计算名称的所有前缀的哈希值。然后将该条目映射到由哈希值选择的存储桶( buckets )中。如果将多个名称映射到同一个存储桶( buckets ),则通过在双向链表中链接条目来解决哈希冲突。

    随着存储的NameTree条目数的更改,哈希表会自动调整大小。调整大小操作由nfd::name_tree::HashtableOptions中的参数控制。 参数设置是在空存储桶的浪费内存和链接时间开销之间的权衡。当负载因子(条目数除以存储桶数)高于扩展阈值( expand threshold )或低于收缩阈值( shrink threshold )时,将触发调整大小操作,其中每个NameTree条目都将移至新哈希表中的相应存储桶。

    我们引入一个nfd::name_tree::Node来存储哈希表实现中使用的字段,包括:

    • 哈希值,因此调整大小操作不需要重新计算它
    • 指向存储桶双向链接列表中的上一个节点的指针
    • 指向存储桶双向链接列表中的下一个节点的指针
    • NameTree条目(节点拥有条目,条目具有指向节点的指针)

3.8.2 操作和算法(Operations and Algorithms)

  • 插入和删除操作(Insertion and Deletion operations)

    lookup/insertion 操作(NameTree::lookup)查找或插入给定Name的条目。为了维护树结构,必要时插入祖先( ancestor )条目。当插入FIB/PIT/策略选择/度量条目时,将调用此操作。

    如果一个NameTree条目上没有存储FIB/PIT/策略选择/度量值条目,并且没有子条目,使用 conditional deletion 操作(NameTree::eraseEntryIfEmpty)可删除该条目。如果满足相同要求,则被删除条目的祖先也将被删除。删除FIB/PIT/策略选择/度量条目时,将调用此操作。

  • 匹配算法(Matching algorithms)

    完全匹配exact match )算法(NameTree::findExactMatch)查找具有指定名称的条目,如果该条目不存在,则返回null。

    最长前缀匹配longest prefix match )算法(NameTree::findLongestPrefixMatch)查找指定名称的最长前缀匹配项,并通过可选的 EntrySelector 进行过滤。EntrySelector 是一个 predicate ,用于确定是否可以接受(返回)条目。该算法的实现方式为:从查找哈希表中的全名开始;如果不存在NameTree条目或被 predicate 拒绝,则删除一个名称组件并再次查找,直到找到可接受的NameTree条目。FIB的最长前缀匹配算法(第3.1.1节)调用此算法,其 predicate 仅在包含FIB条目的情况下接受NameTree条目。策略选择查找有效策略算法(第3.6.1节)调用此算法,其 predicate 仅在包含策略选择条目的情况下接受NameTree条目。

    全匹配all match )算法(NameTree::findAllMatches)枚举作为给定名称前缀的所有条目,并通过可选的 EntrySelector 进行过滤。该算法实现为:首先执行最长的前缀匹配;然后删除名称组件,直到到达根条目为止。PIT数据匹配算法调用该算法(第3.4.2节)。

  • 枚举算法(Enumeration algorithms)

    完整枚举full enumeration )算法(NameTree::fullEnumerate)枚举所有条目,并通过可选的 EntrySelector 进行过滤。FIB枚举和策略选择枚举使用此算法。

    部分枚举partial enumeration )算法(NameTree::partialEnumerate)枚举指定名称前缀( Name prefix )下的所有条目,并通过可选的 EntrySubTreeSelector 进行过滤。EntrySubTreeSelector 是一个双谓词( double-predicate ),用于确定是否可以接受条目以及是否应访问其子项。在运行时策略更改(第5.1.3节)期间使用此算法清除命名空间下的 StrategyInfo items

3.8.3 捷径(Shortcuts)

NameTree的好处之一是可以减少数据包转发过程中索引查找的次数。为了实现此目标,一种方法是让转发管道显式执行NameTree查找,并使用NameTree条目的字段。但是,这不是理想的,因为引入NameTree是为了提高四个表的性能,并且它不应更改转发管道的过程。

为了减少索引查找的数量,但仍将NameTree隐藏在转发管道之外,我们在表之间添加了快捷方式。每个FIB/PIT/策略选择/度量条目均包含指向相应NameTree条目的指针,NameTree条目包含指向FIB/PIT/策略选择/度量条目和父级NameTree条目的指针。因此,例如,在给定一个PIT条目的情况下,可以通过指针在恒定时间内检索相应的NameTree条目,然后通过NameTree条目检索或附加 Measurements 条目,或者通过指向父条目的指针找到最长的前缀匹配FIB条目。

仅当PIT条目的兴趣名称不以隐式摘要结尾时才适用; 否则,将执行常规查找。

采用这种方法,NameTree条目仍然可以进行转发。为了也隐藏NameTree条目,我们在表算法中引入了新的重载,这些重载将相关的表条目替换为Name。这些重载包括:

  • Fib::findLongestPrefixMatch可以接受PIT条目或Measurements条目来代替Name
  • StrategyChoice::findEffectiveStrategy可以接受PIT条目或Measurements条目来代替Name
  • Measurements::get可以接受FIB条目或PIT条目代替名称

带有表条目的重载通常比带有Name的重载更有效。通过使用这些重载,转发可以利用减少索引查找的优势,但无需直接处理NameTree条目。

为了支持这些重载,NameTree提供了NameTree::getEntry函数模板,该模板返回从FIB/PIT/Strategy Choice/Measurements条目链接的NameTree条目。NameTree::getEntry允许一个表从另一表的条目中检索相应的NameTree,而无需知道该条目的内部结构。它还允许表将来离开NameTree,而不会破坏其他代码:假设有一天 Measurements 不再基于NameTree,NameTree::getEntry可以使用Measurements条目中的“兴趣名称”执行查找;Fib::findLongestPrefixMatch仍然可以接受Measurements条目,尽管它并不比使用Name更有效。

你可能感兴趣的:(NFD开发指南-3.Tables)