场景:
最近做的一个邮件代理的项目,在服务端相当于开了多个Foxmail,每个foxmail负责接收500个用户的邮件,然后提供web服务。一共大约有6000用户的邮件需要接收。
一个EmailReceiver程序覆盖500个用户,20个线程并发,每2分钟接收一次邮件,每台服务器运行8个程序,服务器并发为160
接收邮件使用的是java mail,imap协议,邮件解析修改自jodd-mail,处理了排重,中文等问题。
问题
为了更快的接收邮件,所以使用Guava提供的缓存来缓存EmailReceiver到邮件Server的session,代码如下:
/**
* receive session缓存.
*/
private static Cache<String, ReceiveMailSession> receiveSessionCache = CacheBuilder
.newBuilder().initialCapacity(1000)
.expireAfterWrite(10, TimeUnit.MINUTES).build();
10分钟过期时间,这样基本所有用户都能复用。
今天做邮箱切换的时候,出现了问题:对方发现我们的服务器连接过多,造成接收邮件失败,出现以下错误:
too many connection,pleasy try later again
每台服务器的连接数高达几千。
按说不应该啊,我们的并发每台服务器只有160,怎么会这么高呢。
排查
第一时间想到了缓存的问题,是否缓存的session一直还保持着连接呢。首先了解下IMAP协议。
IMAP协议和POP3协议的最大区别在于:
支持连接和断开两种操作模式。当使用POP3时,客户端只会连接在服务器上一段的时间,直到它下载完所有新信息,客户端即断开连接。在IMAP中,只要用户界面是活动的和下载信息内容是需要的,客户端就会一直连接在服务器上。对于有很多或者很大邮件的用户来说,使用IMAP4模式可以获得更快的响应时间。
见http://zh.wikipedia.org/wiki/IMAP
由上面的描述可以得出结论,如果客户端的session一直存在,没有关闭,那么这个连接也是一直存在的,我们为了缓存,是一直没有关闭用户的session的。(在其他场景下其实正确做法应该是清除缓存的时候关闭),也就是session一直存在,所有用户都能命中缓存,从而造成了最后整个服务器覆盖的用户,都会对邮箱Server有一个连接,也就是4000的连接。。
相关Linux命令
通过netstat
命令查看服务器,几个选项:
-a 显示一个所有的有效连接信息列表(包括已建立的连接,也包括监听连接请求的那些连接)
-n 显示所有已建立的有效连接
-t tcp协议
-u udp协议
-l 查询正在监听的程序
-p 显示正在使用socket的程序识别码和程序名称
查询命令为netstat -nap |grep java |grep ESTABLISHED |grep 143
,查询已连接的有效连接,并且过滤java
程序,ESTABLISHED
状态,以及143
端口。结果如下:
tcp 0 0 ::ffff:10.103.10.141:55281 ::ffff:10.27.130.247:143 ESTABLISHED 14449/java
tcp 0 0 ::ffff:10.103.10.141:55220 ::ffff:10.27.130.247:143 ESTABLISHED 24508/java
tcp 0 0 ::ffff:10.103.10.141:55287 ::ffff:10.27.130.247:143 ESTABLISHED 14778/java
tcp 0 0 ::ffff:10.103.10.141:55217 ::ffff:10.27.130.247:143 ESTABLISHED 14449/java
tcp 0 20 ::ffff:10.103.10.141:55206 ::ffff:10.27.130.247:143 ESTABLISHED 14449/java
tcp 0 17 ::ffff:10.103.10.141:55257 ::ffff:10.27.130.247:143 ESTABLISHED 10052/java
关于连接状态:
指定要匹配包的的状态,当前有4种状态可用:INVALID,ESTABLISHED,NEW和RELATED。 INVALID意味着这个包没有已知的流或连接与之关联,也可能是它包含的数据或包头有问题。ESTABLISHED意思是包是完全有效的,而且属于一个已建立的连接,这个连接的两端都已经有数据发送。NEW表示包将要或已经开始建立一个新的连接,或者是这个包和一个还没有在两端都有数据发送的连接有关。RELATED说明包正在建立一个新的连接,这个连接是和一个已建立的连接相关的。比如,FTP data transfer,ICMP error 和一个TCP或UDP连接相关。注意NEW状态并不在试图建立新连接的TCP包里寻找SYN标记,因此它不应该不加修改地用在只有一个防火墙或在不同的防火墙之间没有启用负载平衡的地方。
可以查看到具体的java进程和远程的连接,统计命令则可以在后面接wc -l,wc为Word Count的缩写,具体使用参考:
执行结果如下:
[root@localhost ~]# netstat -nap |grep java |grep ESTAB |grep 143 |wc -l
53
不得不说Linux的管道太屌了。。
解决方法
去掉session的缓存,使用完session之后close掉即可。
总结
遇到相关问题处理思路:
- 使用netstat命令查看总体连接占用数,也可以在服务端查看所有到服务端的连接
- 定位每台服务器,每个程序占用的连接,确保程序对连接有控制
- 使用连接池等手段复用连接,注意特殊情况,连接是否有回收,是否和连接池配置或者程序设置一致
- 找到具体实现的地方处理解决问题
比如遇到mongodb的连接问题,可以先在mongodb服务器上查看连接都来自于哪些服务器,每个服务器是否正常,然后在服务器查看程序连接数是否正常,如果不正常,查看连接池等配置及代码。
参考:http://blog.csdn.net/michael493439861/article/details/7418970