linux中如何配置路由负载均衡是一个古老的问题,可是至今仍然没有什么好的解决方案,我指的解决方案是配置意义上的,如果可以触动内核源代码的话,补丁倒是有好几个,不过很多的linux服务器是不允许给内核打补丁的,成本太高了,要停机,编译同版本内核,如果不同版本内核还要编译所有驱动,测试应用兼容性等等。因此如果有人让你配置负载均衡了,那么你能做到的也就是仅仅“看起来像那么回事”罢了,所谓看起来像的意义就是在用户空间实现一个脚本,每隔一段时间刷新内核的路由表,而路由表中配置多条到同一目的地的路由,如此一来在每次刷新完路由表后,再有数据包过来的时候就会重新查找路由了,结果就可能查到不同的路由,从而实现负载均衡(看起来像),但是如果你要是配置了MASQUERADE target的NAT,你可要当心了,会有问题的,看一下内核源码就明白了:
static unsigned int masquerade_target(...)
{
...
    struct rtable *rt;
    ct = ip_conntrack_get(*pskb, &ctinfo);
    mr = targinfo;
    {
        struct flowi fl = { .nl_u = { .ip4_u =
                          { .daddr = (*pskb)->nh.iph->daddr,
                        .tos = (RT_TOS((*pskb)->nh.iph->tos) |
                            RTO_CONN),
                          } } };
        if (ip_route_output_key(&rt, &fl) != 0) { //重新查找
            return NF_DROP;
        }
        if (rt->u.dst.dev != out) {//如果在从路由表或者cache中得到out和上面调用ip_route_output_key之间在用户空间调用了ip route flush cache的话,那么由于重新查找fib得到的dst.dev就可能会变化(为了负载均衡配置多个weight值相等到同一目的地的路由,因此选择的结果可能是随机的)
            if (net_ratelimit())
                printk("MASQUERADE:" //打印出错信息
                       " Route sent us somewhere else./n");
            ip_rt_put(rt);
            return NF_DROP;
        }
    }
...
}
      实际上,即使修正上上述问题,也就是不再使用MASQUERADE target,而是使用SNAT --to-source,负载均衡结果也不会令人满意,真正的负载均衡在linux内核(排除内核patch,仅讨论mainline)中是不做的(不知道别的OS是否做到,起码IOS能做到),所谓的真正就是基于路由cache的负载均衡而不是基于路由表即fib的负载均衡,基于fib的负载均衡实际上很没有意义,毕竟内核路由模块几乎总是首先从cache中获取路况信息,即使你在fib中做了负载均衡,如果cache中已经有了转发信息的话,内核是不会再去查表的,因此负载均衡也就失去了意义,一种做法是每隔一定的时间调用一次ip route flush cache进行路由表cache的刷新,但是这么做的代价太大了,一切能从cache中获得的优点全被flush掉了,因此这种方式并不合理,它仅仅看起来实现了负载均衡,但是没有达到负载均衡的效果。
      负载均衡可以分为基于流的和基于包的,基于流的实现更合理但是有的时候必须需要基于包的,只可惜,linux内核并没有很好的实现基于包的负载均衡,我们看看这是为什么,究其原因就是因为2.6早期的内核实现了一个multipath的机制,可以认为是一个负载均衡器,但是它却是基于fib的,可以认为是基于流的,因为每一个流的第一个包从cache中或者fib中查找到路由后项后就会cache起来,然后后续的包直接就是用cache的路由信息了,以2.6.8内核为例:
ip_route_input_slow(...)
{
...
#ifdef CONFIG_IP_ROUTE_MULTIPATH  //只有在定义了该宏的时候才启用“负载均衡”
    if (res.fi->fib_nhs > 1 && fl.oif == 0)  //下一跳多于一个的情况下进行均衡
        fib_select_multipath(&fl, &res); //该函数实现了一个简陋的随机查找操作,在所有的下一跳中随机选择一个出来作为结果
#endif
...
//后续的make route cache操作,就将该项路由信息插入cache了
...
}
一旦路由信息进入了cache,那么后续的包就会使用这个cache的路由信息被转发,因此起码2.6.8的内核不支持真正的基于包的cache路由负载均衡操作,如果你非要做到看起来像负载均衡,那就调用下面的脚本吧:
#!/bin/bash
while [ 1 ]; do
    sleep $1
    ip route flush cache
