Java程序员也应该知道的系统知识系列之网卡

对于编写分布式Java应用的同学而言,网卡的一些知识也是必须掌握的,例如什么是网卡的bonding模式,网卡的中断处理,os相关的参数等。

机器上网卡的型号可通过lspci | grep -i ether来查看,同样在看到网卡的型号后可通过google了解下网卡的状况,对于Java应用而言,主要需要关注的是网卡是否支持多队列,这个可以通过lspci -vvv | grep -i msi-x来查看网卡是否支持多队列,以及支持多少个队列,具体多队列的作用后面讲。

机器上网卡的bonding模式是需要了解的,原因是bonding模式对应用可用的网络带宽会有直接影响,网卡的bonding模式可通过cat /proc/net/bonding/*bond*来查看,看到的信息会类似如下:
Bonding Mode: fault-tolerance (active-backup)
上面这种模式代表一块网卡是激活的,另一块是备份状态,也就是应用能用的网络带宽是一块网卡,例如网卡是千兆的,那意味着应用最多能用的网络带宽是1000m bits(这个还取决于机器所在的网络环境的收敛比),linux支持7种网卡的bonding模式,感兴趣的话可以google看下。

除了bonding模式外,和应用比较相关的另外一点就是网卡的中断处理,这个在之前有专门写过一篇文章,具体请见前面的文章。

除了硬件层面外,os层面有几个和网络相关的重要参数:
net.core.somaxconn
net.ipv4.tcp_max_syn_backlog
这两个参数是用来控制并发建连接时等待的队列长度,当队列满了后,后续连接会直接失败,对于需要支撑较大量的并发连接的server端而言就很重要了,但对于java程序而言,如果是直接new ServerSocket这种的,会把backlog这个值设置为50,因此最好是在写程序的时候能设置下这个值,无论是直接基于java写,还是基于一些通讯框架写,backlog这个都是支持设置的,具体也可以看看这个关于backlog的case

由于每建一个连接其实都是打开一个文件,因此需要关注下进程的open files的限制,可通过cat /proc/[pid]/limits来查看其中的Max open files,这个值建议稍微配大一些(否则很容易出现too many open files的错误),可通过修改/etc/security/limits.conf或直接ulimit -n来设置。

net.ipv4.tcp_tw_reuse
net.ipv4.tcp_tw_recycle
net.ipv4.tcp_tw_timeout
这三个参数主要是用来控制time_out连接的回收的,如果不配置好可能会出现time_wait太多导致连接建不了的现象,其中tcp_tw_recycle最好关闭,在通过lb设备连接的情况下,如果打开这个参数,有些时候会出问题。

net.ipv4.tcp_wmem
net.ipv4.tcp_rmem
读写缓冲区,这个值如果太小,会导致在读写的时候出现网卡阻塞的现象,例如写就会出现写不进网卡的现象,而由于linux默认的值通常太小,尤其是在局域网环境下,由于网络的不可确定性,Java通信程序在编写的时候一定要注意限流,避免由于send buffer满的情况下java heap也被耗光的现象(由于sendbuffer被写满,通常发不出去的数据就会缓存在java heap里)。

网卡的资源使用率状况可以通过sar -n DEV来查看,需要关注的重点还是网络带宽,另外要关注的一点是cpu上处理网卡中断的均衡性状况,这个通过观看cpu的hi/si指标可以看出。

对于编写通信框架或RPC框架的同学而言,小包跑满网卡带宽通常是目标(因为大包的话很容易就跑满了),但要做到难度还是不小的,具体可见我做的一个开源rpc框架nfs-rpc的优化记录(ps: 欢迎大家来一起做优化)。

在网卡带宽跑满的情况下,通常可选择优化方法是压缩,压缩通常可选择的是java自带的zip、lzo以及google的snappy,相比而言其实基本就是在lzo和snappy中做选择就可,在优化了的情况下网络带宽仍跑满而其他资源比较富余的话,可以考虑网卡多块激活,或升级到更高带宽的网卡,例如千兆到万兆等,但这些优化就不仅仅是单机了,而是要整个结构配合。

=============================
题图来源于:http://goo.gl/uMq2Ii
欢迎关注微信公众号:hellojavacases

关于此微信号:
分享Java问题排查的Case、Java业界的动态和新技术、Java的一些小知识点Test,以及和大家一起讨论一些Java问题或场景,这里只有Java细节的分享,没有大道理、大架构和大框架。

公众号上发布的消息都存放在http://hellojava.info上。

netty新建连接并发数很小的case

双12之前的一个case,现在分享下,这个case的现象是当这个应用每秒新建40多个连接的时候,就会经常连接不上,这个应用时是基于netty的,按照我对netty的理解,这不太可能发生,可以来看看这个case解决的那个弯曲,总共折腾了4天,搞的这么弯曲和我有很大关系。

这个应用的背景先简单说一下,是一个提供给手机访问的应用,手机端app和这个应用时采用长连的方式,通讯这层是基于netty。

当时有同学给我反馈这个现象后,就登录到机器上看了看,按照以往经验,通常来说,新建连接并发数支撑的不够大有可能是两原因:
1. 应用在netty建连接的过程中做了耗时的事;
因此我先dump了应用的线程,看到一切正常,boss线程看起来非常空闲;

2. backlog太小;
首先问了下开发代码里有没有设置过backlog,开发告诉我没设置过,于是我翻了下netty的源码确认下默认值,看到backlog的默认值为读取自系统的/proc/sys/net/core/somaxconn,于是查了下系统的这个值,确认系统的这个值已经是调整过的,设置为了2048,然后确认了系统上的tcp_max_syn_backlog是4096,也就是最后work的会是2048(为什么是这样具体可见这篇文章),也就是说应该是够的。

用ss -s观察连接的状况,看到的是synrecv是0,也印证了上面的不是backlog的问题。

到这一步就彻底傻眼了,不知道该用什么方法排查了,于是开始一堆的google,看到的各种说解决新建连接并发低的解决办法,除了调整backlog外,主要是以下两种:
1 关闭tcp_tw_recycle,尝试了(话说这里充分体现了”病急乱投医“的心态,其实我自己都不相信改这参数有用,但想着只是改下参数这种小代价的事,还是试试吧),没任何作用;
2 关闭window scaling,也尝试了,一样没任何作用;

查到这个阶段觉得自己已经无法理解了,于是求助了厂内内核团队对网络这块比较精通的同学,然后又求助了“神”,“神“帮忙看了会后,说主要的问题是现在是每epollWait唤醒一次,只建了一个连接,这导致在大量新建连接请求并发的时候,效率不够高,因此我翻了下代码,发现我本机上看到的代码不是这样的,我本机上看到的netty代码在epollWait唤醒后,是会尝试一直去accept的,但这个应用使用的netty确实不是这样,于是查了下应用的jar包库,发现里面有两个版本的netty(一个是3.2.1.Final,一个是3.6.3.Final),3.2.1确实是每次epollWait后就处理一个,于是通知开发同学把3.2.1去掉,满心期待的认为应该是好了,等开发更新好了后,自己也确认了一次epollWait唤醒后会连续处理很多个建立连接的请求,但悲催的还是没解决问题,具体的netty在这块的改造感兴趣的同学可以看看NioServerSocketPipelineSink这个类(重点看select唤醒后)…

到这步,就彻底郁闷了,话说其实到这步的时候我已经被这个问题困扰了2天多了,于是只好继续google,发现又有提到打开syn cookies的建议,于是尝试了下,竟然真的work了,打开了这个参数后新建连接的并发请求轻松超过100+了。

到此以为已经解决了,但很快开发给我反馈,整个集群开启了这个参数后,连接确实是能建上了,但客户端出现了发了请求后,等不到任何响应的现象,当时还不确定服务器端到底有没有收到请求,于是只好又先关闭了这个参数(话说这个我到现在都不明白为什么这个参数打开后,会出现发请求没响应的现象,求高人解答)。

尽管还是没解决,但毕竟有进展,有的进展就是打开了syn cookies后连接就能建上,而syn cookies只有在backlog满了后才会生效,那也就是说还是backlog满了,从kern的日志也能确认syn cookies确实是work了,到这步就觉得诡异了,明明netty用的默认值就是somaxconn,而每秒新建40多个连接,且boss线程还很空闲的情况下显然不应该出现backlog满的现象,这个时候仔细看了下本机查看的netty代码,才发现我看的是netty 4的代码,而应用用的是netty 3.6.3,悲催,赶紧把netty 3.6.3的代码捞下来看了,才发现在3.6.3里backlog的默认值处理时不一样的,在3.6.3里默认值是50,不是netty写的默认值,是java本身,50那估计真的不一定够,于是就通知开发在代码里先强制设置下backlog为1000。

在开发改代码的过程中,还有一个怀疑点想确认,就是既然是backlog满了,为什么看到的synrecv会是0呢,于是再用netstat -na | grep [port] | grep SYN_RECV -c统计了下,结果发现值基本一直是64,java层面默认设置的是50,linux会将这个值调整为大于这个值的2的n次幂的值,那也就是64,好吧,看到这就彻底可以确定真的是因为backlog太小了造成的(只是话说我不明白为什么ss -s统计出来的synrecv会是0呢,求高人解答)。

等开发改完代码重新发布后,稍微增加了点引流测试了下,轻松支撑每秒200+,客户端建立连接后发请求获取响应也完全ok,问题到此宣告解决。

题外话: 这应用之所以会比较容易出现较多的synrecv,主要是因为手机网络通常是不太稳定的,另外一个原因是这种对外的都很容易带来攻击,而当时刚好这个应用前面的一个用来防syn flood的由于有bug临时关闭了,所以问题暴露的比较明显。

从这个折腾了4天的case的排查过程,大家可以看到其实如果一开始我仔细确认过应用用的netty版本和我本机看的代码是不一致的话,估计很快就会排查出原因就是backlog值太小造成的,所以说折腾了这么多天其实也是自己造成的,这个告诉自己,以后排查问题的时候一定要对出现问题的应用所在的环境更加清楚的确认。

ps: 最后再多啰嗦几句,从这个case还能看到的是netty的版本其实在细节上是一直在改进的,就像这个case里的不同版本的netty在处理连接事件唤醒上,还有backlog的默认值上,所以我一直很强调,对于需要存活很多年的软件而言,选择一个使用范围较广的开源软件是非常重要的,如果自己开发,也许短期能超越,但放到三年、五年这样的范围来看,通常是很难和开源软件去抗衡的(原因是商业公司没多少人会专注在一个领域做三五年的,而开源界这样的人实在是多,说实话,这种case看过太多),所以如果觉得你能做的比开源软件好,还不如去帮助已有的(当然,如果这个领域目前完全没有什么使用面较广的、靠谱的,那自己做一个开源是挺好的),软件的可持续发展能力(除非是一次性软件、做的玩的或就玩个一两年的)是非常非常重要的。

=============================
题图来源于:http://www.prairiemoonbed.com/Time_out_faded.jpg
欢迎关注微信公众号:hellojavacases

关于此微信号:
分享Java问题排查的Case、Java业界的动态和新技术、Java的一些小知识点Test,以及和大家一起讨论一些Java问题或场景,这里只有Java细节的分享,没有大道理、大架构和大框架。

公众号上发布的消息都存放在http://hellojava.info上。