月度归档:2015年11月

ParameterInfo properties, attributes and out ref parameters of the methods

引用: http://stackoverflow.com/questions/15636969/parameterinfo-properties-attributes-and-out-ref-parameters-of-the-methods

Well, I'm confused by the properties of the ParameterInfo class.
Unfortunately documentation is not very clear: examples show how to build methods but don't show how these methods look in C#.

Cane somebody tell more about these properties:

  • DefaultValue
  • HasDefaultValue
  • IsIn
  • IsLcid
  • IsOptional
  • IsOut
  • IsRetval

And which combination leads to what method params.
I made a simple program which gives the following output:

Method name M1 void M1(object param)
IL signature: .method public hidebysig instance void M1(object param) cil managed
Method parameter description:
Is passed by reference False
HasDefaultValue=False
IsIn=False
IsLcid=False
IsOptional=False
IsOut=False
IsRetVal=False

Method name M2 void M2(object param = null)
IL signature .method public hidebysig instance void M2([opt] object param) cil managed
Method parameter description:
Is passed by reference False
HasDefaultValue=True
DefaultValue=null
IsIn=False
IsLcid=False
IsOptional=True
IsOut=False
IsRetVal=False

Method name M3 void M3(out object param)
IL signature .method public hidebysig instance void M3([out] object& param) cil managed
Method parameter description:
Is passed by reference True
HasDefaultValue=False
IsIn=False
IsLcid=False
IsOptional=False
IsOut=True
IsRetVal=False

Method name M4 void M4(ref object param)
IL signature .method public hidebysig instance void M4(object& param) cil managed
Method parameter description:
Is passed by reference True
HasDefaultValue=False
IsIn=False
IsLcid=False
IsOptional=False
IsOut=False
IsRetVal=False

Method name M5 void M5([In] object param)
IL signature .method public hidebysig instance void M5([in] object param) cil managed
Method parameter description:
Is passed by reference False
HasDefaultValue=False
IsIn=True
IsLcid=False
IsOptional=False
IsOut=False
IsRetVal=False

Method name M6 void M6([Out] object param)
IL signature .method public hidebysig instance void M6([out] object param) cil managed
Method parameter description:
Is passed by reference False
HasDefaultValue=False
IsIn=False
IsLcid=False
IsOptional=False
IsOut=True
IsRetVal=False

Method name M7 void M7([Out] out object param)
IL signature .method public hidebysig instance void M7([out] object& param) cil managed
Method parameter description:
Is passed by reference True
HasDefaultValue=False
IsIn=False
IsLcid=False
IsOptional=False
IsOut=True
IsRetVal=False

Method name M8 void M8([DefaultValue(null)] object param)
IL signature .method public hidebysig instance void M8(object param) cil managed
Method parameter description:
Is passed by reference False
HasDefaultValue=False
IsIn=False
IsLcid=False
IsOptional=False
IsOut=False
IsRetVal=False

Method name M9 void M9([DefaultValue(-10)] int param = 10)
IL signature .method public hidebysig instance void M9([opt] int32 param) cil managed
Method parameter description:
Parameter name param
Is passed by reference False
HasDefaultValue=True
DefaultValue=10
IsIn=False
IsLcid=False
IsOptional=True
IsOut=False
IsRetVal=False

Method name M10 void M10([Optional] int param)
IL signature .method public hidebysig instance void M10([opt] int32 param) cil managed
Method parameter description:
Is passed by reference False
HasDefaultValue=False
IsIn=False
IsLcid=False
IsOptional=True
IsOut=False
IsRetVal=False

I guess In, Out and Optional attributes relate to COM as they are located in System.Runtime.InteropServices namesapce.
But again documentation is quite poor. 🙁

And what is RetVal and where it is used?

TCP状态转换图

Linux内核协议栈为一个tcp连接管理使用两个队列,一个是半链接队列(用来保存处于SYN_SENT和SYN_RECV状态的请求),一个是accpetd队列(用来保存处于established状态,但是应用层没有调用accept取走的请求)。

第一个队列的长度是/proc/sys/net/ipv4/tcp_max_syn_backlog,默认是1024。如果开启了syncookies,那么基本上没有限制。

第二个队列的长度是/proc/sys/net/core/somaxconn,默认是128,表示最多有129个established链接等待accept。

 

windows :
TCPView
https://technet.microsoft.com/en-us/sysinternals/bb897437.aspx

Linux 各状态查看命令:

netstat -ant|fgrep ":"|cut -b 77-90|sort |uniq -c

状态转换图中状态的描述:
CLOSED:无连接是活动的或正在进行
LISTEN:服务器在等待进入呼叫
SYN_RECV:一个连接请求已经到达,等待确认
SYN_SENT:应用已经开始,打开一个连接
ESTABLISHED:正常数据传输状态
FIN_WAIT1:应用说它已经完成
FIN_WAIT2:另一边已同意释放
ITMED_WAIT:等待所有分组死掉
CLOSING:两边同时尝试关闭
TIME_WAIT:另一边已初始化一个释放
LAST_ACK:等待所有分组死掉
TCP/IP状态转换图的分析
其中,EATABLISHED是数据的传送状态。我们主要分析主动关闭这个框内的转换状态。
当客户端应用程序主动请求关闭时,调用close或shutdown关闭连接,这时应用程序发送FIN,然后进入FIN_WAIT_1状态,等待服务器端发送确认包ACK,接受到服务器端的ACK以后,然后客户端进入FIN_WAIT_2状态,等待服务器端调用close,并发送FIN,当客户端接受到FIN后,发送ACK,进入最终的TIME_WAIT状态,这结合着TCP关闭连接时的分组交换连接图可以更加的明白。需要注意的是,执行主动关闭的那一端进入TIME_WAIT状态。留在TIME_WAIT的持续的时间是MSL(最长分节生命周期 maximum segment lifttime)时间的两倍,也就是2MSL. MSL一般情况下是30秒到2分种,所以TIME_WAIT的时间一般为1-4分种。

 

点击查看原图

 

点击查看原图

点击查看原图