done
然后测试一下性能就知道结果了,很无奈!
到了2.6.18内核,内核增加了一个预编译宏--CONFIG_IP_ROUTE_MULTIPATH_CACHED,看起来支持了cache路由的负载均衡,然后查一下代码:
static inline int multipath_select_route(const struct flowi *flp,
                     struct rtable *rth,
                     struct rtable **rp)
{
#ifdef CONFIG_IP_ROUTE_MULTIPATH_CACHED
    struct ip_mp_alg_ops *ops = ip_mp_alg_table[rth->rt_multipath_alg];
    if (ops && (rth->u.dst.flags & DST_BALANCED)) {
        ops->mp_alg_select_route(flp, rth, rp);  //以一个回调函数的方式实现,这样就可以策略化地实现选择多个路由中的一个了,可以是基于当前负载的,也可以是基于weight的,还可以是随机的
        return 1;
    }
#endif
    return 0;
}
看起来不错,但是可以看到,只有在__ip_route_output_key的时候才会调用这个函数,如果linux作为一个路由器,那么它大多数是做forward的,此时就不能使用负载均衡了,可能是因为forward对速度要求很高,而查找多路径操作相对耗时,并且这种需求也不是必须的,故而在2.6.25内核中它就不见了踪影,因此,linux内核最终还是没有将cache路由的负载均衡并入内核的mainline而只能通过补丁来实现,当前有一个2.4内核的补丁,实现得很差劲,其思想几乎和用户空间每间隔一段时间flush掉路由cache一样,只不过它只是flush掉相关的cache而不是将cache全部flush掉,代码如下:
#ifdef CONFIG_IP_ROUTE_MULTIPATH
-    if (res.fi->fib_nhs > 1 && key.oif == 0)
+    if (res.fi->fib_nhs > 1 && key.oif == 0) {
         fib_select_multipath(&key, &res);
+        if (res.fi->fib_flags&RTM_F_EQUALIZE)
+            flags |= RTCF_EQUALIZE;  //打上标志,待日后判断时候清除cache时使用
+    }
ip_route_input:
+    if (rth->rt_flags&RTCF_EQUALIZE) {
+        *rthp = rth->u.rt_next;
+        rth->u.rt_next = NULL;
+        rt_free(rth);
+        break;  //这里就是强制执行路径进入后面的slow函数
+    }
ouput的时候同样进行上面类似input的patch,如此看来,这个补丁为了负载均衡几乎放弃了内核对路由cache带来的好处,确实不怎么样,起码也要启动一个定时器,然后每间隔一段时间再清除掉该路由cache,而不是每次都清除掉,基于hash的cache查找要比查询fib快得多。由此,linux几乎没有拿出一个像样的基于包的负载均衡方案,这难道说明linux不好吗?不是的,因为基于包的负载均衡会带来很多的问题,我们看一下会产生什么问题。基于包的负载均衡在配置nat情况下会对基于流的连接造成影响,甚至致命的影响,负载均衡最直观的感觉就是同一个流的数据分散到不同的出口,而不同的出口会有不同的ip地址,在这些出口做SNAT就会产生问题,因为对于同一个流来讲最终的目的地只能用一个源地址而不是两个或者多个,因此连接可能会莫名的断开。也不是都会出问题,像udp这类连接可能就没有影响,只要最终目的地不在乎源地址就可以,但是谁能保证目的地如何配置呢?另外一个问题就是公网中很多网络机制都是基于流的,如果在负载均衡器的N多个出口做了nat,那么这些路径的汇聚处就会把同一个流当成不同的流,不但策略配置复杂化了,而且还会出现重复误判等问题。因此多路径路由真的会带来很多问题,它能解决的问题敌不过为了实现它而付出的成本。虽然基于包的负载均衡不值得实现,那么基于流的呢?基于流的负载均衡实际上linux已经实现了,这就是multipath,也就是2.6.8内核中就已经存在的那种,一个流的源IP,目的IP都是一样的,因此该流使用一个路由cache项,而另一个流由于和第一个流的源不同,因此只能查找fib,得到对应同一个目的地址的另一个路由表项,但是如果同一台机器发起了到同一个目的地的多个流,linux仍然不会做负载均衡,linux会认为那是一个流,毕竟在IP层是认不到传输层协议和端口的。因此linux实现的基于流的负载均衡仅仅是一种粗粒度的基于主机的负载均衡,要想实现真的基于流的负载均衡,还要使用ip_conntrack模块,但是这样就涉及到了四层信息,比较耗时.最终的结论就是linux的负载均衡几乎没有实现.
      可是即使是基于包的负载均衡,在有一种情况还是很有意义的,不但不会带来问题,而且真的可以提高性能,那就是隧道中的负载均衡,由于隧道的两端并不是数据的终点,隧道仅仅是一条路而已,所以也就没有基于流的连接如tcp对同一源的那种苛刻要求了,隧道只要求进入的是什么,出来的也是什么,就此就够了,隧道上的基于包的负载均衡的应用之所以可以不必考虑单一源问题是因为虽然从负载均衡的地方分开成多条路径(隧道),但是这些路由又无一缺漏地聚合于每条路径(隧道)的终点,我们只是对若干隧道的流量做了负载均衡并且可能由于出口不同而改变了源地址(也就是将数据分担到不同的隧道中),并没有触及隧道中传输的数据。一条隧道不能建太宽,但是却可以修多条。

