linux内存回收的三种方式

linux内存回收的三种方式,第1张

1. 快速内存回收:处于get_page_from_freelist()函数中,在遍历zonelist过程中,对每个zone都在分配前进行判断,如果分配后zone的空闲内存数量 <阀值 + 保留页框数量,那么此zone就会进行快速内存回收。其中阀值可能是min/low/high的任何一种,因为在快速内存分配,慢速内存分配和oom分配过程中如果回收的页框足够,都会调用到get_page_from_freelist()函数,所以快速内存回收不仅仅发生在快速内存分配中,在慢速内存分配过程中也会发生。2. 直接内存回收:处于慢速分配过程中,直接内存回收只有一种情况下会使用,在慢速分配中无法从zonelist的所有zone中以min阀值分配页框,并且进行异步内存压缩后,还是无法分配到页框的时候,就对zonelist中的所有zone进行一次直接内存回收。注意,直接内存回收是针对zonelist中的所有zone的,它并不像快速内存回收和kswapd内存回收,只会对zonelist中空闲页框不达标的zone进行内存回收。在直接内存回收中,有可能唤醒flush内核线程。3. kswapd内存回收:发生在kswapd内核线程中,每个node有一个swapd内核线程,也就是kswapd内核线程中的内存回收,是只针对所在node的,并且只会对分配了order页框数量后空闲页框数量 <此zone的high阀值 + 保留页框数量的zone进行内存回收,并不会对此node的所有zone进行内存回收。

tcp 协议 是互联网中最常用的协议 , 开发人员基本上天天和它打交道,对它进行深入了解。 可以帮助我们排查定位bug和进行程序优化。下面我将就TCP几个点做深入的探讨

客户端:收到 ack 后 分配连接资源。 发送数据

服务器 : 收到 syn 后立即 分配连接资源

客户端:收到ACK, 立即分配资源

服务器:收到ACK, 立即分配资源

既然三次握手也不是100%可靠, 那四次,五次,六次。。。呢? 其实都一样,不管多少次都有丢包问题。

client 只发送一个 SYN, server 分配一个tcb, 放入syn队列中。 这时候连接叫 半连接 状态;如果server 收不到 client 的ACK, 会不停重试 发送 ACK-SYN 给client 。重试间隔 为 2 的 N 次方 叠加(2^0 , 2^1, 2^2 ....);直至超时才释放syn队列中的这个 TCB

在半连接状态下, 一方面会占用队列配额资源,另一方面占用内存资源。我们应该让半连接状态存在时间尽可能的小

当client 向一个未打开的端口发起连接请求时,会收到一个RST回复包

当listen 的 backlog 和 somaxconn 都设置了得时候, 取两者min值

Recv-Q 是accept 队列当前个数, Send-Q 设置最大值

这种SYN洪水攻击是一种常见攻击方式,就是利用半连接队列特性,占满syn 队列的 资源,导致 client无法连接上。

解决方案:

为什么不像握手那样合并成三次挥手? 因为和刚开始连接情况,连接是大家都从0开始, 关闭时有历史包袱的。server(被动关闭方) 收到 client(主动关闭方) 的关闭请求FIN包。 这时候可能还有未发送完的数据,不能丢弃。 所以需要分开。事实可能是这样

当然,在没有待发数据,并且允许 Delay ACK 情况下, FIN-ACK合并还是非常常见的事情,这是三次挥手是可以的。

同上

CLOSE_WAIT 是被动关闭方才有的状态

被动关闭方 [收到 FIN 包 发送 ACK 应答] 到 [发送FIN, 收到ACK ] 期间的状态为 CLOSE_WAIT, 这个状态仍然能发送数据。 我们叫做 半关闭 , 下面用个例子来分析:

这个是我实际生产环境碰到的一个问题,长连接会话场景,server端收到client的rpc call 请求1,处理发现请求包有问题,就强制关闭结束这次会话, 但是 因为client 发送 第二次请求之前,并没有去调用recv,所以并不知道 这个连接被server关闭, 继续发送 请求2 , 此时是半连接,能够成功发送到对端机器,但是recv结果后,遇到连接已经关闭错误。

如果 client 和 server 恰好同时发起关闭连接。这种情况下,两边都是主动连接,都会进入 TIME_WAIT状态