下面是(《Unix 网络编程第一卷》关于TIME_WAIT状态存在的理由)
存在TIME_WAIT状态有两个理由:
1.  实现终止TCP全双工连接的可靠性
假设最终的ACK丢失,服务器将重发最终的FIN,因此客户必须维护状态信息以允许它重发最终的ACK,如果不维护状态信息,它将响应以RST,而服务器则把该分节解释成一个错误,如果TCP打算执行所有必要的工作以彻底终止某个连接上两个方向的数据流,那么它必须,能够处理连接终止序列四个分节中任何一个分节的丢失的情况,主动关闭的那一端必须进入TIME_WAIT状态,因为它可能不得不重发最终的ACK。
2.  允许老的重复分节的网络中消失。

我们假设206.62.226.33端口1500和198.162.10.2端口21之间有一个TCP连接,我们关闭这个连接后,在以后某个时候又重新建立起相同的IP地址和端口之间的TCP连接。后一个连接称为前一个连接的化身,因为它们的IP地坛和端口号是相同的,TCP必须防止来自某个连接的老重复分组在连接终止后再现,从而被误解成属于同一个连接的化身。要实现这种功能TCP不能给处于TIME_WAIT状态的启动新的化身,既然TIME_WAIT状态的待续时间是2MLS,这就足够让某个方向上的分组最多存活MSL秒即被丢弃,另一个方向上的应答最多存活MSL秒也被丢弃,通过实施这个规则,我们就能保证当成功建立一个TCP连接时,来自该连接先前的化身的老重复分组都已在网络中消逝了

参考图2:

点击查看原图

点击查看原图

 

参考:http://www.cnxct.com/coping-with-the-tcp-time_wait-state-on-busy-linux-servers-in-chinese-and-dont-enable-tcp_tw_recycle/

其它TCP状态转换图一:
点击查看原图

 

CLOSE_WAIT SYN_RECV Netty 分析过程

Netty出现大量的CLOSE_WAIT SYN_RECV通过下面的配置解决

net.ipv4.ip_forward = 0
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.default.accept_source_route = 0
kernel.sysrq = 0
kernel.core_uses_pid = 1
net.ipv4.tcp_syncookies = 1
kernel.msgmnb = 65536
kernel.msgmax = 65536
kernel.shmmax = 68719476736
kernel.shmall = 4294967296
net.ipv4.tcp_keepalive_time = 1800
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 3
net.ipv4.netfilter.ip_conntrack_tcp_timeout_last_ack = 10
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30
net.ipv4.netfilter.ip_conntrack_tcp_timeout_established = 180

用 netstat 看发现有大量来自国外 IP 的 LAST_ACK 状态的连接。

net.ipv4.tcp_syncookies = 1 表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;
net.ipv4.tcp_tw_reuse = 1 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。
net.ipv4.tcp_fin_timeout 修改系統默认的 TIMEOUT 时间

现象:在netstat的时候发现大量处于LAST_ACK状态的TCP连接,达到在ESTABLISHED状态的90%以上
[root@ccsafe ~]# netstat -ant|fgrep ":"|cut -b 77-90|sort |uniq -c                
       6 CLOSE_WAIT
       7 CLOSING     
    6838 ESTABLISHED
    1037 FIN_WAIT1  
     357 FIN_WAIT2  
    5830 LAST_ACK    
       2 LISTEN     
     276 SYN_RECV    
      71 TIME_WAIT  
[root@ccsafe ~]#
看看系统状态,性能都花在系统中断和上下文切换
[root@ccsafe ~]# vmstat 2
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu------
r b    swpd    free    buff cache    si    so     bi     bo    in    cs us sy id wa st
1 0       0 3091812 363032 284132     0     0      0      0     1     1 0 0 100 0 0
0 0       0 3091812 363032 284132     0     0      0      0 13750 3174 0 5 94 0 0
0 0       0 3091936 363032 284132     0     0      0      0 13666 3057 1 5 94 0 0
0 0       0 3092060 363032 284132     0     0      0     16 13749 3030 0 5 95 0 0
0 0       0 3092060 363032 284132     0     0      0      0 13822 3144 0 5 95 0 0
0 0       0 3092060 363032 284132     0     0      0      0 13390 2961 0 5 95 0 0
0 0       0 3092060 363032 284132     0     0      0      0 13541 3182 0 6 94 0 0

查看socket队列信息
[root@ccsafe ~]# sar -n SOCK 5
Linux 2.6.18-53.1.13.el5PAE (ccsafe)      10/21/2008
06:31:43 PM     totsck     tcpsck     udpsck     rawsck    ip-frag     tcp-tw
06:31:48 PM       6951      13868          1          0          0        430
Average:          6951      13868          1          0          0        430
根据TCP状态的变化过程来分析,LAST_ACK属于被动关闭连接过程中的状态
ESTABLISHED->CLOSE_WAIT->(发送ACK)->LAST_ACK->(发送FIN+接收ACK)->CLOSED
现在状态都堆积到LAST_ACK,初步判断问题从上下两个状态着手
调节一下LAST_ACK时间...
[root@ccsafe ~]# sysctl -a |grep last_ack
net.ipv4.netfilter.ip_conntrack_tcp_timeout_last_ack = 30
[root@ccsafe ~]# sysctl -w net.ipv4.netfilter.ip_conntrack_tcp_timeout_last_ack=10
net.ipv4.netfilter.ip_conntrack_tcp_timeout_last_ack = 10
[root@ccsafe ~]# sysctl -p
[root@ccsafe ~]# watch -n 10 "netstat -ant|fgrep ":"|cut -b 77-90|sort |uniq -c"
Every 5.0s: netstat -ant|fgrep :|cut -b 77-90|sort |uniq -c                    
       6 CLOSE_WAIT
       9 CLOSING
    6420 ESTABLISHED
     693 FIN_WAIT1
     391 FIN_WAIT2
    5081 LAST_ACK
       2 LISTEN
     203 SYN_RECV
      66 TIME_WAIT
检查一下LAST_ACK所对应的应用
[root@ccsafe ~]# netstat -ant|fgrep "LAST_ACK"|cut -b 49-75|cut -d ":" -f1|sort |uniq -c|sort -nr --key=1,7|head -5
     101 220.160.210.6
      46 222.75.65.69
      31 221.0.91.118
      24 222.210.8.160
      22 60.161.81.28
