免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12下一页
最近访问板块 发新帖
查看: 6423 | 回复: 14
打印 上一主题 下一主题

[网络子系统] 多个进程绑定相同端口的实现分析[Google Patch] [复制链接]

论坛徽章:
36
IT运维版块每日发帖之星
日期:2016-04-10 06:20:00IT运维版块每日发帖之星
日期:2016-04-16 06:20:0015-16赛季CBA联赛之广东
日期:2016-04-16 19:59:32IT运维版块每日发帖之星
日期:2016-04-18 06:20:00IT运维版块每日发帖之星
日期:2016-04-19 06:20:00每日论坛发贴之星
日期:2016-04-19 06:20:00IT运维版块每日发帖之星
日期:2016-04-25 06:20:00IT运维版块每日发帖之星
日期:2016-05-06 06:20:00IT运维版块每日发帖之星
日期:2016-05-08 06:20:00IT运维版块每日发帖之星
日期:2016-05-13 06:20:00IT运维版块每日发帖之星
日期:2016-05-28 06:20:00每日论坛发贴之星
日期:2016-05-28 06:20:00
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2013-07-17 15:46 |只看该作者 |倒序浏览
本帖最后由 Godbach 于 2013-07-17 15:52 编辑

作者:Godbach <nylzhaowei@gmail.com>
Blog:  http://godbach.blog.chinaunix.net  
微博:weibo.com/godbach
日期:Jul 17, 2013
本文可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接

Google REUSEPORT 新特性,支持多个进程或者线程绑定到相同的 IP 和端口,以提高 server 的性能。

1. 设计思路

该特性实现了 IPv4/IPv6 下 TCP/UDP 协议的支持, 已经集成到 kernel 3.9 中。

核心的实现主要有三点:

1. 扩展 socket option,增加 SO_REUSEPORT 选项,用来设置 reuseport。
2. 修改 bind 系统调用实现,以便支持可以绑定到相同的 IP 和端口
3. 修改处理新建连接的实现,查找 listener 的时候,能够支持在监听相同 IP 和端口的多个 sock 之间均衡选择


共包含 7 个 patch,其中有两个为 buf fix

数据结构调整: 055dc21a1d1d219608cd4baac7d0683fb2cbbe8a
TCP/IPv4: da5e36308d9f7151845018369148201a5d28b46d
UDP/IPv4: ba418fa357a7b3c9d477f4706c6c7c96ddbd1360
TCP/IPv6: 5ba24953e9707387cce87b07f0d5fbdd03c5c11b
UDP/IPv6: 72289b96c943757220ccc681fe2e22b46e21aced
bug fix: 7c0cadc69ca2ac8893aa162ee80d92a805840909 fix: UDP/IPv4
bug fix: 5588d3742da9900323dc3d766845a53bacdfb5ab fix: 数据结构定义


下面根据该特性的实现,简单介绍 IPv4 下多个进程绑定相同 IP 和端口的逻辑分析。 kernel 代码版本:3.11-rc1。

评分

参与人数 1可用积分 +8 收起 理由
瀚海书香 + 8 赞一个!

查看全部评分

论坛徽章:
36
IT运维版块每日发帖之星
日期:2016-04-10 06:20:00IT运维版块每日发帖之星
日期:2016-04-16 06:20:0015-16赛季CBA联赛之广东
日期:2016-04-16 19:59:32IT运维版块每日发帖之星
日期:2016-04-18 06:20:00IT运维版块每日发帖之星
日期:2016-04-19 06:20:00每日论坛发贴之星
日期:2016-04-19 06:20:00IT运维版块每日发帖之星
日期:2016-04-25 06:20:00IT运维版块每日发帖之星
日期:2016-05-06 06:20:00IT运维版块每日发帖之星
日期:2016-05-08 06:20:00IT运维版块每日发帖之星
日期:2016-05-13 06:20:00IT运维版块每日发帖之星
日期:2016-05-28 06:20:00每日论坛发贴之星
日期:2016-05-28 06:20:00
2 [报告]
发表于 2013-07-17 15:50 |只看该作者
本帖最后由 Godbach 于 2013-07-17 15:55 编辑

