Intro
本文旨在通过 ndnSIM 实现一个最简单的缓存策略,为后面自定义更加复杂的策略提供参考。注意ndnSIM 2.8版本的缓存实现与之前的版本有一些区别,因此不保证可以在其它版本下适用。
首先我们找到调用缓存插入是由谁调用的?
Forwarder::onIncomingData(),这个函数用于处理到达本节点的 data 包。在这里面会调用 m_cs.insert(data) 将到达本节点的 data 插入缓存。
1. 源码分析
下面我们来深入分析 Cs::insert()
// 如果设置了禁止 admit 任何数据,或者缓存容量为 0。直接 return,不缓存。
if (!m_shouldAdmit || m_policy->getLimit() == 0) {
return;
}
// 取出 data 中携带的 CachePolicyTag,这个 tag 用于指示是否缓存。如果没有这个tag,则认为可以进行后续的缓存。如果这个tag指明了不缓存,直接return。
shared_ptr<lp::CachePolicyTag> tag = data.getTag<lp::CachePolicyTag>();
if (tag != nullptr) {
lp::CachePolicyType policy = tag->get().getPolicy();
if (policy == lp::CachePolicyType::NO_CACHE) {
return false;
}
}
// 将data压入m_table表,这个表用来存CS的内容。后面兴趣包转发时,逐跳查询是否命中缓存的时候,用的也是这个表。
const_iterator it;
bool isNewEntry = false;
std::tie(it, isNewEntry) = m_table.emplace(data.shared_from_this(), isUnsolicited);
Entry& entry = const_cast<Entry&>(*it);
entry.updateFreshUntil();
// 如果这个插入的 data 不是新的项,即缓存中已经存在了,则执行 refresh 操作,这对于不同的策略具有不同的操作(LRU & FIFO)
if (!isNewEntry) { //
if (entry.isUnsolicited() && !isUnsolicited) {
entry.clearUnsolicited();
}
m_policy->afterRefresh(it);
}
// 如果是新的 data,就将它插入缓存队列中。 TODO:m_queue 和 m_table ?
else {
m_policy->afterInsert(it);
}
2. 缓存决策:LCD
即Leave Copy Down策略,只在服务节点的下一跳节点进行缓存,即每次缓存都距离用户更近一跳。具体可参考:The LCD interconnection of LRU caches and its analysis
该技术最初是为分层 Web 缓存系统提出的。具体操作如下:每当内容请求从缓存或内容源获得服务时,内容只会在指向请求者的路径上的缓存层次结构中向下复制一层(即只在当前服务节点的下一跳缓存)。
这导致两个期望的效果:首先,内容在从服务节点到请求者的路径上只存储一次,从而减轻了冗余缓存的问题。 其次,内容逐渐向网络边缘复制,使得更流行的内容复制到更多节点,更靠近请求者。
该技术仅需要在节点间很少的协作,因为它们可以通过简单地向传输的内容中附加一个标志,来向下游的其他节点指示是否缓存。
3. 具体实现
3.1 自定义Tag的实现
用于指示节点是否缓存。源节点将其设为0,并添加到data包中。中间缓存节点接收到 data 包时,若发现这个 Tag 值为0,则缓存,并将该tag值置为1。否则直接转发。可参照原生的 HopCountTag 的实现,具体如下:
src/ndnSIM/ndn-cxx/ndn-cxx/lp/tags.hpp,添加以下:
/** \class LCDTag
* \brief a packet tag for LCD
*
* This tag can be attached to Interest, Data, Nack.
*/
typedef SimpleTag<uint64_t, 0x60000002> LCDTag;
src/ndnSIM/ndn-cxx/ndn-cxx/lp/tlv.hpp,添加以下:
/**
* \brief TLV-TYPE numbers for NDNLPv2
*/
enum {
...
LCDTag = 90,
}
src/ndnSIM/ndn-cxx/ndn-cxx/lp/fields.hpp,添加以下
typedef FieldDecl<field_location_tags::Header, uint16_t, tlv::LCDTag, false, NonNegativeIntegerTag,
NonNegativeIntegerTag>
LCDTagField;
BOOST_CONCEPT_ASSERT((Field<LCDTagField>));
...
/** \brief Set of all field declarations.
*/
typedef boost::mpl::set<FragmentField, SequenceField, FragIndexField, FragCountField, PitTokenField, NackField,
NextHopFaceIdField, IncomingFaceIdField, CachePolicyField, CongestionMarkField, AckField,
TxSequenceField, NonDiscoveryField, PrefixAnnouncementField, HopCountTagField, LCDTagField, GeoTagField>
FieldSet;
3.2 自定义Tag的处理
src/ndnSIM/NFD/daemon/face/generic-link-service.cpp,做以下修改:
(1) 节点收到 data 后如何处理它的 tag ?不做更改,交给后面的缓存策略来决定。
void
GenericLinkService::decodeData(const Block& netPkt, const lp::Packet& firstPkt, const EndpointId& endpointId)
{
...
if (firstPkt.has<lp::LCDTagField>()) { // 收到的包, 如果有LCDTag, 不在这里修改
data->setTag(make_shared<lp::LCDTag>(firstPkt.get<lp::LCDTagField>()));
}
}
(2) 本节点转发和缓存操作完成后,这里是给发出去的 data 包,打上这个 tag。(注意前面的缓存操作有可能会修改这个 tag 值)
void
GenericLinkService::encodeLpFields(const ndn::PacketBase& netPkt, lp::Packet& lpPacket) {
...
shared_ptr<lp::LCDTag> lcdTag = netPkt.getTag<lp::LCDTag>();
if (lcdTag != nullptr) { // 如果有这个tag, 直接去拿它的值
lpPacket.add<lp::LCDTagField>(*lcdTag);
}
else { // 如果没有 我们去添加这个tag 并将它的字段值设置为0
// 什么时候没有? Producer节点, 或缓存命中节点.
lpPacket.add<lp::LCDTagField>(0);
}
}
3.3 缓存策略的实现
src/ndnSIM/NFD/daemon/fw/forwarder.cpp中,做以下修改:根据缓存决策的结果,对 data 中的 LCDTag 值做对应的修改。
void
Forwarder::onIncomingData(const FaceEndpoint& ingress, const Data& data)
{
...
// 把原来这个注掉, 它相当于缓存每一个内容, 即 LCE
// m_cs.insert(data);
// Added:
// 缓存决策: 你可以直接在这里实现, 也可以在 Cs::insert() 里面实现
bool cacheDecision = m_cs.insert(data);
if (cacheDecision) {
data.setTag(make_shared<lp::LCDTag>(1));
}
...
}
src/ndnSIM/NFD/daemon/table/cs.cpp中,做以下修改:(1)返回值类型改为bool,这是为了返回缓存决策结果。(2)添加 LCD 缓存决策函数的实现。
// modified:
bool
Cs::insert(const Data& data, bool isUnsolicited)
{
if (!m_shouldAdmit || m_policy->getLimit() == 0) {
return false;
}
shared_ptr<lp::CachePolicyTag> tag = data.getTag<lp::CachePolicyTag>();
if (tag != nullptr) {
lp::CachePolicyType policy = tag->get().getPolicy();
if (policy == lp::CachePolicyType::NO_CACHE) {
return false;
}
}
// added: cache decision
bool cacheDecRes = cacheDecisionLCD(data);
if (!cacheDecRes)
return false;
NFD_LOG_DEBUG("insert " << data.getName());
const_iterator it;
bool isNewEntry = false;
std::tie(it, isNewEntry) = m_table.emplace(data.shared_from_this(), isUnsolicited);
Entry& entry = const_cast<Entry&>(*it);
entry.updateFreshUntil();
if (!isNewEntry) {
if (entry.isUnsolicited() && !isUnsolicited) {
entry.clearUnsolicited();
}
m_policy->afterRefresh(it);
}
else {
m_policy->afterInsert(it);
}
return true;
}
// added:
bool
Cs::cacheDecisionLCD(const Data& data)
{
// (1) if management protocol --> not insert
bool isData = true;
std::string testStr = "/localhost/nfd/";
std::string dataName = data.getName().toUri();
std::string::size_type idx = dataName.find(testStr);
if (idx != std::string::npos) { // match ---> 即不是常规data项
isData = false;
return false;
}
// (2) content
auto lcdTag = data.getTag<lp::LCDTag>();
int lcd_tag = 0;
if (lcdTag != nullptr) {
lcd_tag = *lcdTag;
}
if (lcd_tag == 0) { // 有2种情形: (1) Producer响应数据,tag为null; (2) 缓存节点响应,*tag为0
return true;
} else {
return false;
}
}
src/ndnSIM/NFD/daemon/table/cs.hpp中,做以下修改:即上面的函数声明。
...
public:
bool insert(const Data& data, bool isUnsolicited = false);
bool cacheDecisionLCD(const Data& data);
...
4. 测试
仍然使用 src/ndnSIM/examples/ndn-tree-cs-tracers.cpp 进行测试,我们对打印做了一些过滤和修改,输出如下:
'build' finished successfully (38.182s)
+0.000000000s 0 ndn.Consumer:Consumer()
+0.000000000s 1 ndn.Consumer:Consumer()
+0.000000000s 0 ndn.Consumer:StartApplication()
+0.000000000s 2 ndn.Consumer:Consumer()
+0.000000000s 0 ndn.Consumer:SendPacket(): [INFO ] > Interest for 0
+0.000000000s 0 ndn.Consumer:WillSendOutInterest(): [DEBUG] Trying to add 0 with +0.0ns. already 0 items
+0.000000000s 0 ndn-cxx.nfd.ContentStore:findImpl(): [DEBUG] find /root/%FE%00 no-match
+0.000000000s 3 ndn.Consumer:Consumer()
+0.000000000s 0 ndn.Consumer:OnNack(): [INFO ] NACK received for: /root/%FE%00, reason: NoRoute
+0.000000000s 6 ndn.Producer:Producer()
+0.000000000s 6 ndn.Producer:StartApplication()
+0.010000000s 1 ndn.Consumer:StartApplication()
+0.010000000s 1 ndn.Consumer:SendPacket(): [INFO ] > Interest for 0
+0.010000000s 1 ndn.Consumer:WillSendOutInterest(): [DEBUG] Trying to add 0 with +10000000.0ns. already 0 items
+0.010000000s 1 ndn-cxx.nfd.ContentStore:findImpl(): [DEBUG] find /root/%FE%00 no-match
+0.011028799s 4 ndn-cxx.nfd.ContentStore:findImpl(): [DEBUG] find /root/%FE%00 no-match
+0.012057598s 6 ndn-cxx.nfd.ContentStore:findImpl(): [DEBUG] find /root/%FE%00 no-match
+0.012057598s 6 ndn.Producer:OnInterest(0x5648ffd74c00, /root/%FE%00?Nonce=4231791817&Lifetime=2000)
+0.012057598s 6 ndn.Producer:OnInterest(): [INFO ] node(6) responding with Data: /root/%FE%00
# 仅在节点 6 缓存
+0.012057598s 6 ndn-cxx.nfd.ContentStore:insert(): [DEBUG] insert /root/%FE%00
+0.015769596s 1 ndn.Consumer:OnData(0x5648ffe19440, Name: /root/%FE%00
MetaInfo: ContentType: 0
Content: (size: 1024)
Signature: (type: Unknown(255), value_length: 1)
)
+0.015769596s 1 ndn.Consumer:OnData(): [DEBUG] Hop count: 2
+0.015769596s 1 ndn.Consumer:OnData(): [DEBUG] LCD TAG: 1
+0.020000000s 2 ndn.Consumer:StartApplication()
+0.020000000s 2 ndn.Consumer:SendPacket(): [INFO ] > Interest for 0
+0.020000000s 2 ndn.Consumer:WillSendOutInterest(): [DEBUG] Trying to add 0 with +20000000.0ns. already 0 items
+0.020000000s 2 ndn-cxx.nfd.ContentStore:findImpl(): [DEBUG] find /root/%FE%00 no-match
+0.021028799s 5 ndn-cxx.nfd.ContentStore:findImpl(): [DEBUG] find /root/%FE%00 no-match
+0.022057598s 6 ndn-cxx.nfd.ContentStore:findImpl(): [DEBUG] find /root/%FE%00 matching /root/%FE%00
# 仅在节点5缓存
+0.023913597s 5 ndn-cxx.nfd.ContentStore:insert(): [DEBUG] insert /root/%FE%00
+0.025769596s 2 ndn.Consumer:OnData(0x5648ffedbf50, Name: /root/%FE%00
MetaInfo: ContentType: 0
Content: (size: 1024)
Signature: (type: Unknown(255), value_length: 1)
)
+0.025769596s 2 ndn.Consumer:OnData(): [DEBUG] Hop count: 2
+0.025769596s 2 ndn.Consumer:OnData(): [DEBUG] LCD TAG: 1
+0.029999999s 3 ndn.Consumer:StartApplication()
+0.029999999s 3 ndn.Consumer:SendPacket(): [INFO ] > Interest for 0
+0.029999999s 3 ndn.Consumer:WillSendOutInterest(): [DEBUG] Trying to add 0 with +29999999.0ns. already 0 items
+0.029999999s 3 ndn-cxx.nfd.ContentStore:findImpl(): [DEBUG] find /root/%FE%00 no-match
+0.031028798s 5 ndn-cxx.nfd.ContentStore:findImpl(): [DEBUG] find /root/%FE%00 matching /root/%FE%00
# 仅在节点3缓存
+0.032884797s 3 ndn-cxx.nfd.ContentStore:insert(): [DEBUG] insert /root/%FE%00
+0.032884797s 3 ndn.Consumer:OnData(0x5648ffdf2920, Name: /root/%FE%00
MetaInfo: ContentType: 0
Content: (size: 1024)
Signature: (type: Unknown(255), value_length: 1)
)
+0.032884797s 3 ndn.Consumer:OnData(): [DEBUG] Hop count: 1
+0.032884797s 3 ndn.Consumer:OnData(): [DEBUG] LCD TAG: 1