[root@ccsafe ~]#
[root@ccsafe ~]# netstat -an|grep "220.160.210.6"
tcp         0 17280 10.1.1.145:80            220.160.210.6:52787          ESTABLISHED
tcp         1 14401 10.1.1.145:80            220.160.210.6:52513          LAST_ACK    
tcp         1 14401 10.1.1.145:80            220.160.210.6:52769          LAST_ACK    
tcp         1 14401 10.1.1.145:80            220.160.210.6:52768          LAST_ACK    
tcp         0    8184 10.1.1.145:80            220.160.210.6:52515          LAST_ACK    
tcp         1 14401 10.1.1.145:80            220.160.210.6:52514          LAST_ACK    
tcp         0    8184 10.1.1.145:80            220.160.210.6:52781          LAST_ACK    

是TCP80端口的应用,调节一下nginx的keepalive时间...
[root@ccsafe ~]# /usr/local/nginx/sbin/nginx -t -c /usr/local/nginx/conf/nginx.conf
2008/10/21 19:15:31 [info] 21352#0: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
2008/10/21 19:15:31 [info] 21352#0: the configuration file /usr/local/nginx/conf/nginx.conf was tested successfully
[root@ccsafe ~]# ps aux|egrep '(PID|nginx)'
USER        PID %CPU %MEM     VSZ    RSS TTY       STAT START    TIME COMMAND
root       8290 0.0 0.0    7572 1124 ?         Ss    Oct04    0:00 nginx: master process /usr/local/nginx/sbin/nginx
nobody     8291 0.2 0.3 19704 13776 ?         S     Oct04 71:35 nginx: worker process     
nobody     8292 0.3 0.2 17604 11680 ?         S     Oct04 77:26 nginx: worker process     
nobody     8293 0.2 0.4 22528 16636 ?         S     Oct04 58:13 nginx: worker process     
nobody     8294 0.3 0.4 24944 19020 ?         S     Oct04 94:07 nginx: worker process     
nobody     8295 0.3 0.5 27496 21508 ?         S     Oct04 84:41 nginx: worker process     
nobody     8296 0.3 0.1 13388 7496 ?         S     Oct04 84:14 nginx: worker process     
nobody     8297 0.2 0.0    9196 3268 ?         S     Oct04 58:21 nginx: worker process     
nobody     8298 0.3 0.2 15392 9504 ?         S     Oct04 75:16 nginx: worker process     
root      21354 0.0 0.0    3896    720 pts/0     S+    19:15    0:00 egrep (PID|nginx)
(动态加载新配置)
[root@ccsafe ~]# kill -HUP 8290
[root@ccsafe ~]#
Every 10.0s: netstat -ant|fgrep :|cut -b 77-90 |sort |uniq -c                                               
       1 CLOSE_WAIT
    1138 CLOSING
    7161 ESTABLISHED
    1427 FIN_WAIT1
     396 FIN_WAIT2
    5740 LAST_ACK
       2 LISTEN
     350 SYN_RECV
     148 TIME_WAIT
...
[root@ccsafe ~]# netstat -ant|fgrep ":"|cut -b 77-90 |sort |uniq -c
    1151 CLOSING     
    8506 ESTABLISHED
    1452 FIN_WAIT1  
     666 FIN_WAIT2  
    6568 LAST_ACK    
       2 LISTEN     
     429 SYN_RECV    
      92 TIME_WAIT  
...

LAST_ACK不下,而且CLOSING 和FIN_WAIT突增
着重看看可影响主动断开TCP连接时几个参数
tcp_keepalive_intvl:探测消息发送的频率
tcp_keepalive_probes:TCP发送keepalive探测以确定该连接已经断开的次数
tcp_keepalive_time:当keepalive打开的情况下,TCP发送keepalive消息的频率
[root@ccsafe ~]# sysctl -a|grep tcp_keepalive
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 2
net.ipv4.tcp_keepalive_time = 160
tcp_retries2:在丢弃激活(已建立通讯状况)的TCP连接之前?需要进行多少次重试
[root@ccsafe ~]# sysctl -a |grep tcp_retries
net.ipv4.tcp_retries2 = 15
net.ipv4.tcp_retries1 = 3
加速处理那些等待ACK的LAST_ACK,减少等待ACK的LAST_ACK的重试次数
[root@ccsafe ~]# sysctl -w net.ipv4.tcp_retries2=5
net.ipv4.tcp_retries2 = 5
减少keepalive发送的频率
[root@ccsafe ~]# sysctl -w net.ipv4.tcp_keepalive_intvl=15
net.ipv4.tcp_keepalive_intvl = 15
[root@ccsafe ~]# sysctl -p
排除syncookies的影响
[root@ccsafe ~]# !ec
echo "0" >/proc/sys/net/ipv4/tcp_syncookies
[root@ccsafe ~]# echo "1" >/proc/sys/net/ipv4/tcp_syncookies
[root@ccsafe ~]# sysctl -a|grep tcp_keepalive              
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 2
net.ipv4.tcp_keepalive_time = 160
[root@ccsafe ~]# sysctl -a|grep syncookies
net.ipv4.tcp_syncookies = 1
延长keepalive检测周期,保留ESTABLISHED数量
[root@ccsafe ~]# echo "1800" >/proc/sys/net/ipv4/tcp_keepalive_time
[root@ccsafe ~]# echo "5" >/proc/sys/net/ipv4/tcp_keepalive_probes
[root@ccsafe ~]# echo "15" >/proc/sys/net/ipv4/tcp_keepalive_intvl
[root@ccsafe ~]# sysctl -a|grep tcp_keepalive                      
net.ipv4.tcp_keepalive_intvl = 15
net.ipv4.tcp_keepalive_probes = 5
net.ipv4.tcp_keepalive_time = 1800
[root@ccsafe ~]# !wat
watch -n 10 "netstat -ant|fgrep ":"|cut -b 77-90|sort |uniq -c"
Every 10.0s: netstat -ant|fgrep :|cut -b 77-90|sort |uniq -c                         
       1 CLOSE_WAIT
     363 CLOSING
    5145 ESTABLISHED
    1073 FIN_WAIT1
     174 FIN_WAIT2
    6042 LAST_ACK
       2 LISTEN
     301 SYN_RECV
      85 TIME_WAIT

