DPDK 学习5 -- DPDK rte meter令牌桶的实现

为了解决各种限速的问题,我们先来解决TB的实现。

令牌桶(Token-Bucket)是目前最常采用的一种流量测量方法,用来评估流量速率是否超过了规定值。

关于令牌桶处理报文的方式,RFC 中定义了两种令牌桶算法:

  1. 单速率三色标记(single rate three color markersrTCMRFC2697定义,或称为单速双桶算法)算法,主要关注报文尺寸的突发。
  2. 双速率三色标记(two rate three color markertrTCMRFC2698定义,或称为双速双桶算法)算法,主要关注速率的突发。

实际中比较常见的有两种实现方式:

  1. 周期性的添加,添加的时间间隔就是令牌桶的容量与添加速率的比值:△t=CBS/CIR,每次添加的令牌数为 CBS 个。
  2. 一次性添加,添加令牌的数量是△t×CIR(t 是当前时间与上次添加令牌的时间之差),且是一次添加完毕,并不是按照一定速率添加。

当前业界都采用的第二种方式,第一种方式涉及定时器精度不太好控制。

 

由于报文过令牌桶的时候我们很容易能获取到的是报文长度和时间,这两个度量我们分别取单位为:

1个字节和1个cycle,然后我们使用这两个单位来展开令牌桶的换算:

1秒时间单位内有CIR(CIR已经换算成字节了)个字节,而1秒的时间单位有HZ * 1个cycle,利用这层关系我们假设:

CIR * 1个字节 = HZ * 1个cycle,换算后我们可以得到两个公式:

  1. 1个字节  = HZ/CIR * 1个cycle
  2. 1个cycle = CIR/HZ * 1个字节

两种实现方式的基本思想是一直的,只是细节上有一些差别,我们下面具体来看

扫描二维码关注公众号,回复: 6070638 查看本文章

3.1 第一种实现方式

根据公式:1个字节  = HZ/CIR * 1个cycle 我们来实现第一种方式的令牌桶,也是DPDK当前的实现方式,代码分散在dequeue的各个出队状态机里面。

3.1.1 基本实现

当CIR太大的时候,1个字节计算出来的cycle数会为小数,为了解决这个问题,我们引入扩大因子,扩大因子越大,分辨率越高,同时需要考虑HZ和最大支持的CIR的关系和溢出的风险,扩大因子又不能太大,那么公式就变成如下:

1个字节  = (HZ/CIR * 1个cycle) << 5 (扩大因子) ①

以此为依据我们看下过srTCM的整个过程:

获取当前的cycle,记为new_cycle

获取这段期间的cycle数,记为diff_cycle = new_cycle – old_cycle

获取diff_cycle的字节数 diff_bytes = diff_cycle/ * 扩大因子

然后使用diff_bytes过桶:

1、计算TC 和TE:

IF tc + diff_bytes >= CBS

    tc = CBS;

    diff_bytes -= (CBS – tc);

   IF te + diff_bytes >= EBS

       te = EBS;

    ELSE

       te += diff_bytes;

ELSE

tc += diff_bytes;

2、计算颜色

IF tc >= Pkt_len

   Color = GREE;

   Tc -= Pkt_len;

ELSE IF te >= Pkt_len

   Color = YELLOW;

   Te -= Pkt_len;

ELSE

   Color = RED;

3.1.2 优化方法

上面的实现由两个地方可以优化:

1、diff_bytes 使用了除法,这个代码是很昂贵的,DPDK 实现了一个使用移位代替除法的算法,来提高性能,但没怎么看明白。

我们可以使用空间换时间的想法,使用以下的方法,提高整个过桶的过程:

  • 根据公式:1个字节  = HZ/CIR * 1个cycle * << 5(扩大因子),我们已经知道了一个字节代表的cycle数,那么可以可以把所有的都转化为cycle的时间单位来计算。

创建令牌桶的时候,CBS、EBS 根据公式换算成 cycles数保存,同时计算数组(下标是字节数)L2T[1 … 512] = [x cycle …… ycycle],为过桶做好准备。

 

过桶的时候,获取报文长度pkt_len,然后计算cycle,公式如下:

tmp_len = pkt_len / 扩大因子,然后计算tc_cycle,这里考虑tmp_len的计算,扩大因子不能太大,32是一个比较好的值。

IF tmp_len > 512

    tc_cycle = (tmp_len / 512) * L2T[512] + L2T[tmp_len & 511];

ELSE

tc_cycle = L2T[tmp_len];

然后在根据tc_cycle去过桶。

解决了除法的问题,同时使用数组和移位加快计算,不在乎内存的话,L2T的数组可以定义为MTU的大小,任何报文长度都可以根据数组一次得到tc_cycle。