2. 数据结构扩展

通用 sock 结构扩展,增加 skc_reuseport 成员,用于 socket option 配置是记录对应 结果:

  1. --- a/include/net/sock.h
  2. +++ b/include/net/sock.h
  3. @@ -140,6 +140,7 @@ typedef __u64 __bitwise __addrpair;
  4.   *        @skc_family: network address family
  5.   *        @skc_state: Connection state
  6.   *        @skc_reuse: %SO_REUSEADDR setting
  7. + *        @skc_reuseport: %SO_REUSEPORT setting
  8.   *        @skc_bound_dev_if: bound device index if != 0
  9.   *        @skc_bind_node: bind hash linkage for various protocol lookup tables
  10.   *        @skc_portaddr_node: second hash linkage for UDP/UDP-Lite protocol
  11. @@ -179,7 +180,8 @@ struct sock_common {

  12.         unsigned short                skc_family;
  13.         volatile unsigned char        skc_state;
  14. -        unsigned char                skc_reuse;
  15. +        unsigned char                skc_reuse:4;
  16. +        unsigned char                skc_reuseport:4;
  17.         int                        skc_bound_dev_if;
  18.         union {
  19.                 struct hlist_node        skc_bind_node;
  20. @@ -297,6 +299,7 @@ struct sock {
  21. #define sk_family                __sk_common.skc_family
  22. #define sk_state                __sk_common.skc_state
  23. #define sk_reuse                __sk_common.skc_reuse
  24. +#define sk_reuseport                __sk_common.skc_reuseport
  25. #define sk_bound_dev_if                __sk_common.skc_bound_dev_if
  26. #define sk_bind_node                __sk_common.skc_bind_node
  27. #define sk_prot                        __sk_common.skc_prot

  28. --- a/net/core/sock.c
  29. +++ b/net/core/sock.c
  30. @@ -665,6 +665,9 @@ int sock_setsockopt(struct socket *sock, int level, int optname,
  31.         case SO_REUSEADDR:
  32.                 sk->sk_reuse = (valbool ? SK_CAN_REUSE : SK_NO_REUSE);
  33.                 break;
  34. +        case SO_REUSEPORT:
  35. +                sk->sk_reuseport = valbool;
  36. +                break;
  37.         case SO_TYPE:
  38.         case SO_PROTOCOL:
  39.         case SO_DOMAIN:
复制代码
bind socket 结构扩展,记录 fastreuseport 和 fastuid。这个会在执行 bind 时做相关 的初始化。其中,fastuid 应该是创建 fd 的 uid。

  1. --- a/include/net/inet_hashtables.h
  2. +++ b/include/net/inet_hashtables.h
  3. @@ -81,7 +81,9 @@ struct inet_bind_bucket {
  4.         struct net                *ib_net;
  5. #endif
  6.         unsigned short                port;
  7. -        signed short                fastreuse;
  8. +        signed char                fastreuse;
  9. +        signed char                fastreuseport;
  10. +        kuid_t                        fastuid;
  11.         int                        num_owners;
  12.         struct hlist_node        node;
  13.         struct hlist_head        owners;
复制代码
对于 TCP 来讲,owners 记录了使用相同端口号的 sock 列表。这个列表中的 sock 也包含 了监听 IP 不同的情况。而我们要分析的相同 IP 和端口 sock 也在该列表中。

3. bind 系统调用
分析该函数的 callpath,就是为了明确 google patch 中如果是绑定相同 IP 和 端口号的 多个 socket 如何成功的通过 bind 系统调用。如果没有该 patch 的话,应该返回 Address in use 之类的错误。

sys_bind()
-> inet_bind() (TCP)
-> sk->sk_prot->get_port(TCP: inet_csk_get_port)

inet_csk_get_port() 根据 bind 参数中指定的端口,查表 hashinfo->bhash

3.1. 初次绑定某端口
初次绑定某个端口的话,应该查表找不到对应的 struct inet_bind_bucket tb,因此要调用 inet_bind_bucket_create 创建一个表项,并作 resue 方面的初始化:
  1. 216 tb_not_found:
  2. 217     ret = 1;                                                                                                                     
  3. 218     if (!tb && (tb = inet_bind_bucket_create(hashinfo->bind_bucket_cachep,
  4. 219                     net, head, snum)) == NULL)
  5. 220         goto fail_unlock;
  6. 221     if (hlist_empty(&tb->owners)) {
  7. 222         if (sk->sk_reuse && sk->sk_state != TCP_LISTEN)
  8. 223             tb->fastreuse = 1;
  9. 224         else
  10. 225             tb->fastreuse = 0;
  11. 226         if (sk->sk_reuseport) {
  12. 227             tb->fastreuseport = 1;
  13. 228             tb->fastuid = uid;
  14. 229         } else
  15. 230             tb->fastreuseport = 0;
  16. 231     } else {
复制代码
226-228 行: 如果 socket 设置了 reuseport 的话,则新建表项的 fastreuseport 置 1, fastuid 也记录下来,应该就是创建当前 socket fd 的 uid

接着调用 inet_bind_hash() 将当前的 sock 插入到 tb->owners 中,并增加计数

  1. 62 void inet_bind_hash(struct sock *sk, struct inet_bind_bucket *tb,                                                                 
  2. 63             const unsigned short snum)
  3. 64 {      
  4. 65     struct inet_hashinfo *hashinfo = sk->sk_prot->h.hashinfo;
  5. 66            
  6. 67     atomic_inc(&hashinfo->bsockets);
  7. 68            
  8. 69     inet_sk(sk)->inet_num = snum;
  9. 70     sk_add_bind_node(sk, &tb->owners);
  10. 71     tb->num_owners++;
  11. 72     inet_csk(sk)->icsk_bind_hash = tb;
  12. 73 }
复制代码
并将 sock 对应 inet_connection_sock 的icsk_bind_hash 执行新分配的 tb。

3.2. 再次绑定相同端口
这次应该就可以找到对应的 tb,因此应该进行如下流程:
  1. 190 tb_found:
  2. 191     if (!hlist_empty(&tb->owners)) {
  3. 192         if (sk->sk_reuse == SK_FORCE_REUSE)
  4. 193             goto success;
  5. 194
  6. 195         if (((tb->fastreuse > 0 &&
  7. 196               sk->sk_reuse && sk->sk_state != TCP_LISTEN) ||
  8. 197              (tb->fastreuseport > 0 &&
  9. 198               sk->sk_reuseport && uid_eq(tb->fastuid, uid))) &&
  10. 199             smallest_size == -1) {
  11. 200             goto success;
  12. 201         } else {
  13. 202             ret = 1;
  14. 203             if (inet_csk(sk)->icsk_af_ops->bind_conflict(sk, tb, true)) {
  15. 204                 if (((sk->sk_reuse && sk->sk_state != TCP_LISTEN) ||
  16. 205                      (tb->fastreuseport > 0 &&
  17. 206                       sk->sk_reuseport && uid_eq(tb->fastuid, uid))) &&
  18. 207                     smallest_size != -1 && --attempts >= 0) {
  19. 208                     spin_unlock(&head->lock);
  20. 209                     goto again;
  21. 210                 }
  22. 211
  23. 212                 goto fail_unlock;
  24. 213             }
  25. 214         }
  26. 215     }
复制代码
195-196 为 socket reuse 的判断,并且非 LISTEN 的认为可以 bind,如果已经处理 LISTEN 状态的话,这里的条件不成立

197-198 为 Google patch 的检测,tb 配置启用了 reuseport,并且当前 socket 也设置 了reuseport,且 tb 和当前 socket 的 UID 一样,可以认为当前 socket 也可以放到 bind hash 中,随后会调用 inet_bind_hash 将当前 sock 也加入到 tb->owners 链表中。

4. listen 系统调用
sys_listen -> inet_listen -> inet_csk_listen_start

关键的实现就在 inet_csk_listen_start 中。重要的检测主要是再次检查端口是否可用。 因为 bind 和 listen 的执行有时间差,完全有可能被别的进程占去:
  1. 769     sk->sk_state = TCP_LISTEN;
  2. 770     if (!sk->sk_prot->get_port(sk, inet->inet_num)) {
  3. 771         inet->inet_sport = htons(inet->inet_num);                                                                                 
  4. 772   
  5. 773         sk_dst_reset(sk);
  6. 774         sk->sk_prot->hash(sk);
  7. 775   
  8. 776         return 0;
  9. 777     }   
复制代码
774 行调用 sk->sk_prot->hash(sk) 将对应的 sock 加入到 listening hash 中。 对于 TCP 而言, hash 指针指向 inet_hash()。这里记录下 listen socket 的 hash 的计算逻辑:

inet_hash
->__inet_hash(sk)
->inet_sk_listen_hashfn
->inet_lhashfn
  1. 238 /* These can have wildcards, don't try too hard. */
  2. 239 static inline int inet_lhashfn(struct net *net, const unsigned short num)                                                         
  3. 240 {
  4. 241     return (num + net_hash_mix(net)) & (INET_LHTABLE_SIZE - 1);
  5. 242 }
复制代码
对于 listening socket,可以看出,应该是按照端口做 key 的,最终将 socket 放到了 listening_hash[] 中。

因此,绑定同一个端口的多个 listener sock 最后是放在了同一个 bucket 中。

5. 接受新连接
这里主要就是重点观察 TCP 协议栈将新建连接的请求分发给绑定了相同 IP 和端口的不同 listening socket。

tcp_v4_rcv
-> __inet_lookup_skb
-> __inet_lookup
->  __inet_lookup_listener (新建连接,只能通过 listener hash 查到其所属 listener)

__inet_lookup_listener 函数增加两个参数,saddr 和 sport。没有 Google patch 之前, 查找 listener 的话是不需要这两个参数的:
  1. 177 struct sock *__inet_lookup_listener(struct net *net,                                                                              
  2. 178                     struct inet_hashinfo *hashinfo,
  3. 179                     const __be32 saddr, __be16 sport,
  4. 180                     const __be32 daddr, const unsigned short hnum,
  5. 181                     const int dif)
  6. 182 {
  7. ... ...
  8. 191 begin:
  9. 192     result = NULL;
  10. 193     hiscore = 0;
  11. 194     sk_nulls_for_each_rcu(sk, node, &ilb->head) {
  12. 195         score = compute_score(sk, net, hnum, daddr, dif);
  13. 196         if (score > hiscore) {
  14. 197             result = sk;
  15. 198             hiscore = score;
  16. 199             reuseport = sk->sk_reuseport;
  17. 200             if (reuseport) {
  18. 201                 phash = inet_ehashfn(net, daddr, hnum,
  19. 202                              saddr, sport);
  20. 203                 matches = 1;
  21. 204             }
  22. 205         } else if (score == hiscore && reuseport) {
  23. 206             matches++;
  24. 207             if (((u64)phash * matches) >> 32 == 0)
  25. 208                 result = sk;
  26. 209             phash = next_pseudo_random32(phash);
  27. 210         }
  28. 211     }
复制代码
该函数就是根据 sip+sport+dip+dport+dif 来查找合适的 listener。在没加入 google REUSEPORT patch 之前,是没有 sip 和 sport 的。这两个元素就是用来帮助在多个监 听相同 port 的 listener 之间做选择,并可能尽量保证公平。

这里有个函数调用 compute_score(),用来计算匹配的分数,得分最高的 listener 将作为 result 返回。计算的匹配分数主要是看 listener 的 portnum,rcv_saddr, 目的接口与 listener 的匹配程度。

196-204 行: 查到一个合适的 listener,而且得分比历史记录还高,记下该 sock。同时, 考虑到 reuseport 的问题,根据四元组计算一个 phash,match 置 1.

205 行: 走到这个分支,说明就是出现了 reuseport 的情况,而且是遍历到了第 N 个 (N>1)个监听相同端口的 listener。因此,其得分与历史得分肯定相等。

206-209 行:这几行代码就是实现了是否使用当前 listener 的逻辑。如果不使用的话, 那就继续遍历下一个。最终的结果就会在多个绑定相同端口的 listener 中使用其中一个。 因为 phash 的初次计算中加入了 saddr 和 sport,这个算法在 IP 地址及 port 足够多 的情况下保证了多个 listener 都会被平均分配到请求。

至此,google REUSEPORT 的 patch 简单的分析完毕。

论坛徽章:
10
戌狗
日期:2013-10-17 09:43:0215-16赛季CBA联赛之广东
日期:2018-02-05 11:22:1215-16赛季CBA联赛之八一
日期:2016-07-04 12:26:1815-16赛季CBA联赛之青岛
日期:2016-06-08 11:15:4115-16赛季CBA联赛之辽宁
日期:2016-04-05 10:10:1415-16赛季CBA联赛之辽宁
日期:2016-03-11 11:11:48酉鸡
日期:2014-12-18 14:35:48狮子座
日期:2014-02-20 10:14:07寅虎
日期:2013-12-02 13:48:2915-16赛季CBA联赛之广夏
日期:2018-03-21 08:51:10
3 [报告]
发表于 2013-07-17 15:57 |只看该作者
请教下版主,这个有没有性能分析结果?大致能提高多少性能?

论坛徽章:
36
IT运维版块每日发帖之星
日期:2016-04-10 06:20:00IT运维版块每日发帖之星
日期:2016-04-16 06:20:0015-16赛季CBA联赛之广东
日期:2016-04-16 19:59:32IT运维版块每日发帖之星
日期:2016-04-18 06:20:00IT运维版块每日发帖之星
日期:2016-04-19 06:20:00每日论坛发贴之星
日期:2016-04-19 06:20:00IT运维版块每日发帖之星
日期:2016-04-25 06:20:00IT运维版块每日发帖之星
日期:2016-05-06 06:20:00IT运维版块每日发帖之星
日期:2016-05-08 06:20:00IT运维版块每日发帖之星
日期:2016-05-13 06:20:00IT运维版块每日发帖之星
日期:2016-05-28 06:20:00每日论坛发贴之星
日期:2016-05-28 06:20:00
4 [报告]
发表于 2013-07-17 15:58 |只看该作者
具体的代码分析部分有点零碎,主要是顺着个人的疑问,按照流程简单分析一下。

核心的设计思想都已经在 1 楼介绍了。

论坛徽章:
36
IT运维版块每日发帖之星
日期:2016-04-10 06:20:00IT运维版块每日发帖之星
日期:2016-04-16 06:20:0015-16赛季CBA联赛之广东
日期:2016-04-16 19:59:32IT运维版块每日发帖之星
日期:2016-04-18 06:20:00IT运维版块每日发帖之星
日期:2016-04-19 06:20:00每日论坛发贴之星
日期:2016-04-19 06:20:00IT运维版块每日发帖之星
日期:2016-04-25 06:20:00IT运维版块每日发帖之星
日期:2016-05-06 06:20:00IT运维版块每日发帖之星
日期:2016-05-08 06:20:00IT运维版块每日发帖之星
日期:2016-05-13 06:20:00IT运维版块每日发帖之星
日期:2016-05-28 06:20:00每日论坛发贴之星
日期:2016-05-28 06:20:00
5 [报告]
发表于 2013-07-17 16:38 |只看该作者
回复 3# daniel_11

我了解到的一个 demo 测试,启动 8 个进程,相对于单进程,新建方面有 3 倍的提升。


   

论坛徽章:
10
戌狗
日期:2013-10-17 09:43:0215-16赛季CBA联赛之广东
日期:2018-02-05 11:22:1215-16赛季CBA联赛之八一
日期:2016-07-04 12:26:1815-16赛季CBA联赛之青岛
日期:2016-06-08 11:15:4115-16赛季CBA联赛之辽宁
日期:2016-04-05 10:10:1415-16赛季CBA联赛之辽宁
日期:2016-03-11 11:11:48酉鸡
日期:2014-12-18 14:35:48狮子座
日期:2014-02-20 10:14:07寅虎
日期:2013-12-02 13:48:2915-16赛季CBA联赛之广夏
日期:2018-03-21 08:51:10
6 [报告]
发表于 2013-07-17 17:12 |只看该作者
回复 5# Godbach
了解,多谢!


   

论坛徽章:
36
IT运维版块每日发帖之星
日期:2016-04-10 06:20:00IT运维版块每日发帖之星
日期:2016-04-16 06:20:0015-16赛季CBA联赛之广东
日期:2016-04-16 19:59:32IT运维版块每日发帖之星
日期:2016-04-18 06:20:00IT运维版块每日发帖之星
日期:2016-04-19 06:20:00每日论坛发贴之星
日期:2016-04-19 06:20:00IT运维版块每日发帖之星
日期:2016-04-25 06:20:00IT运维版块每日发帖之星
日期:2016-05-06 06:20:00IT运维版块每日发帖之星
日期:2016-05-08 06:20:00IT运维版块每日发帖之星
日期:2016-05-13 06:20:00IT运维版块每日发帖之星
日期:2016-05-28 06:20:00每日论坛发贴之星
日期:2016-05-28 06:20:00
7 [报告]
发表于 2013-07-17 18:36 |只看该作者
回复 6# daniel_11

:wink:
   

论坛徽章:
0
8 [报告]
发表于 2013-07-18 13:07 |只看该作者
支持下楼主

论坛徽章:
36
IT运维版块每日发帖之星
日期:2016-04-10 06:20:00IT运维版块每日发帖之星
日期:2016-04-16 06:20:0015-16赛季CBA联赛之广东
日期:2016-04-16 19:59:32IT运维版块每日发帖之星
日期:2016-04-18 06:20:00IT运维版块每日发帖之星
日期:2016-04-19 06:20:00每日论坛发贴之星
日期:2016-04-19 06:20:00IT运维版块每日发帖之星
日期:2016-04-25 06:20:00IT运维版块每日发帖之星
日期:2016-05-06 06:20:00IT运维版块每日发帖之星
日期:2016-05-08 06:20:00IT运维版块每日发帖之星
日期:2016-05-13 06:20:00IT运维版块每日发帖之星
日期:2016-05-28 06:20:00每日论坛发贴之星
日期:2016-05-28 06:20:00
9 [报告]
发表于 2013-07-18 13:24 |只看该作者
回复 8# 奶茶dsk
你这个 ID 让我想到了 奶茶MM


   

论坛徽章:
6
金牛座
日期:2013-10-08 10:19:10技术图书徽章
日期:2013-10-14 16:24:09CU十二周年纪念徽章
日期:2013-10-24 15:41:34狮子座
日期:2013-11-24 19:26:19未羊
日期:2014-01-23 15:50:002015年亚洲杯之阿联酋
日期:2015-05-09 14:36:15
10 [报告]
发表于 2013-07-19 08:19 |只看该作者
回复 1# Godbach
话说God兄好及时啊,这3.11-rc1内核才发布几天把分析贴出来了。赞一个!

   
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP