• 推荐使用最新版火狐浏览器或Chrome浏览器访问本网站
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏吧

Linux很多TIME_WAIT问题的解决方法

服务器 紫鹰 4年前 (2020-06-11) 2985次浏览 0个评论 扫描二维码

Linux很多TIME_WAIT

Linux很多TIME_WAIT问题解决

0x00 问题

一个服务器运行用nginx的web服务,由于php需要频繁的访问数据库,而且使用的都是短链接,因此一段时间内产生并保持大量的TIME_WAIT。

$ netstat -an | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

LAST_ACK 11
LISTEN 11
SYN_RECV 42
ESTABLISHED 172
FIN_WAIT1 13
FIN_WAIT2 11
CLOSING 1
SYN_SENT 38
TIME_WAIT 3792

0x01 危害

Linux很多TIME_WAIT,相应的会占用大量的系统资源,同时还会占用大量的端口和连接数,导致新来的请求无法被服务器及时响应处理;默认情况下,linux临时端口号范围是(32768,61000),本机可用于调用的端口约3万个,进而导致调用后端服务阻塞,页面响应变慢;

0x02 解决办法

根据以上分析,需要对系统内核参数进行优化,启用TIME_WAIT连接重用,TIME_WAIT连接回收、缩短连接保持时间、增加可用端口数:

vi /etc/sysctl.conf

net.ipv4.tcp_tw_reuse = 1                   # 连接重用
net.ipv4.tcp_tw_recycle = 1                 # 连接回收
net.ipv4.tcp_fin_timeout = 30               # 连接保持时间
net.ipv4.ip_local_port_range=1024 65000     # 增加端口数量

sysctl -p    # 生效

通常情况下,设置前三条即可,减少了TIME_WAIT数量,端口占用也会缓解,但如果服务的并发量本来就很大,增加端口数量就很有必要;

优化生效后,TIME_WAIT明显减少:

$ netstat -an | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

LAST_ACK 17
LISTEN 11
SYN_RECV 25
ESTABLISHED 107
FIN_WAIT1 9
FIN_WAIT2 23
CLOSING 2
SYN_SENT 14
TIME_WAIT 217

0x03 注意

  • 需要确认 net.ipv4.tcp_timestamps = 1 是打开的(默认是打开的),因为只有timestamps打开了,net.ipv4.tcp_tw_recycle = 1 才会生效;
  • net.ipv4.tcp_timestampsnet.ipv4.tcp_tw_recycle 同时打开,对通过NAT网关访问服务器的连接会有问题,因为 tcp_tw_recycle/tcp_timestamps 都开启的条件下,60s内同一源ip主机的socket connect请求中的timestamp必须是递增的。而timestamp时间为系统启动到当前的时间,因此,不同客户端的timestamp不相同,timestamp 大的客户端可以正常访问,timestamp小的客户端则访问失败; 这种情况建议关闭 tcp_tw_recycle 选项,而不是 timestamp;因为 在tcp timestamp关闭的条件下,开启tcp_tw_recycle是不起作用的;而tcp timestamp可以独立开启并起作用。

0x04 总结

  • 使用过多的短连接,导致了大量的TIME_WAIT;在服务设计时,应尽量采用长连接,连接池,KEEPALIVE等技术减少对后端的连接次数,提高连接的效率。
  • 这次的问题,因PHP本身的特性,一个页面处理完成后,所有相关连接就断了,不太好使用长连接,连接池,KEEPALIVE技术;

0x05 确有频繁访问时,以上设置并不能减少 TIME_WAIT

  • 当服务器却又很多用户频繁访问,以上针对系统的优化并不能达到效果。
  • 原因试试php页面访问mysql采用的是短连接,用完就断开,频繁访问必然产生很多TIME_WAIT;
  • 解决办法, 采用长连接的方式访问数据库,这里采用php的PDO扩展访问mysql,长连接还能提高性能:
    $db_host="localhost:3306";
    $db_username="user";
    $db_password="";
    $dsn = "mysql:host=$db_host;dbname=$database;";
    
    try {
      $sql = new PDO($dsn,$db_username,$db_password,array(PDO::ATTR_PERSISTENT => true));//PDO::ATTR_PERSISTENT => true 表示采用持久化连接;
    } catch(PDOException $e){
      die($e->getMessage());
    }
    
    try{
         $result = $sql->query("select * from accounts where id = '".$sn."';");
      } catch(PDOException $e){
          die($e->getMessage());
      }
      $rows = $result->fetchAll(PDO::FETCH_ASSOC);// 这里要注意,查询结果只能调用一次fetch,后面再调用就返回空了,所以这个地方第一次调用就要保存下来给后续使用;
    if(count($rows) == 1){
          $row = $rows[0];// 多个结果时,foreach 遍历即可
          .....
      }
    或者:
      foreach($dbh->query('SELECT * from FOO') as $row) {
          print_r($row);
      }
  • php7.2 设置支持PDO扩展
vim /etc/php/7.2/cli/php.ini   这个配置文件是配置php命令行运行环境;
vim /etc/php/7.2/fpm/php.ini   这个配置 php-fpm,里面 extension=pdo_mysql 是注释掉的,但是 /etc/php/7.2/fpm/conf.d/20-pdo_mysql.ini 中有定义,所以应该是默认支持pdo的;

​ 打开注释: extension=pdo_mysql 即可;

系统优化后,再使用PDO长连接访问mysql,TIME_WAIT连接数,直线下跌几乎没有;


版权所有丨如未注明 , 均为原创丨
转载请注明原文链接:Linux很多TIME_WAIT问题的解决方法
喜欢 (5)
[谢谢打赏!]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址