LAST_ACK不下,但是CLOSING有所回落
tcp_orphan_retries:在近端丢弃TCP连接之前?要进行多少次重试。
[root@ccsafe ~]# sysctl -a|grep tcp_orphan
net.ipv4.tcp_orphan_retries = 0
关键,丢TCP太频繁了,以至于后勤都跟不上。设置丢弃之前的重试次数
[root@ccsafe ~]# echo "3" >/proc/sys/net/ipv4/tcp_orphan_retries
[root@ccsafe ~]# !wat
watch -n 10 "netstat -ant|fgrep ":"|cut -b 77-90|sort |uniq -c"
Every 10.0s: netstat -ant|fgrep :|cut -b 77-90|sort |uniq -c                         
       1 CLOSE_WAIT
      24 CLOSING
    5422 ESTABLISHED
     279 FIN_WAIT1
     214 FIN_WAIT2
    1966 LAST_ACK
       2 LISTEN
     269 SYN_RECV
      74 TIME_WAIT
上下调节该值,找个合适的临界点
[root@ccsafe ~]# echo "7" >/proc/sys/net/ipv4/tcp_orphan_retries                 
[root@ccsafe ~]# !wat
watch -n 10 "netstat -ant|fgrep ":"|cut -b 77-90|sort |uniq -c"
Every 10.0s: netstat -ant|fgrep :|cut -b 77-90|sort |uniq -c                         
       1 CLOSE_WAIT
     175 CLOSING
    5373 ESTABLISHED
     436 FIN_WAIT1
     209 FIN_WAIT2
    3184 LAST_ACK
       2 LISTEN
     283 SYN_RECV
     110 TIME_WAIT
恢复,同时FIN_WAIT1的值过高。考虑减少tcp_fin_timeout时间
[root@ccsafe ~]# echo "2" >/proc/sys/net/ipv4/tcp_orphan_retries                 
[root@ccsafe ~]# sysctl -a|grep tcp_fin
net.ipv4.tcp_fin_timeout = 10
[root@ccsafe ~]# echo "5" >/proc/sys/net/ipv4/tcp_fin_timeout
[root@ccsafe ~]# !wat
watch -n 10 "netstat -ant|fgrep ":"|cut -b 77-90|sort |uniq -c"
Every 10.0s: netstat -ant|fgrep :|cut -b 77-90|sort |uniq -c                         
       2 CLOSE_WAIT
      17 CLOSING
    5665 ESTABLISHED
     145 FIN_WAIT1
     141 FIN_WAIT2
    1068 LAST_ACK
       2 LISTEN
     287 SYN_RECV
      68 TIME_WAIT
相比FIN_WAIT,SYN_RECV的值偏高。加大发送synack的质量
[root@ccsafe ~]# sysctl -a|grep synack
net.ipv4.tcp_synack_retries = 1
[root@ccsafe ~]# echo "2" >/proc/sys/net/ipv4/tcp_synack_retries
[root@ccsafe ~]# !wat
watch -n 10 "netstat -ant|fgrep ":"|cut -b 77-90|sort |uniq -c"
Every 10.0s: netstat -ant|fgrep :|cut -b 77-90|sort |uniq -c                         
       3 CLOSE_WAIT
      16 CLOSING
    5317 ESTABLISHED
     200 FIN_WAIT1
     158 FIN_WAIT2
    1001 LAST_ACK
       2 LISTEN
     303 SYN_RECV
      78 TIME_WAIT
[root@ccsafe ~]# sysctl -a|grep keepalive
net.ipv4.tcp_keepalive_intvl = 15
net.ipv4.tcp_keepalive_probes = 5
net.ipv4.tcp_keepalive_time = 1800
[root@ccsafe ~]# watch -n 10 "netstat -ant|fgrep ":"|cut -b 77-90|sort |uniq -c"
Every 10.0s: netstat -ant|fgrep :|cut -b 77-90|sort |uniq -c                         
       1 CLOSE_WAIT
       7 CLOSING
    5356 ESTABLISHED
     175 FIN_WAIT1
     136 FIN_WAIT2
    1045 LAST_ACK
       2 LISTEN
     345 SYN_RECV
      64 TIME_WAIT
减少keepalive的检测周期,LAST_ACK上升
[root@ccsafe ~]# echo "10" >/proc/sys/net/ipv4/tcp_keepalive_intvl
[root@ccsafe ~]# echo "1" >/proc/sys/net/ipv4/tcp_synack_retries                 
[root@ccsafe ~]# !wat
watch -n 10 "netstat -ant|fgrep ":"|cut -b 77-90|sort |uniq -c"
Every 10.0s: netstat -ant|fgrep :|cut -b 77-90|sort |uniq -c                                               
       1 CLOSE_WAIT
      13 CLOSING
    5605 ESTABLISHED
     212 FIN_WAIT1
     131 FIN_WAIT2
    1143 LAST_ACK
       2 LISTEN
     252 SYN_RECV
      79 TIME_WAIT
恢复
[root@ccsafe ~]# echo "15" >/proc/sys/net/ipv4/tcp_keepalive_intvl              
[root@ccsafe ~]# watch -n 10 "netstat -ant|fgrep ":"|cut -b 77-90|sort |uniq -c"
Every 10.0s: netstat -ant|fgrep :|cut -b 77-90|sort |uniq -c                       
       3 CLOSE_WAIT
      14 CLOSING
    5862 ESTABLISHED
     230 FIN_WAIT1
     205 FIN_WAIT2
    1064 LAST_ACK
       2 LISTEN
     244 SYN_RECV
      59 TIME_WAIT

[root@ccsafe ~]# watch -n 10 "netstat -ant|fgrep ":"|cut -b 77-90|sort |uniq -c"
Every 10.0s: netstat -ant|fgrep :|cut -b 77-90|sort |uniq -c                       
       3 CLOSE_WAIT
      26 CLOSING
    6712 ESTABLISHED
     270 FIN_WAIT1
     230 FIN_WAIT2
     994 LAST_ACK
       2 LISTEN
     254 SYN_RECV
      73 TIME_WAIT

[root@ccsafe ~]#
目前LAST_ACK占ESTABLISHED的量在15%左右

