QoS中的流量监管(Traffic Policing)就是对流量进行控制,通过监督进入网络端口的流量速率,对超出部分的流量进行“惩罚”(这个惩罚可以是丢弃、也可是延迟发送),使进入端口的流量被限制在一个合理的范围之内。例如可以限制HTTP报文不能占用超过50%的网络带宽,否则QoS流量监管功能可以选择丢弃报文,或重新配置报文的优先级。
QoS流量监管功能是采用令牌桶(Token-Bucket)机制进行的。这里的“令牌桶”是指网络设备的内部存储池,而“令牌”则是指以给定速率填充令牌桶的虚拟信息包。可以这么简单理解,“令牌桶”可以理解为一个水桶,而“令牌”则可以理解为通过一根水管流到水桶中的水。
交换机在接收每个帧时都将添加一个令牌到令牌桶中,但这个令牌桶底部有一个孔,不断地按你指定作为平均通信速率(单位为b/s)的速度领出令牌(也就是从桶中删除令牌的意思)。相当于一个水桶的上边连接一根进水的水管,而下边又连接一根连接到用水的地方的出水管。在每次向令牌桶中添加新的令牌包时,交换机都会检查令牌桶中是否有足够容量(也就是在要向桶水加水前,先要检查是桶内否已满了),如果没有足够的空间,包将被标记为不符规定的包,这时在包上将发生指定监管器中规定的行为(丢弃或标记),就相当于如果当前水桶满了,但上边水管的水还是来了,这时要么就是让这些水白白流到桶外,要么把这些水用其它容器先装起来,等水桶中不再满水时再倒进去,供用户使用。整个令牌桶的基本工作原理表示:
图 令牌桶的基本工作作原理
令牌桶填满的时间长短是由令牌桶深度(也就是容量,单位为bit,类似于水桶的的深度)、令牌漏出速率(类似桶下边接的水管的水速)和超过平均速率的突发通信流(类似于上桶上边水管突发的急速水流)持续的时间三个方面共同决定的。 令牌桶的大小利用突发时长上限乘以点对点传输时的帧数限制得出(也就类似突发水流持续的时间*突发水流的流速)。如果突发时间比较短,令牌桶不会溢出,在通信流上不会发生行为。但是,如果突发时间比较长,并且速率比较高,令牌桶将溢出,这时将对突发过程中的帧采取相应的流监管策略行为(也就是在水桶满水后对溢出的水的处理方法)。
在令牌桶处理包的行为方面,RFC中定义了两种令牌桶算法——单速率三色标记(single rate threecolor marker,srTCM)算法和双速率三色标记(two rate threecolor marker,trTCM)算法,其评估结果都是为包打上红、黄、绿三色标记(所以称为“三色标记”,有关这些颜色的具体含义将在具体算法中介绍)。QoS会根据包的颜色,设置包的丢弃优先级,其中单速率三色标记比较关心包尺寸的突发,而双速率三色标记则关注速率上的突发,两种算法都可工作于色盲模式和非色盲模式(具体在下面介绍)。下面分别介绍这两种算法原理。
1. 单速率三色标记算法原理
这里首先要理解“单速率”是什么意思,那就是算法中的两个令牌桶有同样的承诺信息速率(CIR),也就是具有相同平均访问速率。这两个令牌桶分别是正常使用的令牌桶(也就是下面将要说到的C桶)和超出令牌桶容量的突发令牌桶(也就是下面将要说到的E桶),可以理解为两个水桶,一个是正常使用的水桶,另一个是用来当正常使用的水桶满后装多余的水的水桶。下面具体解释单速率三色标记算法原理。
单速率三色标记(srTCM)算法关注的是数据包的突发尺寸,数据包的色标记评估依据以下3个参数:承诺信息速率(CommittedInformation Rate,CIR)、承诺突发尺寸(Committed BurstSize,CBS)和超额突发尺寸(Excess Burst Size,EBS)。CIR是指向令牌桶中填充令牌的平均速率,即允许的通信流平均速度;CBS是指每次突发所允许的最大的流量尺寸,也相当于允许的最大取令牌的速率,等于桶的容量(最大时就是一个包就可以全部领取桶中的全部令牌)。EBS是指每次突发允许超出CBS的最大流量尺寸。CBS和EBS的单位都是bit(位)。
单速率三色机制采用双桶结构:C桶和E桶(之所以用这两个字母来表示,为的就是与前面说的CBS和EBS两种速率的头个字母一致,便于描述),且两个令牌桶的CIR一样。C令牌桶中任何未用的令牌都被放入E令牌桶中,用做以后临时超过CIR的突发流量的令牌;另外,当C令牌桶满时,超出的令牌也都会放在E令牌桶中。
Tc和Te分别表示C令牌桶和E令牌桶中的令牌数,也就是桶中当前的容量(单位也为bit),两桶的总容量分别为CBS和EBS,也就是对应前面介绍的承诺突发尺寸和超额突发尺寸,最初它们都是满的,即Tc和Te初始值分别等于CBS和EBS。正常情况下,不会使用第二个令牌桶(也就是E桶),而是把任何CBS(也就是C桶)中未使用的令牌都放入E桶中,只有当C令牌桶满后,后面来的令牌才放到E令牌桶中,为可能出现的突发数据提供信用令牌(也就是经过允许的令牌)。
在这种单速率三色标记算法中,两个令牌桶中令牌的添加是按照相同的CIR速率进行的。即每隔1/CIR时间添加一个令牌。添加的顺序是先添加C桶再添加E桶,当两个令牌桶中的令牌都满时,再产生的令牌就会被丢弃。至于在发送数据包时,令牌的使用IEEE又定义了三种颜色(分别为红色、黄色和绿色)以及两种模式:色盲(color-blind)模式和感色(color-aware)模式,默认为色盲模式。三种颜色的功能与我们日常生活中的交通指示灯中的三种颜色类似,红色表示违规数据,直接丢弃,黄色表示数据包虽然违法,但不直接丢弃,而是延迟发送,绿争为合法数据包,直接发送。
在色盲(color-blind)模式下是假设包都是没有经过“着色”处理的(不辨别包中原来标记的颜色),是根据包长度来确定包被标记的颜色。现假设到达的包长度为B(单位为bit)。若包长度B小于C桶中的令牌数Tc(也就是C桶中的令牌数足够该包发送所需),则包被标记为绿色,表示包符合要求,包发送后C桶中的令牌数Tc减少B。如果TcTe,标记为红色,表示是违反规定的包,直接丢弃,两令牌桶中的总令牌数都不减少。
在感色(color-aware)模式下是假设包在此之前已经过“着色”处理(会辨别包中原来标记的颜色),如果包已被标记为绿色,或包长度B
2. 双速率三色算法
这里同样首先要稿清楚“双速率”是什么意思,它是指该算法中两个令牌桶中的CIR速率不同,存在两个令牌填充速率。
IETF的双速率三色标记(trTCM)算法主要是根据四种流量参数来评估:CIR、CBS、峰值信息速率(Peak InformationRate,PIR),峰值突发尺寸(Peak Burst Size,PBS)。CIR和CBS参数与单速率三色算法中的含义相同,PIR就是允许的最大突发信息传输速率,当然它的值肯定不会小于CIR的;PBS是允许的最大突发信息尺寸,它的值也不会小于CBS。
与单速率三色标记算法不同,双速率三色标记算法中的两个令牌桶是C桶和P桶(不是C桶和E桶),但它们的令牌填充速率是不同的,C桶填充速率为CIR,P桶为PIR;两桶的容量分别为CBS和PBS(之所以用C桶和P桶表示也是基于方便描述,因为表示不同速率的参数与对应桶的容量参数相同,第一个字母对应为C,或者P)。用Tc和Tp表示两桶中的令牌数目,初始状态时两桶是满的,即Tc和Tp初始值分别等于CBS和PBS。
双速率三色标记算法关注的是速率的突发,但它不像单速率三色标记算法那样把第一个桶中未使用的令牌放到第二个桶中,而是使用两个独立的令牌桶。第一个令牌桶为PIR,大小为PBS,第二个令牌桶为CIR,大小为CBS。数据的测量是先比较PIR,然后再比较CIR。也就是在双速率三色标记中,首先判断的是数据发送速率是否符合规定的突发要求,而不是正常情况下的色标方法。
双速率三色标记算法也有色盲模式和感色模式两种。
在色盲模式下,当包速率大于PIR,此时未超过Tp+Tc部分的包会分别从P桶和C桶中获取令牌,而且从P桶中获取令牌的部分包被标记为黄色,从C桶中获取令牌的部分包被标记为绿色,超过Tp+Tc部分无法得到令牌的包被标记为红色;当包速率小于PIR,而大于CIR时,包可以得到令牌,但超过Tc部分的包将从P桶中获取令牌,此时这部分包都被标记为黄色,而从C桶中获取令牌的包被标记为绿色;当包速率小于CIR时,包所需令牌数不会超过Tc,只需从C桶中获取令牌,包被标记为绿色。
在感色模式下,如果包已被标记为红色,或者超过Tp+Tc部分无法得到令牌的包,被标记为红色;如果标记为黄色,或者超过Tc但未超过Tp部分包记为黄色;如果包被标记为绿,或者未超过Tc部分包,被标记为绿色。
实际中比较常见的有两种实现方式:
当前业界都采用的第二种方式,第一种方式涉及定时器精度不太好控制。
由于报文过令牌桶的时候我们很容易能获取到的是报文长度和时间,这两个度量我们分别取单位为:
1个字节和1个cycle,然后我们使用这两个单位来展开令牌桶的换算:
1秒时间单位内有CIR(CIR已经换算成字节了)个字节,而1秒的时间单位有HZ * 1个cycle,利用这层关系我们假设:
CIR * 1个字节 = HZ * 1个cycle,换算后我们可以得到两个公式:
1个字节 = HZ/CIR * 1个cycle
1个cycle = CIR/HZ * 1个字节
两种实现方式的基本思想是一直的,只是细节上有一些差别,我们下面具体来看
根据公式:1个字节 = HZ/CIR * 1个cycle 我们来实现第一种方式的令牌桶,也是DPDK当前的实现方式,代码分散在dequeue的各个出队状态机里面。
当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;
上面的实现由两个地方可以优化:
1、diff_bytes 使用了除法,这个代码是很昂贵的,DPDK 实现了一个使用移位代替除法的算法,来提高性能,但没怎么看明白。
我们可以使用空间换时间的想法,使用以下的方法,提高整个过桶的过程:
创建令牌桶的时候,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,没有实现借贷,可能会导致报文不平滑,举个例子:
……………………
整个过程如下表所示:
序号 |
时间(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的令牌,整个报文输出表现的更为平滑。
根据公式:1个cycle = CIR/HZ * 1个字节,我们来实现第二种方式的令牌桶。
当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位存储。