1、 被动关闭方在LAST_ACK状态(已经发送FIN),等待主动关闭方的ACK应答,但是 ACK丢掉, 主动方并不知道,以为成功关闭。因为没有TIME_WAIT等待时间,可以立即创建新的连接, 新的连接发送SYN到前面那个未关闭的被动方,被动方认为是收到错误指令,会发送RST。导致创建连接失败。

2、 主动关闭方断开连接,如果没有TIME_WAIT等待时间,可以马上建立一个新的连接,但是前一个已经断开连接的,延迟到达的数据包。 被新建的连接接收,如果刚好seq 和 ack字段 都正确, seq在滑动窗口范围内(只能说机率非常小,但是还是有可能会发生),会被当成正确数据包接收,导致数据串包。 如果不在window范围内,则没有影响( 发送一个确认报文(ack 字段为期望ack的序列号,seq为当前发送序列号),状态变保持原样)

TIME_WAIT 问题比较比较常见,特别是CGI机器,并发量高,大量连接后段服务的tcp短连接。因此也衍生出了多种手段解决。虽然每种方法解决不是那么完美,但是带来的好处一般多于坏处。还是在日常工作中会使用。

1、改短TIME_WAIT 等待时间

这个是第一个想到的解决办法,既然等待时间太长,就改成时间短,快速回收端口。但是实际情况往往不乐观,对于并发的机器,你改多短才能保证回收速度呢,有时候几秒钟就几万个连接。太短的话,就会有前面两种问题小概率发生。

2、禁止Socket lingering

这种情况下关闭连接,会直接抛弃缓冲区中待发送的数据,会发送一个RST给对端,相当于直接抛弃TIME_WAIT, 进入CLOSE状态。同样因为取消了 TIME_WAIT 状态,会有前面两种问题小概率发生。

3、tcp_tw_reuse

net.ipv4.tcp_tw_reuse选项是 从 TIME_WAIT 状态的队列中,选取条件:1、remote 的 ip 和端口相同, 2、选取一个时间戳小于当前时间戳; 用来解决端口不足的尴尬。

现在端口可以复用了,看看如何面对前面TIME_WAIT 那两种问题。 我们仔细回顾用一下前面两种问题。 都是在新建连接中收到老连接的包导致的问题 , 那么如果我能在新连接中识别出此包为非法包,是不是就可以丢掉这些无用包,解决问题呢。

需要实现这些功能,需要扩展一下tcp 包头。 增加 时间戳字段。 发送者 在每次发送的时候。 在tcp包头里面带上发送时候的时间戳。 当接收者接收的时候,在ACK应答中除了TCP包头中带自己此时发送的时间戳,并且把收到的时间戳附加在后面。也就是说ACK包中有两个时间戳字段。结构如下:

那我们接下来一个个分析tcp_tw_reuse是如何解决TIME_WAIT的两个问题的

4、tcp_tw_recycle

tcp_tw_recycle 也是借助 timestamp机制。顾名思义, tcp_tw_reuse 是复用 端口,并不会减少 TIME-WAIT 数量。你去查询机器上TIME-WAIT 数量,还是 几千几万个,这点对有强迫症的同学感觉很不舒服。tcp_tw_recycle 是 提前 回收 TIME-WAIT资源。会减少 机器上 TIME-WAIT 数量。

tcp_tw_recycle 工作原理是。

作为高性能WEB服务器,只调整Nginx本身的参数是不行的,因为Nginx服务依赖于高性能的 *** 作系统。

以下为常见的几个Linux内核参数优化方法。

net.ipv4.tcp_max_tw_buckets

对于tcp连接,服务端和客户端通信完后状态变为timewait,假如某台服务器非常忙,连接数特别多的话,那么这个timewait数量就会越来越大。

毕竟它也是会占用一定的资源,所以应该有一个最大值,当超过这个值,系统就会删除最早的连接,这样始终保持在一个数量级。

这个数值就是由net.ipv4.tcp_max_tw_buckets这个参数来决定的。

CentOS7系统,你可以使用sysctl -a |grep tw_buckets来查看它的值,默认为32768,

你可以适当把它调低,比如调整到8000,毕竟这个状态的连接太多也是会消耗资源的。

但你不要把它调到几十、几百这样,因为这种状态的tcp连接也是有用的,

如果同样的客户端再次和服务端通信,就不用再次建立新的连接了,用这个旧的通道,省时省力。

net.ipv4.tcp_tw_recycle = 1