Netty 移动开发注意事项

1. 最大句柄数修改

百万长连接接入,首先需要优化的就是Linux内核参数,其中Linux最大文件句柄数是最重要的调优参数之一,默认单进程打开的最大句柄数是1024,通过ulimit -a可以查看相关参数,示例如下:

[root@lilinfeng ~]# ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 256324
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024

......后续输出省略

当单个推送服务接收到的链接超过上限后,就会报“too many open files”,所有新的客户端接入将失败。

通过vi /etc/security/limits.conf 添加如下配置参数:修改之后保存,注销当前用户,重新登录,通过ulimit -a 查看修改的状态是否生效。

*  soft  nofile  1000000
*  hard  nofile  1000000

需要指出的是,尽管我们可以将单个进程打开的最大句柄数修改的非常大,但是当句柄数达到一定数量级之后,处理效率将出现明显下降,因此,需要根据服务器的硬件配置和处理能力进行合理设置。如果单个服务器性能不行也可以通过集群的方式实现。


2. 当心CLOSE_WAIT

从事移动推送服务开发的同学可能都有体会,移动无线网络可靠性非常差,经常存在客户端重置连接,网络闪断等。

在百万长连接的推送系统中,服务端需要能够正确处理这些网络异常,设计要点如下:

    客户端的重连间隔需要合理设置,防止连接过于频繁导致的连接失败(例如端口还没有被释放);
    客户端重复登陆拒绝机制;
    服务端正确处理I/O异常和解码异常等,防止句柄泄露。

最后特别需要注意的一点就是close_wait 过多问题,由于网络不稳定经常会导致客户端断连,如果服务端没有能够及时关闭socket,就会导致处于close_wait状态的链路过多。close_wait状态的链路并不释放句柄和内存等资源,如果积压过多可能会导致系统句柄耗尽,发生“Too many open files”异常,新的客户端无法接入,涉及创建或者打开句柄的操作都将失败。

下面对close_wait状态进行下简单介绍,被动关闭TCP连接状态迁移图如下所示:
点击查看原图
图3-1 被动关闭TCP连接状态迁移图

close_wait是被动关闭连接是形成的,根据TCP状态机,服务器端收到客户端发送的FIN,TCP协议栈会自动发送ACK,链接进入close_wait状态。但如果服务器端不执行socket的close()操作,状态就不能由close_wait迁移到last_ack,则系统中会存在很多close_wait状态的连接。通常来说,一个close_wait会维持至少2个小时的时间(系统默认超时时间的是7200秒,也就是2小时)。如果服务端程序因某个原因导致系统造成一堆close_wait消耗资源,那么通常是等不到释放那一刻,系统就已崩溃。

导致close_wait过多的可能原因如下:

    程序处理Bug,导致接收到对方的fin之后没有及时关闭socket,这可能是Netty的Bug,也可能是业务层Bug,需要具体问题具体分析;
    关闭socket不及时:例如I/O线程被意外阻塞,或者I/O线程执行的用户自定义Task比例过高,导致I/O操作处理不及时,链路不能被及时释放。

下面我们结合Netty的原理,对潜在的故障点进行分析。

设计要点1:不要在Netty的I/O线程上处理业务(心跳发送和检测除外)。Why? 对于Java进程,线程不能无限增长,这就意味着Netty的Reactor线程数必须收敛。Netty的默认值是CPU核数 * 2,通常情况下,I/O密集型应用建议线程数尽量设置大些,但这主要是针对传统同步I/O而言,对于非阻塞I/O,线程数并不建议设置太大,尽管没有最优值,但是I/O线程数经验值是[CPU核数 + 1,CPU核数*2 ]之间。

假如单个服务器支撑100万个长连接,服务器内核数为32,则单个I/O线程处理的链接数L = 100/(32 * 2) = 15625。 假如每5S有一次消息交互(新消息推送、心跳消息和其它管理消息),则平均CAPS = 15625 / 5 = 3125条/秒。这个数值相比于Netty的处理性能而言压力并不大,但是在实际业务处理中,经常会有一些额外的复杂逻辑处理,例如性能统计、记录接口日志等,这些业务操作性能开销也比较大,如果在I/O线程上直接做业务逻辑处理,可能会阻塞I/O线程,影响对其它链路的读写操作,这就会导致被动关闭的链路不能及时关闭,造成close_wait堆积。

设计要点2:在I/O线程上执行自定义Task要当心。Netty的I/O处理线程NioEventLoop支持两种自定义Task的执行:

    普通的Runnable: 通过调用NioEventLoop的execute(Runnable task)方法执行;
    定时任务ScheduledFutureTask:通过调用NioEventLoop的schedule(Runnable command, long delay, TimeUnit unit)系列接口执行。

为什么NioEventLoop要支持用户自定义Runnable和ScheduledFutureTask的执行,并不是本文要讨论的重点,后续会有专题文章进行介绍。本文重点对它们的影响进行分析。

在NioEventLoop中执行Runnable和ScheduledFutureTask,意味着允许用户在NioEventLoop中执行非I/O操作类的业务逻辑,这些业务逻辑通常用消息报文的处理和协议管理相关。它们的执行会抢占NioEventLoop I/O读写的CPU时间,如果用户自定义Task过多,或者单个Task执行周期过长,会导致I/O读写操作被阻塞,这样也间接导致close_wait堆积。

所以,如果用户在代码中使用到了Runnable和ScheduledFutureTask,请合理设置ioRatio的比例,通过NioEventLoop的setIoRatio(int ioRatio)方法可以设置该值,默认值为50,即I/O操作和用户自定义任务的执行时间比为1:1。

我的建议是当服务端处理海量客户端长连接的时候,不要在NioEventLoop中执行自定义Task,或者非心跳类的定时任务。

设计要点3:IdleStateHandler使用要当心。很多用户会使用IdleStateHandler做心跳发送和检测,这种用法值得提倡。相比于自己启定时任务发送心跳,这种方式更高效。但是在实际开发中需要注意的是,在心跳的业务逻辑处理中,无论是正常还是异常场景,处理时延要可控,防止时延不可控导致的NioEventLoop被意外阻塞。例如,心跳超时或者发生I/O异常时,业务调用Email发送接口告警,由于Email服务端处理超时,导致邮件发送客户端被阻塞,级联引起IdleStateHandler的AllIdleTimeoutTask任务被阻塞,最终NioEventLoop多路复用器上其它的链路读写被阻塞。

