探讨下RouterOS PCC的工作原理与应用延伸

在讲PCC之前,我先讲讲RouterOS的负载均衡历史,我最早接触的RouterOS版本是2003年看到的v2.7.14,这个版本是有注册机的,2.7.14破解版本在当时很泛滥。那时候RouterOS就能实现WLAN无线传输、防火墙、流控、pppoe认证,web热点认证和策略路由等等,在2003年功能已经非常多了。

当时的多线接入,主要是用在小区,网吧等场景下,如果是不同运营商就配置运营商静态路由表,相同运营商就配置源地址策略路由。最开始相同运营商的网吧还用过ECMP-Equal Cost Multi-Path Routing(等价多路径),但ECMP在nat下是有问题的,因为ECMP会每10分钟重新生成一次路径,到了v2.9版本出现了nth的功能,初步解决多线负载均衡问题,但Nth还是存在bug。

Nth

Nth两个参数“every”和“packet”,Nth通过计数器方式处理。当规则收到数据包,该规则的计数器会增加1,如果计数器匹配值“every”与packet值相等,计数器将重新设置为0,重新计数。 使用Nth将连续的数据包通过计数器分组,比如可以将连续的数据包分配为多个组,重新排列连接序列。

nth匹配特定的第N次收到的数据报的规则,一个计数器最多可以计数16个数据包,包含两个属性:

  • Every – 匹配每every数据报,同时指定Counter计数器值)
  • Packet – 匹配给定的包,如:Nth=3,1(every=3,packet=1),每3个数据包,取第1个

 

下图是一个三线的Nth分流工作原理:

上图,可以看到数据流被Nth分为3个计数器,并根据Packet重新排列数据流的队列。建立一个数组形式的容器,按照数组序列来分发数据包,计数器满3后,将重新设置为0,重新计数,这样按照顺序3个一组走到指定3条外网线路出口。

Nth没有采用哈希算法,仅仅是按照序列分组排队,会出现源目标IP相同会话的数据包无法走同一条线路,nat后多线的出口IP是不一样的,这样会出现要求对源IP校验的站点无法访问的情况,例如,淘宝验证,银行网银验证等等,线路越多出错的概率越大,因为序列分组后,指定的IP数据包走之前线路的几率越小,当时通过指定443端口走一条固定线路来解决,那时https网站还不流行,勉强能解决。

估计在很多用户的强烈要求下MikroTik在v3.24开发了PCC功能。

 

IP Hash取余算法

在说PCC之前,先讨论下IP Hash,IP_hash是根据用户请求的IP地址和端口,映射成哈希值,并做取余算法,根据余数进行的策略分配。使用ip_hash这种负载均衡以后,可以保证用户相同请求的IP会话保证走同一条线路。Ip_hash算法在网络通信方面应用非常广泛 ,很多CDN缓存技术通过对url进行哈希算法,完成内容访问和存储的负载均衡。

上图是3条线路的负载均衡,根据源和目标IP做哈希取余算法,3作为分母。如果4线就用4作为分母,以此类推。可以看到,同一组源和目标IP地址请求,哈希值是一样的,取余数的结果也会是一样,因此相同的源和目标IP会话始终会走相同线路。

 

PCC负载均衡

Hash算法说清楚了,来看看RouterOS的PCC(per-connection-classifier),每次连接分类器。PCC从IP报头中获取选定的字段,并通过哈希算法将选定的字段转换为32位值。然后将此值除以指定的分母,将余数与指定的余数进行比较,如果相等,则标记该数据包。PCC支持从数据包头选定的字段包括src-address、dst-address、src-port、dst-port,进行哈希算法操作。

明白哈希算法后,我们举例下面的一个实例,这是有3条线路的PCC连接标记规则:

/ip firewall mangle add chain=prerouting action=mark-connection 
new-connection-mark=1st_conn per-connection-classifier=both-addresses:3/0

/ip firewall mangle add chain=prerouting action=mark-connection 
new-connection-mark=2nd_conn per-connection-classifier= both-addresses:3/1

/ip firewall mangle add chain=prerouting action=mark-connection 
new-connection-mark=3rd_conn per-connection-classifier= both-addresses:3/2

依次看看三条规则

  • 第一条:取源和目标IP地址字段(both-addresses)计算哈希值取余,得到的哈希值除以3,余数如果为0,即“both-addresses:3/0”action操作标记为1st_conn。
  • 第二条:哈希值除以3 ,余数如果为1,即“both-addresses:3/1”,action标记为2nd_conn,
  • 第三条:哈希值除以3,余数如果为2,即“both-addresses:3/2”,action标记为3rd_conn。