该参数的作用是快速回收timewait状态的连接。上面虽然提到系统会自动删除掉timewait状态的连接,但如果把这样的连接重新利用起来岂不是更好。

所以该参数设置为1就可以让timewait状态的连接快速回收,它需要和下面的参数配合一起使用。

net.ipv4.tcp_tw_reuse = 1

该参数设置为1,将timewait状态的连接重新用于新的TCP连接,要结合上面的参数一起使用。

net.ipv4.tcp_syncookies = 1

tcp三次握手中,客户端向服务端发起syn请求,服务端收到后,也会向客户端发起syn请求同时连带ack确认,

假如客户端发送请求后直接断开和服务端的连接,不接收服务端发起的这个请求,服务端会重试多次,

这个重试的过程会持续一段时间(通常高于30s),当这种状态的连接数量非常大时,服务器会消耗很大的资源,从而造成瘫痪,

正常的连接进不来,这种恶意的半连接行为其实叫做syn flood攻击。

设置为1,是开启SYN Cookies,开启后可以避免发生上述的syn flood攻击。

开启该参数后,服务端接收客户端的ack后,再向客户端发送ack+syn之前会要求client在短时间内回应一个序号,

如果客户端不能提供序号或者提供的序号不对则认为该客户端不合法,于是不会发ack+syn给客户端,更涉及不到重试。

net.ipv4.tcp_max_syn_backlog

该参数定义系统能接受的最大半连接状态的tcp连接数。客户端向服务端发送了syn包,服务端收到后,会记录一下,

该参数决定最多能记录几个这样的连接。在CentOS7,默认是256,当有syn flood攻击时,这个数值太小则很容易导致服务器瘫痪,

实际上此时服务器并没有消耗太多资源(cpu、内存等),所以可以适当调大它,比如调整到30000。

net.ipv4.tcp_syn_retries

该参数适用于客户端,它定义发起syn的最大重试次数,默认为6,建议改为2。

net.ipv4.tcp_synack_retries

该参数适用于服务端,它定义发起syn+ack的最大重试次数,默认为5,建议改为2,可以适当预防syn flood攻击。

net.ipv4.ip_local_port_range

该参数定义端口范围,系统默认保留端口为1024及以下,以上部分为自定义端口。这个参数适用于客户端,

当客户端和服务端建立连接时,比如说访问服务端的80端口,客户端随机开启了一个端口和服务端发起连接,

这个参数定义随机端口的范围。默认为32768 61000,建议调整为1025 61000。

net.ipv4.tcp_fin_timeout

tcp连接的状态中,客户端上有一个是FIN-WAIT-2状态,它是状态变迁为timewait前一个状态。

该参数定义不属于任何进程的该连接状态的超时时间,默认值为60,建议调整为6。

net.ipv4.tcp_keepalive_time

tcp连接状态里,有一个是established状态,只有在这个状态下,客户端和服务端才能通信。正常情况下,当通信完毕,

客户端或服务端会告诉对方要关闭连接,此时状态就会变为timewait,如果客户端没有告诉服务端,

并且服务端也没有告诉客户端关闭的话(例如,客户端那边断网了),此时需要该参数来判定。

比如客户端已经断网了,但服务端上本次连接的状态依然是established,服务端为了确认客户端是否断网,

就需要每隔一段时间去发一个探测包去确认一下看看对方是否在线。这个时间就由该参数决定。它的默认值为7200秒,建议设置为30秒。

net.ipv4.tcp_keepalive_intvl

该参数和上面的参数是一起的,服务端在规定时间内发起了探测,查看客户端是否在线,如果客户端并没有确认,

此时服务端还不能认定为对方不在线,而是要尝试多次。该参数定义重新发送探测的时间,即第一次发现对方有问题后,过多久再次发起探测。

默认值为75秒,可以改为3秒。

net.ipv4.tcp_keepalive_probes

第10和第11个参数规定了何时发起探测和探测失败后再过多久再发起探测,但并没有定义一共探测几次才算结束。

该参数定义发起探测的包的数量。默认为9,建议设置2。

设置和范例

在Linux下调整内核参数,可以直接编辑配置文件/etc/sysctl.conf,然后执行sysctl -p命令生效


欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/yw/7416453.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-05
下一篇 2023-04-05

发表评论

登录后才能评论

评论列表(0条)

保存