对于ReadTimeoutHandler和WriteTimeoutHandler,约束同样存在

[转]Android微信智能心跳方案

来源:http://mp.weixin.qq.com/s?__biz=MzAwNDY1ODY2OQ==&mid=207243549&idx=1&sn=4ebe4beb8123f1b5ab58810ac8bc5994&scene=0#rd

 

前言:在1311月中旬时,因为基础组件组人手紧张,Leo安排我和春哥去广州轮岗支援。刚到广州的时候,Ray让我和春哥对LineWhatsApp的心跳机制进行分析。我和春哥抓包测试了差不多两个多礼拜,在我们基本上摸清了LineWhatsApp的心跳机制后,Ray才告诉我们真正的任务——对微信的固定心跳进行优化,并告诉我们这不是一件容易的事情。于是我和春哥开始构思第一个方案,我们开始想用统计的方法来解决问题,当我们拿着第一个方案和Ray讨论时,发现不能优雅应对Ray的所有提问:1、测试环境的准确性,失败到底是因为网络的特性导致还是因为用户当前的环境变化导致的暂时失败。2、临界值界定,如果方案选中的心跳值是临界值,我们该怎么办。Ray和组件组同事在网络方面有极其丰富的经验,虽然他没有给我们指出明确的方向,但提出的问题帮助我们更快的补齐需要面对的核心问题。这两个问题让我和春哥意识到如果能很好的解决,就可以给出一个比较好的心跳方案。第一个问题我和春哥开始就意识到,第二个问题我们确实在一开始时疏忽了。但直接解决这两个问题确实不容易,这着实让我和春哥迷茫了几天,有两三天在纺园我都没怎么睡着,因为想不到更好的方法。直到有一天思路发生了一些转变,既然最优解比较复杂,为什么不绕过去,使用有损服务理念找次优解呢。让复杂的事情简单化,好了,想到这里突然有一种拨开云雾的感觉。

思路对了,方案就可以做到简单并且可靠,大家可以看到最终的方案是比较简单的,并且效果还挺好的。在方案描述之前大概讲一下减低问题复杂度的方法:

a)延迟心跳测试法:这是测试结果准确的前提保障,我们认为长连接建立后连续三次成功的短心跳就可以很大程度的保证下一次心跳环境是正常的。

b)成功一次认定,失败连续累积认定:成功是绝对的,连续失败多次才可能是失败。

c)临界值避免:我们使用比计算出的心跳稍微小一点的值做为稳定心跳避免临界值。

d)动态调整:即使在一次完整的智能心跳计算过程中,我们没有找到最好的值,我们还有机会来进行校正。

当我和春哥想出第二个简单易行的方案后,我们心里就很有底了,去找Ray讨论,Ray听完后一次通过,然后Ray约了Harvey,给Harvey讲完后,Harvey说听起来可以,可以试试。

然后就开始动手,分析竞品加确定方案花了差不多两个月。写心跳的主要代码,只花了一天时间,我记得那天是年会后的一天。回过头来再看这个方案花费的时间还是值得的,后来灰度的统计数据显示,70%用户都可以达到我们的心跳上限。

搞完智能心跳后一段时间在广州没事干,我就跟Ray商量,Ray让我去测试下WebView的性能瓶颈。然后我跟周斯基一起来做这件事,搞完了安卓客户端WebView性能瓶颈测试后,因为怀孕的老婆一个人在深圳,领导就安排我先回深圳了。春哥坚守着把GCM部分完成后才回深圳。

等我们的心跳版本正式发布后,一年前我在公司km上分享了智能心跳方案,吸引不少做push的同事加入了讨论,感觉这方面的交流还是很有必要的。

好了,废话了很多,下面分享一下微信的智能心跳方案细节。由于字数比较多,建议大家使用PC版微信查看。

1.主要目标

本方案的主要目标是,在尽量不影响用户收消息及时性的前提下,根据网络类型自适应的找出保活信令TCP连接的尽可能大的心跳间隔,从而达到减少安卓微信因心跳引起的空中信道资源消耗,减少心跳Server的负载,以及减少部分因心跳引起的耗电。

主要方法是参考WhatsAppLine中有价值的做法,结合影响TCP连接寿命的因素,实现Android微信后台自适应心跳算法,同时使用GCM作为辅助通道增加新消息通知的可靠性。

2. WhatsAppLine、微信的Push策略分析

2.1 WhatsApp

在不支持GCM的设备上,采用和微信类似的长连接+心跳策略,WIFI和手机网络下的心跳间隔都为445秒,心跳5次后,主动断开连接再重连。

在支持GCM的设备上,主要靠GCM来激活WhatsAppWhatsApp启动后,会建立一个与服务器的长连接,直接通过此长连接发送Push消息,这个长连接10分钟无消息就会主动断掉,且这十分钟内不做心跳,断掉后WhatsApp客户端和它的服务器不再有连接。当有消息时候,服务器发现没有长连接会发送GCM消息,手机收到GCM消息后,会重新建立长连接来收取消息,10分钟无消息会再断开,如此循环。

2.2 Line

从测试中发现Line在国内、台湾、美国使用了不同的策略。

1、美国(使用GCM):

启动时,会保持7分钟心跳(CDMA2000网络)维持长连接半小时,之后主动断开长连接。当有消息时,服务器会发送GCM消息,Line客户端接收到GCM消息后,重新建立长连接,并再次用心跳维持半个小时。

2、国内(不使用GCM):

在国内,同样帐号在相同网络,不同的手机上测出了两种策略:

长连接+心跳策略(在Galaxy S3上使用),心跳间隔WIFI下是320秒,手机网络是7分钟。

轮询策略(在红米和Nexus S上使用),如图2-1所示。与心跳策略的主要区别用红色标出,客户端在长连接建立后也会定时发送请求,Server会回复并且同时关闭长连接。客户端等待轮询间隔T1后再次建立TCP连接。Line会根据手机的活跃状态动态调整T1,调整范围是从最小1分到最大到2小时半。而长连接存活时间T2比较固定,在WIFI4分钟,手机网络7分钟。如果在T2时收到新消息会延长T2的时间。

