惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Comments on: Blog
S
Schneier on Security
Microsoft Azure Blog
Microsoft Azure Blog
T
Tor Project blog
V
Visual Studio Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Spread Privacy
Spread Privacy
月光博客
月光博客
罗磊的独立博客
Cisco Talos Blog
Cisco Talos Blog
P
Privacy International News Feed
T
Tenable Blog
阮一峰的网络日志
阮一峰的网络日志
AWS News Blog
AWS News Blog
T
ThreatConnect
博客园 - 三生石上(FineUI控件)
Recorded Future
Recorded Future
Hugging Face - Blog
Hugging Face - Blog
T
Tailwind CSS Blog
博客园 - 叶小钗
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
A
Arctic Wolf
L
LINUX DO - 最新话题
美团技术团队
大猫的无限游戏
大猫的无限游戏
I
Intezer
博客园 - 司徒正美
酷 壳 – CoolShell
酷 壳 – CoolShell
量子位
小众软件
小众软件
T
Threatpost
V
V2EX
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
宝玉的分享
宝玉的分享
The Register - Security
The Register - Security
Project Zero
Project Zero
J
Java Code Geeks
Cyberwarzone
Cyberwarzone
IT之家
IT之家
MyScale Blog
MyScale Blog
T
Threat Research - Cisco Blogs
T
The Blog of Author Tim Ferriss
腾讯CDC
S
SegmentFault 最新的问题
F
Fox-IT International blog
S
Security Archives - TechRepublic
Last Week in AI
Last Week in AI
G
GRAHAM CLULEY
M
MIT News - Artificial intelligence

蛮荆

如何获取更多的免费服务器 Kubernetes 调度器队列 - 设计与实现 Kubernetes 调度器 - 核心流程 Kubernetes Networking Model & CNI Kubernetes 控制器管理总结 Kubernetes CronJob 设计与实现 Kubernetes Job 设计与实现 Kubernetes HPA 设计与实现 Kubernetes Deployment 滚动更新实现原理 Kubernetes GC 设计与实现 Kubernetes Pod 驱逐 - 设计与实现 Kubernetes Daemonset 设计与实现 Kubernetes ReplicaSet 设计与实现 Kubernetes EndPoint 设计与实现 Kubernetes Informer 设计与实现 降本增效之应用优化 (三) 日志存储与检索 Kubernetes Pod 设计与实现 - 创建流程 Kubernetes 探针设计与实现 Unix 编程艺术名句摘录 Kubernetes - CRI 概述 Golang 编译速度为什么这么快? Kubernetes Pod 设计与实现 - Pause 容器 Kubernetes - kube-proxy 代理模式工程优化 Kubernetes 应用最佳实践 - 优雅关闭长连接 Kubernetes Service 类型和会话亲和性 Kubernetes 为什么需要 Ingress Kubernetes 架构 - 控制平面和数据平面 降本增效之应用优化 (二) 大报表 Go 语言如何获取 CPU 利用率 降本增效之应用优化 (一) Redis 业务规则引擎演变过程简述 微服务中的熔断算法 漏桶算法和令牌桶算法 jsonparser 为什么比标准库的 encoding/json 快 10 倍 ? zap 高性能设计与实现 HTTP Router 算法演进 布谷鸟过滤器 fastcache 高性能设计与实现 Web 常见的三个安全问题 ants Code Reading 布谷鸟过滤器 Go 线程安全 map 方案选型 布隆过滤器 死锁、活锁、饥饿、自旋锁 sync.Pool Code Reading Go 内存管理概述 Go netpoll Code Reading goroutine 泄漏与检测 time/Timer Code Reading GMP Scheduler Code Reading Go channel 的 15 条规则和底层实现 为什么 Linux “一切皆文件” context.Context Code Reading runtime/HACKING.md Goland 最佳实践 互联网开发与金庸武学 为什么 Redis 6.0 引入多线程模型? Kubernetes 应用最佳实践 - 金丝雀发布 容器中如何正确配置 GOMAXPROCS ? singleflight Code Reading sync.Map Code Reading sync.Cond Code Reading sync.WaitGroup Code Reading sync.RWMutex Code Reading sync.Mutex Code Reading sync.Once Code Reading Go 无锁编程 sync/atomic Code Reading goroutine 交替打印奇偶数 GODEBUG Go 并发模式 Go 汇编 UUID 通用技术选型 Kubernetes 应用最佳实践 - 水平自动伸缩 Go 高性能 Tips fasthttp 为什么比标准库 net/http 快 10 倍 ? 技术文章配图指南 ChatGPT 初体验 Docker 网络原理概览 iptables 的五表五链 Kubernetes 应用最佳实践 - 亲和性和污点容忍度 Go 的反射与三大定律 Docker 官方提供的最佳实践 Go 语言内置的设计模式 HTTP1 到 HTTP3 的工程优化 Kubernetes 应用最佳实践 - Sidecar 模式 Kubernetes 应用最佳实践 - init 容器和钩子函数 为什么 recover 必须在 defer 中调用? 为什么 defer 的执行顺序和注册顺序不同? Go map 设计与实现 Go 切片扩容底层实现 Go 语言中的零拷贝 Go Delve 云原生和边缘计算简介 Kubernetes Pod 服务质量等级 Kubernetes 应用最佳实践 - 探针 Kubernetes 应用最佳实践 - 资源请求和限制 CDN 原理 Kubernetes 应用最佳实践 - 开篇 缓存策略和模式
为什么应用层心跳检测是必要的
2020-10-17 · via 蛮荆