以上标记连接规则只是PCC负载均衡的一部分,还要做mangle的路由标记,和ip route的策略,这里就省略了。完整的配置可以参考

PCC可用于哈希的字段和字段组合包括:both-addresses|both-ports|dst-address-and-port| src-address|src-port|both-addresses-and-ports|dst-address|dst-port|src-address-and-port

Nginx负载均衡与PCC

Nginx在做http服务器的时候,做负载均衡的场景,可以通过IP哈希算法提高后端http服务器的负载均衡访问

以下是nginx的IP哈希的配置

upstream backend{
ip_hash;
server 192.168.88.2:80 ;
server 192.168.88.3:80 ;
server 192.168.88.4:80 ;
}

Nginx还能实现url的哈希算法,这里就不多说了,我提到nginx的负载均衡只是想延申出RouterOS的PCC,之前我们做的PCC是从内到外的多线路出口负载均衡,反过来PCC也可实现从外到内的IP哈希负载均衡,比如内网多服务器的端口映射的负载均衡调度器,PCC还能实现权重比例的调度。

例如:我们需要将80端口同时映射给内网的三台服务器192.168.88.10、192.168.88.20和192.168.88.30,并实现三台服务器端口映射的负载均衡

配置为:在mangle中标记到外网接口IP是192.88.88.2的80端口,做PCC连接标记,分为3组

/ip firewall mangle
add action=mark-connection chain=prerouting dst-address=192.88.88.2 dst-port=
80 new-connection-mark=pcc1 passthrough=yes per-connection-classifier=
src-address:3/0 protocol=tcp

add action=mark-connection chain=prerouting dst-address=192.88.88.2 dst-port=
80 new-connection-mark=pcc2 passthrough=yes per-connection-classifier=
src-address:3/1 protocol=tcp

add action=mark-connection chain=prerouting dst-address=192.88.88.2 dst-port=
80 new-connection-mark=pcc2 passthrough=yes per-connection-classifier=
src-address:3/2 protocol=tcp

在nat做80端口映射到内网的三台服务器192.168.88.10和192.168.88.20,连接标记分别标记为pcc1、pcc2和pcc3:

/ip firewall nat
add action=dst-nat chain=dstnat connection-mark=pcc1 dst-address=
192.88.88.2 dst-port=80 log=yes log-prefix=pcc1 protocol=tcp 
to-addresses=192.168.88.10 to-ports=80

add action=dst-nat chain=dstnat comment=test1 connection-mark=pcc2 dst-address=
192.88.88.2 dst-port=80 log=yes log-prefix=pcc2 protocol=tcp 
to-addresses=192.168.88.20 to-ports=80

add action=dst-nat chain=dstnat comment=test1 connection-mark=pcc2 dst-address=
192.88.88.2 dst-port=80 log=yes log-prefix=pcc3 protocol=tcp 
to-addresses=192.168.88.30 to-ports=80

这个实例我们调整下,如果192.168.88.30下线,只有2台,192.168.88.20的服务器处理能力更强劲,我们希望3份流量,有两份都给192.168.88.20,我们的nat规则可以修改为

/ip firewall nat
add action=dst-nat chain=dstnat connection-mark=pcc1 dst-address=
192.88.88.2 dst-port=80 log=yes log-prefix=pcc1 protocol=tcp 
to-addresses=192.168.88.10 to-ports=80

add action=dst-nat chain=dstnat comment=test1 connection-mark=pcc2 dst-address=
192.88.88.2 dst-port=80 log=yes log-prefix=pcc2 protocol=tcp 
to-addresses=192.168.88.20 to-ports=80

add action=dst-nat chain=dstnat comment=test1 connection-mark=pcc2 dst-address=
192.88.88.2 dst-port=80 log=yes log-prefix=pcc3 protocol=tcp 
to-addresses=192.168.88.20 to-ports=80

如果后端服务器故障,前端的RouterOS可以通过脚本ping监控判断,并调整nat映射规则,避开故障服务器。RouterOS利用PCC也可以对集群的应用提供负载均衡,上面实例通过nat方式,也算代理访问,让我又想起了haproxy。通过非nat的路由调度也是可以通过PCC实现。

RouterOS的PCC功能是不可能去代替nginx或者haproxy,nginx和haproxy是非常优秀的软件,能实现四层和七层的负载均衡,性能上来说RouterOS和他们差的很远,也只能实现四层的负载均衡,只是想告诉大家RouterOS在许多应用场景中更多的思路,感觉我把RouterOS科技树点歪了!

发表评论