点击查看原图

2-1 Line在国内的轮询策略

3、台湾(不使用GCM):

IBG同事winguang提供的测试数据中看到,台湾使用的策略跟国内的轮询策略类似。

2.3 微信

微信没有使用GCM,自己维护TCP长连接,使用固定心跳。

2.4心跳典型值

WhatsApp

Line

GCM

WIFI

445

320

15分钟

手机网络

445

7分钟

28分钟

2.5LineWhatsApp、微信Push策略的优点

a)微信:当前心跳间隔比竞品短,所以微信在新消息提醒上会最及时。

b)使用GCMLineWhatsApp使用GCM策略的最大优点就是省电,以及减轻系统负荷(减少后台应用数目)。

cLineLine的轮询策略,优点是当Line处于活跃状态时,及时收消息。当Line处于不活跃状态时,省电。

2.6LineWhatsApp微信Push策略的不足

a)微信当前心跳频率相对竞品较大,在耗电、耗流量,占用信令通道等方面有所影响。

bLine的轮询策略,导致的问题是消息可能会延迟接收,测试发现最大延迟间隔到2.5小时。

cWhatsAppLine使用Push拉起一个定时长连接策略,缺点是要依赖GooglePush服务,如果GooglePush服务不稳定,消息也会延迟接收。

d)在国内的移动和联通2G网络下,由于运营商的策略,GCM长连接频繁断连,WhatsAppPush消息很不及时,体验非常差。

3. GCM研究

3.1 GCM特点

aAndroid2.2以下的手机不支持GCM2.23.0需要安装Google Store并设置Google帐号,4.04及以上版本不需要设置帐号也能支持。

bGCM只传递数据(可以传递小于4kb的数据),对这些数据的处理可以全部由开发者控制。

cAndroid应用不需要运行就可以接收消息(通过Android广播)

dGCM不保证发送的消息的顺序,也不保证消息一定能够推送到手机。

3.2 GCM心跳策略以及存在的问题

a用心跳保活长连接,心跳间隔为WIFI15分钟,数据网络下28分钟。

bGoogle可以改变所有Android设备的心跳间隔值(目前还未改变过)。

cGCM由于心跳间隔固定,并且较长,所以在NAT aging-time设置较小的网络(如联通2G,或有些WIFI环境下)会导致TCP长连接在下一次心跳前被网关释放。造成Push延迟接收。

3.3 GCM的可用性及稳定性

目前测试发现GCM在国内可用性不高,原因有:

a) Android很多被手机厂商定制化,厂商可能会去掉GCM服务。

b) Android2.23.0之间需要安装Google Store设置Google帐号。

c)由于国内2G和移动3GNAT超时时间都小于GCM心跳时间(28分钟)TCP长连接必然无法保活,每次都要等28分钟心跳失败重连后才能收到Push

d)某些运营商可能限制了5228端口,移动3G/2G下,发现几乎无法连接上GCM服务器,也就无法获得GCM通知,WhatsApp放后台10分钟后,经常很长时间都收不到Push消息。

在美国3G网络下抓包的24小时,GCM的连接极其稳定,24小时内GCM长连接未曾断过,在台湾3G网络下抓包14个小时,GCM连接也只断过一次。WhatsApp用户在此类地区网络下客户端可以获得很及时的Push通知。

在中国电信3G下抓包,大部分时间GCM连接都比较稳定,只会因为偶尔的DHCP造成断连现象,由于频率很低(平均数小时才发生一次),对Push体验的影响不大。

3.4 GCM Server类型

GCM提供两种Server模型:

aHTTP Server : 使用同步接口发送HTTP请求,一次请求可以发给最多1000个设备。

bXMPP Server :使用异步接口发送请求,只支持对单个设备(或同一个用户的多个关联设备发送),发送请求并发数须小于1000,支持设备到云端Server发送数据。需要Google将我们的发送Server加入白名单。

4.微信可能的改进点探讨

微信Push的优化主要有几个优化点:

a) 公共Push通道

b) 使用GCM Push作为辅助通道

c)自适应心跳间隔优化

4.1 公共Push通道

由于GCM在国内的可靠性很低,现在国内Android上的Push基本上是各自为政,很多软件都自己实现Push。导致手机被经常性的唤醒,耗电耗流量严重。

市面上已经有很多第三方的公共推送服务,大家可以选择一个适合自己应用的推送服务。腾讯也有信鸽和维纳斯组件,大家在选择方案的时候可以对比下。

最终因为我们国内外使用一套方案,并且是辅助公道,所以我们选择使用GCM

4.2 使用GCM Push作为辅助通道

当前使用GCM的成本不大,可以使用GCM作为辅助通道来增加新消息的及时性。

使用GCM作为辅助通道,在支持GCM的设备上微信上传自己的注册GCM ID给微信Server

微信Server在发现长连接失效的情况下,可以使用GCM 作为辅助通道通知客户端有新消息,客户端收到push通知后做一次sync

只利用GCM来激活微信,不传递消息的具体数据,要控制给同一设备发送GCM通知的时间间隔(如五分钟)

4.3 自适应心跳间隔优化

4.3.1影响TCP连接寿命的因素

Android下,不管是GCM,还是微信,都是通过TCP长连接来进行Push消息的,TCP长连接存活,消息Push就及时,所以要对影响TCP连接寿命的因素进行研究。

1NAT超时

大部分移动无线网络运营商都在链路一段时间没有数据通讯时,会淘汰 NAT 表中的对应项,造成链路中断(NAT超时的更多描述见附录6.1)。NAT超时是影响TCP连接寿命的一个重要因素(尤其是国内),所以客户端自动测算NAT超时时间,来动态调整心跳间隔,是一个重要的优化点。