为什么应用层心跳检测是必要的

2020-10-17 计算机网络 网络编程

TCP 心跳

TCP Keepalive 是一种用于检测 TCP 连接是否活跃的机制,通过定期发送探测数据包来确定连接的状态,主要用于检测空闲 (僵尸) 连接、保持 NAT 映射 (NAT 设备、防火墙设备) 等。

原理简述

  1. 要启用 TCP Keepalive 自动检测机制,需要通信双方都开启 Keepalive 选项
  2. 如果在一定时间(默认 2 小时)内没有数据传输,TCP 会发送一个 Keepalive 探测数据包
  3. 如果通信的对方仍然活跃,就会对该探测数据包进行响应,如果对方没有响应,TCP 将重试发送探测数据包
  4. 在达到最大重试次数(默认 10 次)后,如果仍然未收到响应,TCP 将认为连接已断开,关闭连接

下面是根据 TCP Keepalive 工作原理,转换后的逻辑伪代码 (针对单个 TCP 连接)。

# TCP Keepalive 控制参数
KEEPALIVE_INTERVAL = 7200  # 默认 2 小时
KEEPALIVE_PROBES = 10   # 默认 10 次
KEEPALIVE_TIMEOUT = 75  # 默认 75 秒

# 开启 TCP Keepalive 机制
# 初始化各项控制参数
def enable_keepalive(socket):
    socket.setsockopt(..., socket.SO_KEEPALIVE, 1)

    socket.setsockopt(..., KEEPALIVE_INTERVAL)
    socket.setsockopt(..., KEEPALIVE_PROBES)
    socket.setsockopt(... KEEPALIVE_TIMEOUT)

# 如果连接在 Keepalive 间隔时间内处于空闲状态
# 发送 Keepalive 探测包并启动探测计时器
def send_keepalive_probe(socket):
    if is_idle(socket, KEEPALIVE_INTERVAL):
        send_probe_packet(socket)
        start_probe_timer(socket)

# 处理 Keepalive 响应
# 如果收到探测包的 ACK 确认,则重置空闲计时器
# 否则增加探测次数
#   如果超过最大探测次数,则关闭连接
# Function to handle keepalive response
def handle_keepalive_response(socket):
    if received_probe_ack(socket):
        reset_idle_timer(socket)
    else:
        increment_probe_count(socket)
        
        if probe_count(socket) > KEEPALIVE_PROBES:
            close_connection(socket)

# 检查Keepalive超时
# 如果探测计时器过期,则处理 Keepalive 响应
def check_keepalive_timeout(socket):
    if probe_timer_expired(socket):
        handle_keepalive_response(socket)

# 核心主循环管理 Keepalive
# 1. 启用 Keepalive 选项
# 2. 在连接打开时定期发送探测包和检查超时
# Main loop to manage keepalive
def manage_keepalive(socket):
    enable_keepalive(socket)
    
    while is_open(socket):
        send_keepalive_probe(socket)
        check_keepalive_timeout(socket)
        time.sleep(KEEPALIVE_TIMEOUT) 

...
...

相关参数

Linux 内核中和 TCP Keepalive 机制相关的几个参数如下:

  • tcp_keepalive_time:首次探测之前的空闲时间(默认 2 小时)
  • tcp_keepalive_intvl:重试探测的时间间隔(默认 75 秒)
  • tcp_keepalive_probes:最大重试次数(默认 10 次)

