这两天一直在研究这个话题,踩了几个坑,把遇到的东西整理成文,供有需要的朋友参考。
文章目录
一、资源从上往下关闭
1.应用层Socket视角下的关闭
进程Socket对象是里面装引用的普通变量,文件描述符、内核Socket对象是引用对象变量
1.1Socket自己关闭时用的API
1.1.1Socket.flush
Socket.flush是应用层自己决定,现在一次把此时的应用层用户态缓冲区里的全部数据,冲入到传输层发送缓冲区里,进行对,用户态缓冲区->发送缓冲区的自然流速,当区量迅速发完地提速
1.1.2Socket.close
Socket.close是应用层自己决定 现在自己关闭Socket对象,放FIN划出这样的发送边界的:
1. 处FIN前:还在发送缓冲区里与还在发送分流中未到达对方数据为预剩发送数据
2. 处FIN后:还在应用层用户态缓冲区里的数据成遗弃不发数据
1.2进程崩溃时Socket被动的关闭
1.2.1无抉迫划当现发送边界
进程崩溃,里面的堆区毁掉,Socket变量一同毁没,不按自己抉择地现在就放FIN划当现发送边界,就放漏了 处在当现后面,抉择中有进行的:
1. 应用层数据的当后再更新
2. 应用层往传输层当后再流调的数据分布
3. 当后再划出,在那时是那样的自己选择的发送边界
1.2.2无引用后遇走释放流程
Socket变量没了之后,它一对一对应的文件描述符引用对象也没了,等到内核Socket引用对象它一对多的文件描述符全部没了,Socket对象无引用进入释放流程,遇到走里面一个:
- 传FIN进行四次挥手地释放
- 传RST进行得知就中止地释放
- 超时ACK进行异常地处理
2.传输层TCP连接视角下的关闭
TCP把终止分成:
2.1正常关闭机制
调用close使用FIN报文,在走挥手流程,确保发送数据处理完地正常关闭,解决的是"还能通信时,怎样有序告别"的场景
2.1.1FIN报文
2.1.1.1收发
1. 传输层:传输层在连接流发送,接收FIN 会关划发收通道的
2. 应用层:应用层取完接收缓冲区的所有FIN前数据后得到EOF
2.1.1.2分关
每端的发送、接收流 是分开地结束的
2.1.1.3可靠
FIN是可靠传输,单次里可以发多个,直到确认到达
2.1.2先挥方最后一个ACK报文
2.1.2.1报文的确达不定
'去确定报文能否到达'本身就是无法确定能完成的事,所有报文的确认,要么收到ACK就能100%确认到它已送达,要么收不到ACK就无法确认它有没有到达(没有能100%确认到它没送达的)
2.1.2.2ACK的占比保障
2.1.2.2.1方法
2.1.2.2.1.1TIME_WAIT等待2MSL
TIME_WAIT-2MSL,选择保持连接先不关闭和保持的时间是2MSL:
1. 是一种放弃了 去确认 ACK报文的,如果送达且能收到的停止折腾依据
2. 而是选择主动停下折腾、以很小的成本、同样占"如果能到达的保障比例"很大地排掉了偶然因素 地去保障
2.1.2.2.1.2使用其它报文避免设计ACK递归地重传保障
确认ACK到达,其实也可以设计成不用ACK报文,用别的报文 再来一次报文的有超时重传保障的确认到达,就不会有把ACK报文设计成递归确认的问题:
1. 能占"如果能到达的保障比例"很大
2. 且根据如果ACK能到达的且如果能确认到依据而停止折腾,否则继续往下折腾到超量才停止
2.1.2.2.1.3做仅此ACK有的特殊标记避ACK递归地重传
或者对先挥方的这个最后发的ACK报文做特殊标记处理,只让它这时的ACK报文可以继续有ACK的确认回复机制,接着也是能对它进行多次的重传确认:
1. 也是能占"如果能到达的保障比例"很大并
2. 且也根据如果ACK能到达的能确认到的且如果能确认到依据而停止折腾,否则继续往下折腾到超量才停止
2.1.2.2.2选择
TCP协议里选择了使用TIME_WAIT等待2MSL的保障方法,因为额外的设计达成成本小,并且我方尽很大"如果能到达的保障比例"后,我方没有去依据,确认到ACK送达后再停止折腾,而是主动停下折腾
2.1.2.2.2.1原因
因为即使如果它也去确认到达依据:
- 如果确认送达了,是停止折腾接着去单断
- 如果没有确认到送达,此时也已经尽很大保障比例了:本身它如果到达,能确认到它到达本身是个概率事件,而且经过很大保障比例后都没确认到,大概率后面也是无法再确认到了,但没有收到依据就得继续折腾下去到超量、而且经过发送很大保障后,它ACK实际结果能送达的概率也很大了
2.1.2.2.2.2效果
于是此时选择主动停下折腾,不以确认ACK到达而作为停止依据 而陷入更深的,仅为了我方要确定到,而实际可能已送达,实际上很多时候根本无法确定的 超量折腾,此时就已经花费最好的保障成本效率地,取舍可能对方确实没有收到ACK地花费更大巨大代价修复地,去单断连接了
2.2异常中止机制
调用abort/reset使用RST报文,不走挥手流程,不保发送数据正常处理完地,这条连接现在立即作废地异常关闭,解决的是"连接要立即作废"的场景
2.2.1RST报文
2.2.1.1收发
1. 传输层:传输层在连接流发送,接收RST 会立即中止单断连接的
2. 应用层:往上给应用层报connection rest通知
2.2.1.2并关
每端的发送、接收流 是一起地结束的
2.2.1.3不可靠
RST是不可靠传输,单次一个就发完,就要在下次里再发下个了
2.2.1.4发送情况
2.2.1.4.1连接不存在
主机处于CLOSE或处在新建的连接里时 收到 不在该连接的别的连接里的报文时(很有可能就是,我方上一个已经单断开的旧连接里的,不知情的对方,仍能照着我方IP端口,发来的消息),根据此报文的源IP端口四元组信息 给他回应RST,我这里没有你这条连接
对方收到RST后 便会清理掉这个旧的,是半打开的连接状态
2.2.1.4.2遇到异常错误
程序或者内核出现错误,可以选择:不再保证剩余数据正常交付,直接中止单断我方连接 发送RST
对方收到RST后 也跟着去单端断开连接了
3.关机的限时关闭
关机时,
3.1第一限时
3.1.1限时内
对关闭进程Socket选划边界限第一个时:通知进程,让它自己选划发送边界地关闭
3.1.2超时后
超时后崩溃进程,被动直接划发送边界地关闭
3.2第二个限时
3.2.1限时内
对关闭文件描述符->内核Socket对象->TCB释放连接 限第二个时:进程关闭后,按它自己断开连接的流程,遇到哪种情况就应对地进去处理
3.2.2超时后
超时后该单端主机断电 就会直接没了里面的 所有进程、一个内核里面的:文件描述符表,TCP协议栈及里面的TCB及维护的连接状态
二、靠回应验连接
我方发送消息收到ACK回应,说明:
1. 我方能正常发消息
2. 消息能过去
3. 对方能正常收消息
4. 对方能正常发消息
5. 消息能过来
6. 我方能正常收消息
我方就能检验出没有异常,反之收不到ACK回应即检验出有异常
重传、keepalive、应用层心跳,这些额外补充的发送消息:
1. 重传是收不到回应时 再次进一步的试探检验
2. 心跳包是加速开启检验 试探回应地 检验连接可用性
用于”我方根据对方回应,推断连接能否用”的场景
1.维护不确效连接的时间
我方 收到对方ACK回应消息,的接收时间间隔,就是我方 不确定连接是否有效无效地,维护连接 的时间
1.1无效连接的浪费维护
如果连接无效了(比如对方挂了/通信道路断了),就会在 最后一次的不确效连接维护时间段里:
上一次收到ACK回应往后,从连接开始失效的某节点位置开始,直到后面我方发送消息,试探回应超时收不到ACK,确定出连接失效的维护段结尾,都会是我方浪费的无效连接维护
1.1.1异常时机的浪费量影响
异常出现位置是无法控制干预的,异常可出现在 上次收到对方ACK回应的,不确效维护连接段头 到 最后发送消息后超时收不到ACK回应的,确无效连接段尾 的任意位置:
- 如果出现得靠前,那么浪费维护的时间段就大
- 如果出现得靠后,那么浪费维护的时间段就小
2.发送频率
2.1正常间隔发送消息
如果一方 是正常频率发送消息,就是正常间隔地试探回应检验异常,那么:
1. 它的不确效维护时间会正常
2. 浪费段等比例位置出现,但占的总段小,浪费的风险成本小
3. 异常检测的间隔及时性也正常
2.2间隔很久发送消息
2.2.1浪维风险
如果一方间隔很久才发送消息,那么从它上一次收到ACK 直到它过完超长时间间隔,下次发送消息试探ACK回应:
1. 都是它不确效维护的超长时间
2. 如果有异常,在这么长段的任意位置出现,出现位置往后所占比例都还是随机不变的,但总段变长了,不确效维护段↑不变比例,对应的浪费时间的风险成本是变大的
3. 异常检查的间隔及时性是特别晚的
(而如果出现异常,对方正常频率发送消息的话,很快就注意到收不到回复,早早检测到异常单断了,很快检测出有异常、不确效维护的时间短、不确效总长短,等占例出现的浪费维护风险成本小;而如果对方也一样间隔很久地发消息,那么双方一起2)
2.2.2心跳措施
2.2.2.1传输层keepalive
对于会间隔很久发送消息的这端,常常会手动配置上传输层的keepalive心跳,保障回应检测:
2.2.2.1.1过程
当隔过keepalive idle>=2h时间没发送消息后,就在传输层操作系统内核制作keepalive probe心跳包,使用一个特殊序号SEQ=SND.NXT-1,从我端传输层往下封装发送,传到对端接收往上分用,在传输层就由操作系统处理完地,从前往后检查到当前连续前缀到的序号,就往下封装回复ACK回应了
2.2.2.1.2作用
就保障了发送消息最大keepalive idle间隔后至少有发消息,限下了 发送消息的最大时间间隔、不确效维护的最大时间、异常检测的最大间隔时间
2.2.2.1.3局限
2.2.2.1.3.1内容少
只能探测到 对方传输层<->我方传输层的,TCP连接是健康的
2.2.2.1.3.2受干扰
传输层往下会受到
- 自己传输层的整台机器CPU严重饱和内核卡顿
- 往下底层的网卡队列拥塞、网络拥堵
- 的可能变为长期异常失效的临时状况抖动
2.2.2.1.3.3间隔长
保障至少有发的,维护探测间隔时间太长:Keepalive>=2h
2.2.2.2应用层心跳包
2.2.2.2.1优势
2.2.2.2.1.1自定义短间隔
在我方应用层制作的PING/PONG/GET health心跳包,能根据实际如果要更快的异常检测频率,就自定义地缩短应用层心跳探测发送的间隔时间
2.2.2.2.1.2检测内容更多
能测完整 我方应用层发<->对方应用层收 不仅传输层连接,还包括了应用层的服务健康:
- 进程还在
- 应用线程还能处理请求
- HTTP服务正常
- 数据库连接可能正常
- 依赖服务可能正常
2.2.2.2.2劣势
2.2.2.2.2.1受到的干扰更多
应用层的ping,往下受到传输层到底层的临时状况抖动外,还额外有受到自己应用层的临时状况抖动:
线程调度
事件循环卡住
CPU过载
锁竞争
于是要ping更多次超时失败,才会更少误判出是长久性不会自己恢复的失效异常,标记unhealthy
2.2.2.3心跳抖动jitter
心跳包工程上 会加一点随机抖动jitter,打散所有机器的心跳时间,避开全部都同一时刻心跳产生的瞬间流量尖峰
三、负载均衡器
负载均衡器对后端服务器进行:
1.健康检查
- TCP连接检查
- HTTP GET /health
- HTTPS检查
- gRPC health check
- 自定义ping/pong
- 被动观察请求错误率
1.1经受干扰
健康检查要经得住 随机临时的状况抖动:
- 偶尔一次网络抖动
- GC 5md
- 调度延迟
1.1.1工程平衡
要容纳耐受在,持久无法自恢复的异常,判断之外,工程上平衡:
检测越快
↓
虽故障恢复越快,
但误判概率越大、开销越大
检测越慢
↓
虽误判少、开销小,
但故障注意到慢
2.选出服务
用算法:
- 加权轮询
- Least Connection
- Least Requests
- 延迟优先
- 一致性哈希
- 资源负载
- EWMA延迟
3.木板资源瓶颈
系统吞吐量会受最先耗尽的瓶颈资源min(
1. CPU的时间占用率,
2. 网络速率,
3. 内存容量,
4. 线程阻塞,
5. 数据库性能,
6. 连接池容量
)限制
4.安全重传请求
请求没有得到ACK回复,就无法直接地确定出请求是否到达,此时如果直接重传请求就有可能造成重复送达,有实际业务的真实系统会受到:
- 幂等性
- request ID
- 去重
- 事务设计
四、URG与PSH
1.URG=Urgent
传输层:正常TCP字节流里标出紧急信息段的始边界,用Urgent Pointera指出紧急始边界正偏移量后的末边界
应用层:传输层TCP会通知接收的应用层,当前数据流中存在紧急信息,应用可以用socket API对它采取特殊处理
2.PSH=Push
传输层:用于发送方提示接收TCP:这些已经到达接收缓冲区的数据,不要为了凑更多字节而长时间缓冲,应尽快把它们交付给应用层
本次分享就到这里。技术这东西越研究越有意思,后续有新的收获我也会继续更新。
评论 (0)
暂无评论