2、过桶的时候,使用的是比较tc >= pkt_len,没有实现借贷,可能会导致报文不平滑,举个例子:

  • 假设设备端口的CIR设置为1Mbps,CBS为2000bytes, EBS为2000bytes,初始状态时C桶和E桶满。
  • 假设第1个到达的报文是1500 bytes 时,检查C桶发现令牌数大于数据包的长度,所以数据包被标为绿色,C桶减少1500 bytes,还剩500 bytes,E桶保持不变。
  • 假设1ms之后到达第2个报文1500 bytes,新增令牌 CIR*1ms=1000bit=125bytes,此时C桶共有625 bytes,令牌不够。检查E桶有足够令牌,因此报文标记为黄色,E桶减少1500bytes,剩 500bytes,C桶不变。
  • 假设又过1ms后到达第3个报文1000 bytes,新增令牌 CIR*1ms=1000bit=125bytes,此时C桶共有750bytes,令牌不够,检查E桶也不够,因此报文被标记为红色, C桶、 E桶令牌数不变。
  • 假设又过20ms后到达第4个报文1500bytes,新增令牌CIR*20ms=20000bit=2500bytes,C桶此时令牌数3250 bytes,CBS=2000bytes,因此溢出1250bytes 添加到E桶,此时E桶有1750bytes。由于此时C桶大于报文长度,报文标记为绿色,C桶减少1500bytes剩500bytes,E桶不变。

……………………

整个过程如下表所示:

序号

时间(ms)

报文长度

时间间隔

本轮新增令牌

新的后TC

新的后TE

剩余TC

剩余TE

颜色

-

-

-

-

-

2000

2000

2000

2000

-

1

0

1500

0

0

2000

2000

500

2000

GREE

2

1

1500

1

125

625

2000

625

500

YELLOW

3

2

1500

1

125

750

500

750

500

RED

4

22

1500

20

2500

2000

1750

500

1750

GREE

 

如果使用了借贷后,计算颜色的过程变成:

IF tc > 0

   Color = GREE;

   Tc -= Pkt_len;

ELSE IF te > 0

   Color = YELLOW;

   Te -= Pkt_len;

ELSE

   Color = RED;

 

上面的过程变成:

序号

时间(ms)

报文长度

时间间隔

本轮新增令牌

新的后TC

新增后TE

剩余TC

剩余TE

颜色

-

-

-

-

-

2000

2000

2000

2000

-

1

0

1500

0

0

2000

2000

500

2000

GREE

2

1

1500

1

125

625

2000

-875

2000

GREE

3

2

1500

1

125

-750

2000

-750

500

YELLOW

4

22

1500

20

2500

1750

500

250

500

GREE

 

20ms这个上对于上面的过程tc没有溢出1250bytes的令牌,整个报文输出表现的更为平滑。

3.2 第二种实现方式

根据公式:1个cycle = CIR/HZ * 1个字节,我们来实现第二种方式的令牌桶,也是VPP CAR当前的实现方式。

当CIR 太小的时候,这个计算出来的值会为小数,同第一种方式,我们引入扩大因子:

1个cycle * << 17 = CIR/HZ * 1个字节 * << 17, 扩大因子VPP使用的是 1 << 17

我们现在把1个cycle * 217 称为新的时间单位,后续计算都以这个新的时间单位为基准。

以此为依据我们看下过srTCM的整个过程:

获取当前的cycle,记为new_cycle,单位为新的时间单位,计算为cycle >> 17。

获取这段期间的cycle数,记为diff_cycle = new_cycle – old_cycle(单位也是新的时间单位)

获取diff_cycle的字节数 diff_bytes = diff_cycle * CIR/HZ * 1个字节 * << 17

 

然后在根据diff_bytes去过桶,实现和上面的流程一致。

为了考虑乘法溢出的可能,可以使用64位存储。

VPP 还把TC/TE/CIRHZ * 1个字节 * 217,同时扩大了 2N,N根据前面3个最大值的log2N计算来的,然后过桶的时候把报文长度也扩大2N次,然后在过桶,代码注释是:

  // Scale if possible. Scaling helps rate accuracy, but is constrained

  // by the scaled rates and limits fitting in 32-bits.

  // In addition, we need to insure the scaled rate is no larger than

  // 2^22 tokens per period. This allows the dataplane to ignore overflow

  // in the tokens-per-period multiplication since it could only

  // happen if the policer were idle for more than a year.

  // This is not really a constraint because 100Gbps at 1Ghz is only

  // 1.6M tokens per period.

// Scale packet length to support a wide range of speeds

其实也是扩大因子,为了支持更小的CIR,比如800 bps。而第一种方式就没有这种忧虑。

猜你喜欢

转载自blog.csdn.net/armlinuxww/article/details/89392598
今日推荐