当然,这些参数都可以通过修改系统配置文件进行修改,尤其在优化高并发场景和移动场景为主的后端服务器时,这几个参数需要着重优化一下:

# 设置首次探测之前的空闲时间为 10 分钟
echo 600 > /proc/sys/net/ipv4/tcp_keepalive_time

# 设置重试探测的时间间隔为 15 秒
echo 15 > /proc/sys/net/ipv4/tcp_keepalive_intvl

# 设置最大重试次数为 3 次
echo 3 > /proc/sys/net/ipv4/tcp_keepalive_probes

运行 sysctl -p 命令生效,重启之后仍然有效。

局限性

TCP Keepalive 机制由内核 (操作系统) 负责执行,当进程退出后,内核会针对进程中未关闭的连接逐个进行关闭 (向连接的通信对方发送 FIN 报文),这样就保证了每个连接的通信双方都可以知道通信的状态,并根据状态来完成不同的具体业务逻辑。

表面上看,不论进程是运行还是退出,TCP Keepalive 机制都可以通过内核很好地完成,但是在一些极端场景中,内核无法保证 TCP 协议栈正常工作,例如:

  • 操作系统异常导致重启,TCP 协议栈没有机会发送 FIN 报文
  • 服务器硬件故障、基础设置故障 (如断电、断网、地理不可抗力因素),TCP 协议栈同样没有机会发送 FIN 报文
  • 海量并发连接数,操作系统或进程重启时,TCP 协议栈可能无法断开所有连接,也就是 FIN 报文出现丢包后,没有更多的时间进行重试
  • 网络链路故障,只能等到 TCP Keepalive 检测超时,通信双方才能确认这种情况,此时距离发生故障可能已经过去了一段时间

应用层心跳

必要性

前文中讲到了 TCP Keepalive 机制 (内核实现) 的局限性,除此之外,结合到应用层一起来看的话,TCP Keepalive 机制无法确认应用层的心跳检测目标:应用程序还在正常工作。具体来说,TCP Keepalive 检测结果正常,只能说明两件事情:

  1. 应用程序 (进程) 还存在
  2. 网络链路正常

但是 当应用程序进程运行中发生异常时,例如死锁、Bug 导致的无限循环、无限阻塞 等,虽然此时操作系统依然可以正常执行 TCP Keepalive 机制,但是对于应用程序的异常情况,通信对方是无法得知的。

此外,应用层心跳检测具有更好的灵活性,例如可以控制检测时间、间隔、异常处理机制、附加额外数据等。

综上所述,应用层心跳检测是必须实现的。

实现方式

常见的应用层心跳实现方式有:

  • HTTP: 访问指定 URL, 根据响应码或者响应数据来判定应用是否正常
  • Exec: 执行指定 (Shell) 命令 (例如文件检查、网络检查),并检查命令的退出状态码,如果状态码为 0,说明应用正常运行
  • WebSocket: 和 HTTP 检测方式类似
  • 其他自定义检测方式

其中业界主流的检测方式是 HTTP (长连接方式), 主要是因为:

  1. HTTP 实现简单,基于长连接的方式避免了连接的建立和释放带来的开销
  2. HTTP 对于 (异构) 环境的要求很低,而且大多数应用中都使用 HTTP 作为 API 主要通信协议,心跳检测并不会带来多少额外的工作量

实现细节

1. 不要单独实现 “心跳线程”

使用单独的线程来实现 “心跳检测”,虽然可以将心跳检测应用代码和具体的业务逻辑代码隔离,但是当 “业务线程” 发生死锁或者 Bug 崩溃时,心跳线程检测不到。

所以应该将心跳检测直接实现在 “业务线程” 中。

2. 不要单独实现 “心跳连接”

对于网络 (例如 TCP) 编程的场景,心跳检测应该在 “业务连接” 直接实现,而不是使用单独的连接,这样当业务连接出现异常时,通信对方可以第一时间感知到 (没有及时收到心跳响应)。

此外,大多数网络防火墙会定时监测空闲 (僵尸) 连接并清除,如果心跳检测使用额外的连接,那么当 “业务连接” 长时间没有要发送的数据时,就已经被防火墙断开了,但是此时心跳检测连接还在正常工作,这会影响通信对方的判断,以为 “业务连接” 还在正常工作。

所以应该将心跳检测直接实现在 “业务连接” 中。

扩展阅读