2DHCP的租期(lease time

目前测试发现安卓系统对DHCP的处理有BugDHCP租期到了不会主动续约并且会继续使用过期IP,这个问题会造成TCP长连接偶然的断连。(租期问题的具体描述见附录6.2)。

3、网络状态变化

手机网络和WIFI网络切换、网络断开和连上等情况有网络状态的变化,也会使长连接变为无效连接,需要监听响应的网络状态变化事件,重新建立Push长连接。

4.3.2 心跳范围选择

1前后台区分处理

为了保证微信收消息及时性的体验,当微信处于前台活跃状态时,使用固定心跳。

微信进入后台(或者前台关屏)时,先用几次最小心跳维持长链接。然后进入后台自适应心跳计算。这样做的目的是尽量选择用户不活跃的时间段,来减少心跳计算可能产生的消息不及时收取影响。

2后台自适应心跳选择区间

可根据自身产品的特点选择合适的心跳范围。

4.3.3 状态转换图

点击查看原图

4.3.4自适应心跳算法描述

1按网络类型区分计算:

因为每个网络的NAT时间可能不一致。所以需要区分计算,数据网络按subType做关键字,WIFIWIFI名做关键字。

对稳定的网络,因为NAT老化时间的存在,在自适应计算态的时候,暂设计以下步骤在当前心跳区间逼近出最大可用的心跳。

a) 变量说明:

[MinHeartMaxHeart]——心跳可选区间。

successHeart——当前成功心跳,初始为MinHeart

curHeart——当前心跳初始值为successHeart

heartStep——心跳增加步长

successStep——稳定期后的探测步长

b) 最大值探测步骤:

点击查看原图

4-1 自适应心跳计算流程

自适应心跳计算流程如图4-1所示,经过该流程,会找到必然使心跳失败的curHeart(或者MaxHeart),为了保险起见,我们选择比前一个成功值稍微小一点的值作为后台稳定期的心跳间隔。

影响手机网络测试的因素太多,为了尽量保证测试结果的可靠性,我们使用延迟心跳测试法。在我们重新建立TCP连接后,先使用 短心跳连续成功三次,我们才认为网络相对稳定,可以使用curHeart进行一次心跳测试。图4-2显示了一次有效心跳测试过程。图4-3显示了在没有达到稳定网络环境时,我们会一直使用固定短心跳直到满足三次连续短心跳成功。

使用延迟心跳测试的好处是,可以剔除偶然失败,和网络变化较大的情况(如地铁),使测试结果相对可靠(五次延迟测试确定结论)。同时在网络波动较大的情况,使用短心跳,保证收取消息相对及时。

c) 运行时的动态调整策略(已经按测算心跳稳定值后)

NAT超时值算出来后,在维持心跳的过程中的策略

ü 无网络、网络时好时坏、偶然失败、NAT超时变小:在后台稳定期发生心跳发生失败后,我们使用延迟心跳测试法测试五次。如果有一次成功,则保持当前心跳值不变;如果五次测试全失败,重新计算合理心跳值。该过程如图4-4所示,有一点需要注意,每个新建的长连接需要先用短心跳成功维持3次后才用successHeart进行心跳。

点击查看原图

4-2 后台稳定态动态调整心跳策略

ü NAT超时变大:以周为周期,每周三将后台稳定态调至自适应计算态,使用心跳延迟法往后探测心跳间隔。

ü successHeartNAT超时临界值:因为我们现在选择的是一个比successHeart稍小的值作为稳定值,所以在计算过程中可以避开临界值。当运营商在我们后台稳定期将NAT超时调整为我们当前计算值,那么由于我们每周会去向下探索,所以下一周探测时也可以及时调整正确。

4.3.5 冗余Sync和心跳

在用户的一些主动操作以及联网状态改变时,增加冗余Sync和心跳,确保及时收到消息。

1、当用户点亮屏幕的时候,做一次心跳。

2、当微信切换到前台时,做一次Sync

3、联网时重建信令TCP,做一次Sync

5. 可能存在的风险及预防措施

5.1 DHCP租期因素

1问题:根据目前的测试结果显示,安卓不续约到期的IP Bug,会导致TCP连接在不确定的时间点失效,从而会导致一次心跳失败。

2预防:统计后台稳定期的心跳成功率,上报给后台。后台可以按地区分网络监控这个指标的波动,并且后台可以根据不同的波动,动态调整某区域特定网络下可选的心跳区间。

5.2 其他影响TCP寿命的因素

是否有遗漏的因素?欢迎各位联系我反馈。

6 附录

6.1 附录A——NAT超时介绍

因为 IP v4 IP 量有限,运营商分配给手机终端的 IP 是运营商内网的 IP,手机要连接 Internet,就需要通过运营商的网关做一个网络地址转换(Network Address TranslationNAT)。简单的说运营商的网关需要维护一个外网 IP、端口到内网 IP、端口的对应关系,以确保内网的手机可以跟 Internet 的服务器通讯。

点击查看原图

NAT 功能由图中的 GGSN 模块实现

大部分移动无线网络运营商都在链路一段时间没有数据通讯时,会淘汰 NAT 表中的对应项,造成链路中断。下表列出一些已测试过的网络的NAT超时时间(更多数据由于测试条件所限没有测到)

地区/网络

NAT超时时间

中国移动3G2G

5分钟

中国联通2G

5分钟

中国电信3G

大于28分钟

美国3G

大于28分钟

台湾3G

大于28分钟

长连接心跳间隔必须要小于NAT超时时间(aging-time),如果超过aging-time不做心跳,TCP长连接链路就会中断,Server就无法发送Push给手机,只能等到客户端下次心跳失败后,重建连接才能取到消息

6.2 附录B——安卓DHCP的租期(lease time)问题

目前测试发现安卓系统对DHCP的处理有Bug

1、 DHCP租期到了不会主动续约并且会继续使用过期IP,详细描述见http://www.net.princeton.edu/android/android-stops-renewing-lease-keeps-using-IP-address-11236.html这个问题导致的问题表象是,在超过租期的某个时间点(没有规律)会导致IP过期,老的TCP连接不能正常收发数据。并且系统没有网络变化事件,只有等应用判断主动建立新的TCP连接才引起安卓设备重新向DHCP Server申请IP租用。

2、 未到租期的一半时间,安卓设备重新向DHCP Server申请IP租用。从目前测试结果来看,这种现象恢复的比较快。

3、 移动2G/3G,联通2G没有抓到DHCP

4、 美国3G下抓取24小时,没有抓到DHCP