浅谈linux下基于UDP服务的负载均衡方法( 六 )


6.1.子问题:UDP socket和4元组以及处理进程的对应关系UDP无连接 , 它无法“记住“对端 , 因此也就不知道一个数据包的前置和后续 , 也就是说UDP不知道它的前置是否曾经来过 , 也不晓得它的后继还会不会来以及如果还会来的话 , 还会来多久 , 因此就无法为其预先分配资源 , 而数据处理资源的容器是什么?是进程(爱较真儿的又要说还有线程且线程更好了...)或者线程 。无法预先分配资源是多处理的一大障碍!对于TCP而言 , 这很简单 , 一个syn包到来 , 理论上就可以为其分配资源了 , 典型得就是创建一个进程 , 最后一个fin或者reset之类的到来就知道该结束进程了 , 只是说系统没有这么实现罢了 , 而是把满三次握手成功后最为资源分配点 , 最先分配的是一个socket , 也就是accept返回的那个 , 然后使其实际情况 , 是要fork呢 , 要继续select/poll呢 , 还是要event poll呢 , 由调用者决定 。对于UDP , 很难 , 一个UDP包的到来 , 它可能就是唯一的一个 , 比如DNS查询 , 也可能是一次会话的开始 , 比如DTLS或者OpenVPN连接 , 但是仅凭第四层 , 你很难区分 。你几乎不可能为单独的UDP 4元组分配一个单独的进程 , 几乎不可能 。
那么怎么办?只能当靶子守株待兔了 , 事先创建多个进程 , 一个进程处理一个socket , 然后静等 , 等待有数据包来命中 , 至于撞到了哪一个socket , 由第四层的socket查找算法决定!
7.问题3:连接关闭的问题以及何时结束进程的问题-UDP多路负载均衡方案并不通用本文的讨论 , 最终选择了方案b , 但是还有一个问题 , 那就是你无法在外面得知进程什么时候该退出了的消息 , 因为UDP不像TCP那样有一系列的诸如FIN , RST之类的标志表示连接断开 , 即使像OpenVPN那样的”有连接的UDP“ , 也无法在协议层面上得知 , 它的有连接是第七层的连接 , 也许你不知道 , 我可以告诉你 , OpenVPN有一个叫做exit-notify的参数 , 可以代表连接的断开...即使像底层的ip_conntrack那样 , 也无法更好地去对待UDP的”连接状态“ , 虽然它真的追踪了UDP的4元组信息 , 俨然它已经成了有连接的协议(这难道不是它的主要工作吗?否则愧对于自己的名字啊) 。除了超时这种机制之外 , 没有更好的了 , 毕竟当所有的裁决手段都失效的时候 , 一切都要靠时间来冲淡 。然而即使是时间 , 也对第七层的逻辑无能为力 , 在这个意义上 , 你要么狠一点 , 它可能很不准 , 要么准一点 , 但可能很不狠 , 狠和准 , 看你怎么选 , 对于OpenVPN , 我不选 , ...我在等待官方新版本...
UDP无法对应一个4元组 , 但是由于Linux内核在第四层查找socket有明确的算法可以保证相同的4元组始终对应一个socket , 那么就可以保证相同的4元组总是对应一个进程 , 只要保证该进程始终处理该socket即可!但是这并不是绝对的 , 因为按照UDP协议的原始含义 , 它就是数据报协议 , 协议假设任意两个数据报都是没有任何关联的 , 因此协议栈第四层的socket查找逻辑不应该总是保证相同的4元组对应一个socket!然而UDP除了保持其数据报的语义外 , 还是一种真真切切的传输层协议 , 是一种IP协议的端到端映射 , 这就意味着你可以在其上面建立任何的”流“ , 虽然这不是协议栈的职责 , 但是Linux的socket查找算法帮我们做到了最重要的事 , 如果在__udp4_lib_lookup调用的inet_ehashfn中:
static inline unsigned int inet_ehashfn(struct net *net,const __be32 laddr, const __u16 lport,const __be32 faddr, const __be16 fport){return jhash_3words((__force __u32) laddr,(__force __u32) faddr,((__u32) lport) << 16 | (__force __u32)fport,inet_ehash_secret + net_hash_mix(net));}稍微改变了一下方略 , 引入了一个random , 那么以上的所有负载均衡方案全部报废!


推荐阅读