MASQUERADE target在负载均衡中引出的问题相关推荐

  1. 负载均衡中SNA的ip-can(iscs使用示例i)

    负载均衡中SNA的ip-can(iscs使用示例i) 虽然上篇博文的方法已经提高了冗余及安全性,但是可以看出两个网站是存放在不同的服务器上的,在实际的工作中,两个网站的内容是完全一样的,并且,它们要有 ...

  2. 负载均衡中使用 Redis 实现共享 Session

    最近在研究Web架构方面的知识,包括数据库读写分离,Redis缓存和队列,集群,以及负载均衡(LVS),今天就来先学习下我在负载均衡中遇到的问题,那就是session共享的问题. 一.负载均衡 负载均 ...

  3. 一文搞懂负载均衡中的一致性哈希算法

    一致性哈希算法在很多领域有应用,例如分布式缓存领域的 MemCache,Redis,负载均衡领域的 Nginx,各类 RPC 框架.不同领域场景不同,需要顾及的因素也有所差异,本文主要讨论在负载均衡中 ...

  4. 一致性 Hash 在负载均衡中的应用

    点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:干掉 Navicat:这个 IDEA 的兄弟真香!个人原创100W+访问量博客:点击前往,查看更多 转自:Mar ...

  5. hash地址_一致性Hash在负载均衡中的应用

    作者:marklux 原文:http://marklux.cn/blog/90 简介 一致性Hash是一种特殊的Hash算法,由于其均衡性.持久性的映射特点,被广泛的应用于负载均衡领域,如nginx和 ...

  6. 聊聊一致性Hash在负载均衡中的应用

    作者:marklux http://marklux.cn/blog/90 简介 一致性Hash是一种特殊的Hash算法,由于其均衡性.持久性的映射特点,被广泛的应用于负载均衡领域,如nginx和mem ...

  7. 一致性Hash在负载均衡中的应用

    简介 一致性Hash是一种特殊的Hash算法,由于其均衡性.持久性的映射特点,被广泛的应用于负载均衡领域,如nginx和memcached都采用了一致性Hash来作为集群负载均衡的方案.本文将介绍一致 ...

  8. 压力测试过负载均衡_一致性 Hash 在负载均衡中的应用

    点击上方 "程序员小乐"关注, 星标或置顶一起成长 每天凌晨00点00分, 第一时间与你相约 每日英文 If you concentrate on the ONE thing in ...

  9. 一致性 Hash 是什么?在负载均衡中的应用

    点击上方"服务端思维",选择"设为星标" 回复"669"获取独家整理的精选资料集 回复"加群"加入全国服务端高端社群「后 ...

最新文章

  1. JavaScript对象
  2. 【错误记录】Groovy 报错 ( Execution failed for task ‘:compileGroovy‘. > org/apache/tools/ant/taskdefs/Java )
  3. 7-1 最大子列和问题 (20 分)
  4. 2017视频监控行业应用趋势与市场发展分析
  5. 工作总结14:路径错误
  6. jeecg-framework-3.3.2-RELEASE 最新版本发布
  7. 第n小的质数(信息学奥赛一本通-T1099)
  8. 3.5k欧/m,HIIT 博士后招募,共48个项目可选
  9. 使用JDBC来连接数据库
  10. mybatis开启字段自动映射为java驼峰命名规则
  11. yuv格式转换是那个组织定义的_YUV格式
  12. 安徽大学大学计算机网络教学平台,安徽大学-网络教学综合平台.DOC
  13. 游戏鼠标的dpi测试软件,有什么软件可以测试鼠标的dpi或着说cpi – 手机爱问
  14. python写入文件取消自动换行
  15. 【优化模型】每对顶点之间的最短路算法
  16. Excel如何动态获取某个产品最新的库存信息
  17. kangle安装php7.0_搭建Kangle+EasyPanel对接SWAP IDC虚拟主机自助开通完整教程
  18. Python实现将一张图片切成9宫格
  19. MBTI16型人格测试思维导图 | 找到自己的类型
  20. Android wifi信号强弱检测

热门文章

  1. XDM2020 Java版类似IDM微软下载器的开源下载工具编译
  2. 联想 e460 查看 内存卡槽数
  3. python怎么打印字典_Python中的字典
  4. [GXYCTF2019]BabyUpload
  5. 经典背包问题3——背包问题求方案数 、背包问题求具体方案
  6. php源码之路第一章(环境配置及常用代码)
  7. cuda实战阅读笔记:共享内存、常量内存、事件时间
  8. 基于 VIVADO 的 AM 调制解调(3)仿真验证
  9. 单片机万年历阴阳历c语言,单片机阴历阳历c算法万年历.pdf
  10. 玩客云刷上Armbian的体验