[注:本文【原理】部分来自转载,转载来源为https://blog.csdn.net/wuheshi/article/details/79128044,此部分原创作者暂未找到,但感谢原作者的知识贡献]
HASH表介绍:
在交换机内部,每创建一个聚合组时,底层同时创建对应该聚合组的一个hash表,该表存在于交换芯片上,hash表内容如下(简化):左列index为芯片的硬性支持,现在一般是256,512,1024,更高的未见过。Index(索引值)数量越高,负载分担越均衡。这儿以3个成员为例:
index | interface |
---|---|
0 | eth0_0 |
1 | eth0_1 |
2 | eth0_2 |
3 | eth0_0 |
4 | eth0_1 |
5 | eth0_2 |
… | … |
1022 | eth0_0 |
1023 | eth0_1 |
一、HASH表维护:
交换机里有专门的线程,实时检测聚合组有效成员,一旦成员状态发生变化,立即刷新hash表项。顺便谈谈刷新HASH表这个技术。工程师up/down成员口,底层就必须实时的刷新成员(这儿比较考验厂商技术),刷新速度越慢,成员状态变动时丢包越多。技术最强的如cisco,可以做到up/down成员口时,不丢包。而我公司最初会丢一秒钟的包(研发设计思路问题)。后来优化后才达到up/down成员端口,有0.0几秒的丢包,无法做到不丢包。up/down分析:当工程师在命令行up/down聚合组成员时,底层表项会有那么一丁点儿的响应时间刷新表项,这丁点儿时间,已经down掉的接口还存在hash表里,而报文是一直都有的,正好被hash到这个无效的出端口的报文都会被丢弃!)
交换机负载均衡转发原理:
虽然底层有了一张HASH表,那么到底是怎么利用这张表的呢?
1)工程师设定端口成员与HASH算法,如SIP、DIP、SIP+DIP、SIP+DIP+SP+DP等。
2)交换机根据成员生成HASH表,根据算法提取报文中相应内容。
3)使用特定HASH值的计算方法,把提取的内容计算出一个10bits的值。
4)找到底层HASH表项中该值对应的出端口。
5)把报文从这个出端口转发出去。
二、HASH值的计算方法:
xor是异或运算,即两个值不相同,则异或结果为真;反之,为假。不同为1,相同为0。
1、SIP(源IP)
1)SIP xor 0 得到一个32bit的值.
2)然后作高16bits和低16bits的xor.
3)再用16bits的15-12bits与11-8bitsxor,将得到的4bits替换到11-8bits,得到12bits右移2位得到10bits的hash值
注:10bits的值必然是0-1023里的一个数,该index对应的interface是多少,就从该接口转发出去。(相同的IP必然是相同的hash值)
2、DIP(目的IP)
同SIP
3、SIP+DIP(源IP+目的IP)
1)DIP xor SIP得到一个32bits的值。
2)然后作高16bits和低16bits的xor。
3)再用16bits的15-12bits与11-8bitsxor,将得到的4bits替换到11-8bits,得到12bits右移2位得到10bits的hash值。
4、SIP+DIP+SP+DP(源地址 + 目的地址 + 源端口 + 目的端口)
1)SIP xor DIP得到32bit的值value1;
2)hashtemp1的低16bits xor SP 得到32bit的hashtemp2;
3)hashtemp2 的低 16bit xor DP 得到 32bit 的hashtemp3;
4)然后作高16bits和低16bits的xor5)再用16bits的15~12bits和11~8bits xor,将得到的4bits替换到11~8bits,得到12bits右移2位得到10bits的hash值。
本部分是基于源IP地址进行哈希运算,从而计算出哈希值,并通过这个哈希值(上文描述的索引值)选择对应的物理成员接口转发出去。
算法模拟程序运行结果如下:
以下是关键代码的解释:
通过对模拟程序计算的结果来看,按照SIP(源IP地址)进行哈希运算,并通过最后的索引值来决定转发的物理成员接口,我们发现该大部分的IP地址都会从程序中设定的1号物理成员接口转发出去,剩下的IP地址才是从2号物理成员接口进行转发。
这样的结果就意味着,各个物理成员接口流量的分摊比例是不同的,通俗点讲,可能就会出现1号物理口承担了全网80%的流量,2号物理口只承担了全网20%的流量,源IP这样的聚合算法(负载分担模式)并不是绝对平均的。
其它的聚合算法也会有这种“不绝对平均”的问题,不过一般说来,初始组合值越长,比如“源MAC+目的MAC+源IP+目的IP”这样哈希值,经过运算之后,会达到一种更加平均的流量走向。
在某些对流量负载比较严格的环境下,必须要考虑不同链路的分摊比例对网络及其业务造成的影响,并且还要考虑将链路资源浪费的情况降到最低,也要保证各个链路不要出现网络拥塞。
聚合算法是实现链路聚合的原理实现,是前提或基础,而手工负载分担和静态LACP是链路聚合根据应用场景的不同使用的两种模式,他们的前提和基础仍旧是聚合算法,在配置上他们是不冲突的。
为验证上面所有描述的正确性,我们使用HCL(华三云实验室)模拟器进行验证:(经过程序模拟,192.102.20.54与1.1.1.1不会从同一个物理接口转发出)
网络拓扑如下:
通过对左侧交换机的两个物理出接口抓包,其结果如下:
可以看出,通过HCL模拟器的运行结果和上述C程序计算结果一致。
/*
**程序:交换机链路聚合负载均衡算法(源IP)
**作者:木下^_俱欢颜*
**备注:此程序假设前提聚合接口的物理成员接口只有两个
**IDE:Visual Studio 2017
*/
#include
#include
#include
void Switch_EthTrunk_Hash() {
int S_IPadd[32]; //源IP地址
int S_IPadd_XOR[16]; //高16位与低16位异或后的中间值
int S_IPadd_XOR_Forebit[4]; //整个算法在第三步所产生的4位中间值
int S_IPadd_Hash_num[10]; //整个算法最后得到的10位二进制哈希值
int S_IPadd_Hash_numTen = 0; //声明变量:最后哈希值的十进制表示,初始值为0
int S_IPaddTen4= 0, S_IPaddTen3= 0, S_IPaddTen2 = 0, S_IPaddTen1= 0; //将IP地址以点分十进制所需变量
//以下代码为输入源目IP地址
printf("请输入32位二进制源IP地址(本端交换机IP地址):\n");
for (int i = 0; i < 32; i++) {
printf("本端IP地址第%d位为:", 32 - i);
scanf("%d", &S_IPadd[i]);
}
printf("\n");
//将二进制IP地址转换为点分十进制形式(变量j是次幂的指数,每次进行十进制转换前都应当初始化为7)
int j = 7;
for (int i = 0; i < 8; i++) {
S_IPaddTen4 += S_IPadd[i] * pow(2, j);
j--;
}
j = 7;
for (int i = 8; i < 16; i++) {
S_IPaddTen3 += S_IPadd[i] * pow(2, j);
j--;
}
j = 7;
for (int i = 16; i < 24; i++) {
S_IPaddTen2 += S_IPadd[i] * pow(2, j);
j--;
}
j = 7;
for (int i = 24; i < 32; i++) {
S_IPaddTen1 += S_IPadd[i] * pow(2, j);
j--;
}
printf("您输入的二进制IP地址的点分十进制形式为:%d.%d.%d.%d\n\n",S_IPaddTen4, S_IPaddTen3, S_IPaddTen2, S_IPaddTen1);
/*
**对源地址进行哈希计算(包含三个步骤:)
*/
//1、与0异或得到32bit的值
for (int i = 0; i < 32; i++) {
S_IPadd[i] = S_IPadd[i] ^ 0;
}
printf("算法步骤1--->二进制IP地址与0异或之后的结果为:");
for (int i = 0; i < 32; i++) {
printf("%d",S_IPadd[i]);
}
printf("\n");
//2、之后低16bit与高16bit异或
for (int i = 0; i < 16; i++) {
S_IPadd_XOR[i] = S_IPadd[i] ^ S_IPadd[i + 15];
}
printf("算法步骤2--->步骤1结果的高16位与低16位异或的结果为:");
for (int i = 0; i < 16; i++) {
printf("%d", S_IPadd_XOR[i]);
}
printf("\n");
//3、再用16位的15-12位与11-8位异或,将得到的4bits替换到11-8bits,得到12位数右移2位得到10bits的哈希值
for (int i = 0; i < 4; i++) {
S_IPadd_XOR_Forebit[i] = S_IPadd_XOR[i] ^ S_IPadd_XOR[i + 4];
}
printf("算法步骤3.1--->步骤2结果的16位中第15-12位与11-8位异或,将得到的4位二进制数为:");
for (int i = 0; i < 4; i++) {
printf("%d", S_IPadd_XOR_Forebit[i]);
}
printf("\n");
for (int i = 0; i < 4; i++) {
S_IPadd_XOR[i + 4] = S_IPadd_XOR_Forebit[i];
}
//以下代码为12位数向右移动两位的实现
for (int i = 4; i <= 13; i++) {
S_IPadd_Hash_num[i - 4] = S_IPadd_XOR[i];
}
printf("算法步骤3.2--->步骤3.1生成的10位二进制哈希值为:");
for (int i = 0; i < 10; i++) {
printf("%d", S_IPadd_Hash_num[i]);
}
printf("\n");
printf("算法最终计算的结果如下:\n\n");
if (S_IPadd_Hash_num[9] == 0) {
printf("此源IP地址相关数据包将从Eth-Trunk聚合接口的1号物理口转发出去\n\n");
}
else if (S_IPadd_Hash_num[9] == 1) {
printf("此源IP地址相关数据包将从Eth-Trunk聚合接口的2号物理口转发出去\n\n");
}
else {
printf("数据非法!\n\n");
}
}
int main()
{
Switch_EthTrunk_Hash();
system("pause\n");
return 0;
}