更多精彩内容,欢迎观看:
《云原生网络数据面可观测性最佳实践》——五、 典型问题华山论剑——1 某客户nginx ingress偶发性出现4xx or 5xx(上):https://developer.aliyun.com/article/1221275?spm=a2c6h.13148508.setting.25.15f94f0eKXgo7c
解决办法
int __sys_listen(int fd, int backlog) { struct socket *sock; int err, fput_needed; int somaxconn; sock = sockfd_lookup_light(fd, &err, &fput_needed); if (sock) { somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn; if ((unsigned int)backlog > somaxconn) backlog = somaxconn; err = security_socket_listen(sock, backlog); if (!err) err = sock->ops->listen(sock, backlog); } return err; } int inet_listen(struct socket *sock, int backlog) { struct sock *sk = sock->sk; /* Really, if the socket is already in listen state * we can only allow the backlog to be adjusted. */ if (old_state != TCP_LISTEN) { err = inet_csk_listen_start(sk, backlog); if (err) goto out; tcp_call_bpf(sk, BPF_SOCK_OPS_TCP_LISTEN_CB, 0, NULL); } sk->sk_max_ack_backlog = backlog; err = 0; out: release_sock(sk); return err; }
从listen()系统调用可以发现,sk_max_ack_backlog的取值有两个影响因素:
● listen()调用下发的backlog参数。
● net namespace中的somaxconn配置。
listen()调用是可以反复调用的,而内核则是在传入的backlog参数与somaxconn中取一个更小值生效,所以显而易见,解决问题的方法是将net.core.somaxconn和backlog参数都调整到合适的数值。
对于net.core.somaxconn,需要注意的是,由于这个配置已经netns化,通常netns中的配置要比宿主机上的配置要小,默认为4096,一般情况下是足够的,如果需要调整的话,可以通过initContainer方式进行,也可以通过修改SecurityContext方式进行,点击此处参考。
对于backlog参数的调整,则依赖于用户态的配置,常见客户出现问题都是Java和PHP业务,他们常见的解决办法如下:
Java业务的Tomcat配置修改
需要修改的参数为org.apache.catalina.connector.Connector的acceptCount参数,按照文档说明,acceptCount的默认数值为100,所以默认Tomcat仅能够在单个LISTEN地址上同时并发100条连接,在云原生场景下很容易出问题。
参考文档:https://tomcat.apache.org/tomcat-8.5-doc/config/http.html
PHP业务的php-fpm配置修改
在php-fpm的配置中修改listen.backlog的值即可,在Linux中,默认会将这个值设置为128。考虑到php业务最为典型的使用场景中,会和nginx进行搭配,所以PHP业务出现的问题,还需要考虑到nginx可能带来的影响。
参考文档:https://www.php.net/manual/en/install.fpm.configuration.php
Nginx中的backlog修改
对于nginx来说,修改在进行listen()调用时的backlog参数的方式比较简单,直接在配置文件的server块中修改listen的参数即可,类似于:
server{ listen 8080 backlog=1024; }
参考文档:http://nginx.org/en/docs/http/ngx_http_core_module.html#listen
Go中对backlog的透明化处理
与上述的几种常见的业务类型相比,Go的默认行为有很大的不同,在Linux的设计中,我们可以看到,尽管提供了backlog给用户自行设置,但是还是有somaxconn进行保险,防止设置过大引发超出sever能力限制并发,导致无法保证正常能力范围内的算力。
也就是说,somaxconn和backlog本身的作用其实类似,有一个能做到保险就可以了,在这个基础上,backlog直接设置为somaxconn也就更加简洁,上述PHP的listen.backlog也支持设置为-1从而将backlog直接设置为somaxconn,Go则把这种行为变成了默认的行为:
Go并没有在net.Listen中提供可以配置的backlog参数,而是通过直接获取somaxconn来实现自动设置backlog为somaxconn的效果:
// listenerBacklog is a caching wrapper around maxListenerBacklog. func listenerBacklog() int { listenerBacklogCache.Do(func() { listenerBacklogCache.val = maxListenerBacklog() }) return listenerBacklogCache.val } func maxListenerBacklog() int { fd, err := open("/proc/sys/net/core/somaxconn") if err != nil { return syscall.SOMAXCONN } defer fd.close() l, ok := fd.readLine() if !ok { return syscall.SOMAXCONN } f := getFields(l) n, _, ok := dtoi(f[0]) if n == 0 || !ok { return syscall.SOMAXCONN } if n > 1<<16-1 { return maxAckBacklog(n) } return n }