<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>TCP on 最后的DBA</title><link>https://lastdba.com/tags/tcp/</link><description>Recent content in TCP on 最后的DBA</description><generator>Hugo -- gohugo.io</generator><language>zh-CN</language><copyright>© 2026 liuzhilong62</copyright><lastBuildDate>Wed, 10 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://lastdba.com/tags/tcp/index.xml" rel="self" type="application/rss+xml"/><item><title>浅析连接池和TCP探活</title><link>https://lastdba.com/2026/06/10/%E6%B5%85%E6%9E%90%E8%BF%9E%E6%8E%A5%E6%B1%A0%E5%92%8Ctcp%E6%8E%A2%E6%B4%BB/</link><pubDate>Wed, 10 Jun 2026 00:00:00 +0000</pubDate><guid>https://lastdba.com/2026/06/10/%E6%B5%85%E6%9E%90%E8%BF%9E%E6%8E%A5%E6%B1%A0%E5%92%8Ctcp%E6%8E%A2%E6%B4%BB/</guid><description>&lt;blockquote&gt;&lt;p&gt;本文AI率50%&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;DBA了解一些连接池和TCP的探活保活知识也是比较重要的，对一些业务断连报错，SQL执行报错，HA高可用都有帮助。&lt;/p&gt;

&lt;h2 class="relative group"&gt;TCP的keepalive和PG的参数
 &lt;div id="tcp的keepalive和pg的参数" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#tcp%e7%9a%84keepalive%e5%92%8cpg%e7%9a%84%e5%8f%82%e6%95%b0" aria-label="锚点"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;应用（包括业务客户端、数据库server、psql）和操作系统都可以设置socket选项。如果没有显示设置，那么一般都是用的linux内核参数的默认值。&lt;/p&gt;</description><content:encoded>&lt;blockquote&gt;&lt;p&gt;本文AI率50%&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;DBA了解一些连接池和TCP的探活保活知识也是比较重要的，对一些业务断连报错，SQL执行报错，HA高可用都有帮助。&lt;/p&gt;

&lt;h2 class="relative group"&gt;TCP的keepalive和PG的参数
 &lt;div id="tcp的keepalive和pg的参数" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#tcp%e7%9a%84keepalive%e5%92%8cpg%e7%9a%84%e5%8f%82%e6%95%b0" aria-label="锚点"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;应用（包括业务客户端、数据库server、psql）和操作系统都可以设置socket选项。如果没有显示设置，那么一般都是用的linux内核参数的默认值。&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;linux参数&lt;/th&gt;
 &lt;th&gt;linux默认值&lt;/th&gt;
 &lt;th&gt;Socket选项&lt;/th&gt;
 &lt;th&gt;PG server参数&lt;/th&gt;
 &lt;th&gt;libpq参数（PG client）&lt;/th&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;SO_KEEPALIVE（默认1）&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;keepalives&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;1(default),on&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;tcp_keepalive_time&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;7200s&lt;/td&gt;
 &lt;td&gt;TCP_KEEPIDLE&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;tcp_keepalives_idle&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;keepalives_idle&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;tcp_keepalive_intvl&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;75s&lt;/td&gt;
 &lt;td&gt;TCP_KEEPINTVL&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;tcp_keepalives_interval&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;keepalives_interval&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;tcp_keepalive_probes&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;9&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;tcp_keepalives_count&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;keepalives_count&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;tcp_retries2&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;15&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;TCP_USER_TIMEOUT&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;tcp_user_timeout&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;tcp_user_timeout&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;client_connection_check_interval&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;PG server和libpq默认值都是使用OS的socket默认值。&lt;/p&gt;
&lt;p&gt;默认值的含义：达到2小时ildle的连接，tcp内核主动发送keepalive，在75s*9=11.25min后中断连接。&lt;/p&gt;
&lt;p&gt;默认&lt;code&gt;net.pv4.tcp_keepalive_time=7200s&lt;/code&gt;，这个值太大了，几乎毫无意义，等网络中间层比如防火墙设备掐断连接了才来做keepalive有什么意义呢。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;client_connection_check_interval&lt;/code&gt; 是 PG 14 引入的应用层机制——PG 服务端每隔 N 毫秒对客户端 socket 做一次非阻塞 &lt;code&gt;recv()&lt;/code&gt;，如果返回错误（连接断开）就主动清理。这不需要任何 Linux 内核参数配&lt;/p&gt;

&lt;h2 class="relative group"&gt;TCP的FIN和RST包
 &lt;div id="tcp的fin和rst包" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#tcp%e7%9a%84fin%e5%92%8crst%e5%8c%85" aria-label="锚点"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;参考 &lt;a href="https://linuxvox.com/blog/what-is-the-reason-and-how-to-avoid-the-fin-ack-rst-and-rst-ack/" target="_blank" rel="noreferrer"&gt;https://linuxvox.com/blog/what-is-the-reason-and-how-to-avoid-the-fin-ack-rst-and-rst-ack/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;TCP 6个control bits:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Flag&lt;/th&gt;
 &lt;th&gt;Name&lt;/th&gt;
 &lt;th&gt;Purpose&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;SYN&lt;/td&gt;
 &lt;td&gt;Synchronize&lt;/td&gt;
 &lt;td&gt;Initiates a connection (used in the handshake).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;ACK&lt;/td&gt;
 &lt;td&gt;Acknowledge&lt;/td&gt;
 &lt;td&gt;Confirms receipt of a packet (includes an ACK number for sequence tracking).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;FIN&lt;/td&gt;
 &lt;td&gt;Finish&lt;/td&gt;
 &lt;td&gt;Signals intent to close a connection gracefully.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;RST&lt;/td&gt;
 &lt;td&gt;Reset&lt;/td&gt;
 &lt;td&gt;Abruptly terminates a connection (no graceful closure).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;PSH&lt;/td&gt;
 &lt;td&gt;Push&lt;/td&gt;
 &lt;td&gt;Forces immediate delivery of data (bypasses buffering).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;URG&lt;/td&gt;
 &lt;td&gt;Urgent&lt;/td&gt;
 &lt;td&gt;Marks data as &amp;ldquo;urgent&amp;rdquo; (rarely used today).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;FIN和RST都有正常情况和异常情况发的，总结几个要点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;进程退出或程序abort发的是FIN包，这包括KILL -9（已验证PG进程kill -9发FIN包，见“测试”部分）&lt;/li&gt;
&lt;li&gt;端口不可达等网络不可用是RST包&lt;/li&gt;
&lt;li&gt;TCP keepalive超时也是RST包，因为探测出了网络不可用&lt;/li&gt;
&lt;li&gt;防火墙也有可能做RESET&lt;/li&gt;
&lt;li&gt;RST包跟业务层 &lt;code&gt;connection reset by peer&lt;/code&gt; 报错相关&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;下面是 TCP 6个控制位和 FIN/RST 的详细说明：&lt;/p&gt;

&lt;h2 class="relative group"&gt;TCP断连测试
 &lt;div id="tcp断连测试" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#tcp%e6%96%ad%e8%bf%9e%e6%b5%8b%e8%af%95" aria-label="锚点"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;

&lt;h3 class="relative group"&gt;测试：KILL会话是否有主动断开操作
 &lt;div id="测试kill会话是否有主动断开操作" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e6%b5%8b%e8%af%95kill%e4%bc%9a%e8%af%9d%e6%98%af%e5%90%a6%e6%9c%89%e4%b8%bb%e5%8a%a8%e6%96%ad%e5%bc%80%e6%93%8d%e4%bd%9c" aria-label="锚点"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;ORACLE无论是内置的&lt;code&gt;alter system&lt;/code&gt; 杀会话还是&lt;code&gt;kill -9&lt;/code&gt;杀会话，客户端均有收到服务端发的FIN包。&lt;/li&gt;
&lt;li&gt;PG内置&lt;code&gt;pg_terminate_backend()&lt;/code&gt;杀会话，客户端有收到服务端发的FIN包。&lt;/li&gt;
&lt;li&gt;redis关库或者kill -9 redis-server进程，客户端有收到服务端发的FIN包。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;测试结论：即便是进程异常中断，tcp内核也可以发FIN包。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;另外，该轮测试中，redis-cli看起来没有正确处理FIN包，是自己rst的：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th style="text-align: left"&gt;序号&lt;/th&gt;
 &lt;th style="text-align: left"&gt;时间&lt;/th&gt;
 &lt;th style="text-align: left"&gt;方向&lt;/th&gt;
 &lt;th style="text-align: left"&gt;Flags&lt;/th&gt;
 &lt;th style="text-align: left"&gt;说明&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;1&lt;/td&gt;
 &lt;td style="text-align: left"&gt;17:42:43.131958&lt;/td&gt;
 &lt;td style="text-align: left"&gt;服→客&lt;/td&gt;
 &lt;td style="text-align: left"&gt;&lt;code&gt;.&lt;/code&gt; ACK&lt;/td&gt;
 &lt;td style="text-align: left"&gt;服务端回ACK&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;2&lt;/td&gt;
 &lt;td style="text-align: left"&gt;17:42:49.264831&lt;/td&gt;
 &lt;td style="text-align: left"&gt;服→客&lt;/td&gt;
 &lt;td style="text-align: left"&gt;[F.] FIN+ACK&lt;/td&gt;
 &lt;td style="text-align: left"&gt;服务端主动请求关闭&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;3&lt;/td&gt;
 &lt;td style="text-align: left"&gt;17:42:49.304905&lt;/td&gt;
 &lt;td style="text-align: left"&gt;客→服&lt;/td&gt;
 &lt;td style="text-align: left"&gt;&lt;code&gt;.&lt;/code&gt; ACK&lt;/td&gt;
 &lt;td style="text-align: left"&gt;客户端确认 FIN（ack=9=8+1）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;4~15&lt;/td&gt;
 &lt;td style="text-align: left"&gt;17:43:04 ~ 17:44:19&lt;/td&gt;
 &lt;td style="text-align: left"&gt;客→服&lt;/td&gt;
 &lt;td style="text-align: left"&gt;&lt;code&gt;.&lt;/code&gt; ACK&lt;/td&gt;
 &lt;td style="text-align: left"&gt;客户端持续 ACK（保持连接？）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;16&lt;/td&gt;
 &lt;td style="text-align: left"&gt;17:44:19.323962&lt;/td&gt;
 &lt;td style="text-align: left"&gt;服→客&lt;/td&gt;
 &lt;td style="text-align: left"&gt;[R] RST&lt;/td&gt;
 &lt;td style="text-align: left"&gt;服务端发 RST&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 class="relative group"&gt;测试：PG进程终止、正常关库、暴力关库，客户端收到什么包
 &lt;div id="测试pg进程终止正常关库暴力关库客户端收到什么包" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e6%b5%8b%e8%af%95pg%e8%bf%9b%e7%a8%8b%e7%bb%88%e6%ad%a2%e6%ad%a3%e5%b8%b8%e5%85%b3%e5%ba%93%e6%9a%b4%e5%8a%9b%e5%85%b3%e5%ba%93%e5%ae%a2%e6%88%b7%e7%ab%af%e6%94%b6%e5%88%b0%e4%bb%80%e4%b9%88%e5%8c%85" aria-label="锚点"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;测试环境：Rocky 10.1 + PG 18.2，tcpdump 抓 lo 口 TCP 包。&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;场景&lt;/th&gt;
 &lt;th&gt;服务端发的包&lt;/th&gt;
 &lt;th&gt;四次挥手&lt;/th&gt;
 &lt;th&gt;客户端报错&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;pg_terminate_backend(PID)&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;[F.]&lt;/code&gt; FIN+ACK&lt;/td&gt;
 &lt;td&gt;✅ 完整&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;FATAL: terminating connection due to administrator command&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;pg_ctl stop -m fast&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;[F.]&lt;/code&gt; FIN+ACK&lt;/td&gt;
 &lt;td&gt;✅ 完整&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;FATAL: terminating connection due to administrator command&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;kill -9 postmaster&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;[F.]&lt;/code&gt; FIN+ACK&lt;/td&gt;
 &lt;td&gt;✅ 完整&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;server closed the connection unexpectedly&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;结论：kill -9 也发 FIN，不是 RST。&lt;/strong&gt; Linux TCP 内核在进程被 SIGKILL 时替进程关闭 socket，发 FIN 完成四次挥手。三种方式的客户端都收到正常的 FIN 关闭，没有任何场景发 RST。&lt;/p&gt;

&lt;h3 class="relative group"&gt;测试：怎么产生 RST 包
 &lt;div id="测试怎么产生-rst-包" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e6%b5%8b%e8%af%95%e6%80%8e%e4%b9%88%e4%ba%a7%e7%94%9f-rst-%e5%8c%85" aria-label="锚点"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;端口无监听（PG 已停库）&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;14:01:48.492004 IP 127.0.0.1.52092 &amp;gt; 127.0.0.1.ircu-2: Flags [S], seq 2570941791
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;14:01:48.492012 IP 127.0.0.1.ircu-2 &amp;gt; 127.0.0.1.52092: Flags [R.], seq 0, ack 2570941792, win 0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;客户端 SYN → 内核返回 &lt;code&gt;[R.]&lt;/code&gt; RST+ACK，&lt;code&gt;win 0&lt;/code&gt;。psql 报 &lt;code&gt;Connection refused&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;iptables REJECT &amp;ndash;reject-with tcp-reset&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;14:02:37.768515 IP 127.0.0.1.36436 &amp;gt; 127.0.0.1.ircu-2: Flags [S], seq 382980016
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;14:02:37.768522 IP 127.0.0.1.ircu-2 &amp;gt; 127.0.0.1.36436: Flags [R.], seq 0, ack 382980017, win 0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;和端口无监听完全一致：&lt;code&gt;[R.]&lt;/code&gt; RST+ACK。psql 同样报 &lt;code&gt;Connection refused&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;iptables DROP（模拟防火墙静默丢包）&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;14:00:07.050040 IP 127.0.0.1.33166 &amp;gt; 127.0.0.1.ircu-2: Flags [S], seq 985608804
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;14:00:08.095618 IP 127.0.0.1.33166 &amp;gt; 127.0.0.1.ircu-2: Flags [S], seq 985608804 ← 1s后重传
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;14:00:09.119647 IP 127.0.0.1.33166 &amp;gt; 127.0.0.1.ircu-2: Flags [S], seq 985608804 ← 2s后重传&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;服务端无任何回应，客户端 SYN 重传 3 次（1s、2s、4s 间隔）后超时。&lt;strong&gt;与 REJECT 不同，DROP 不会有 RST，客户端只能靠超时感知。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;RST 产生场景总结&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;场景&lt;/th&gt;
 &lt;th&gt;协议层&lt;/th&gt;
 &lt;th&gt;包类型&lt;/th&gt;
 &lt;th&gt;触发方&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;端口无监听&lt;/td&gt;
 &lt;td&gt;TCP 内核&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;[R.]&lt;/code&gt; RST+ACK&lt;/td&gt;
 &lt;td&gt;OS 内核&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;防火墙 REJECT&lt;/td&gt;
 &lt;td&gt;iptables&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;[R.]&lt;/code&gt; RST+ACK&lt;/td&gt;
 &lt;td&gt;防火墙&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;TCP keepalive 超时&lt;/td&gt;
 &lt;td&gt;TCP 内核&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;[R]&lt;/code&gt; RST&lt;/td&gt;
 &lt;td&gt;OS 内核（keepalive 探测失败后）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;进程终止 (kill -9)&lt;/td&gt;
 &lt;td&gt;TCP 内核&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;[F.]&lt;/code&gt; FIN+ACK（不是 RST！）&lt;/td&gt;
 &lt;td&gt;OS 内核替进程关 socket&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;防火墙 DROP&lt;/td&gt;
 &lt;td&gt;—&lt;/td&gt;
 &lt;td&gt;无&lt;/td&gt;
 &lt;td&gt;—&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;核心区分：FIN 是进程退出（内核替进程优雅关闭，哪怕是kill -9），RST 是网络不可达。&lt;/strong&gt;&lt;/p&gt;

&lt;h3 class="relative group"&gt;测试：IP下线是否会有主动断开操作
 &lt;div id="测试ip下线是否会有主动断开操作" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e6%b5%8b%e8%af%95ip%e4%b8%8b%e7%ba%bf%e6%98%af%e5%90%a6%e4%bc%9a%e6%9c%89%e4%b8%bb%e5%8a%a8%e6%96%ad%e5%bc%80%e6%93%8d%e4%bd%9c" aria-label="锚点"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;redis-cli测试，redis server端下线监听ip。&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#term1:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;r -h 30.181.15.96 -p &lt;span style="color:#ae81ff"&gt;17742&lt;/span&gt; -a 1qaz@WSX
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo tcpdump host 30.181.48.7 and port &lt;span style="color:#ae81ff"&gt;54854&lt;/span&gt; -n -vv 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#term2:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo tcpdump host 30.181.48.7 and port &lt;span style="color:#ae81ff"&gt;54854&lt;/span&gt; -n -vv &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;本次测试ip下线没有发生FIN或者RST包，只是keepalive本身发起了RST，序列如下：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th style="text-align: left"&gt;序号&lt;/th&gt;
 &lt;th style="text-align: left"&gt;时间&lt;/th&gt;
 &lt;th style="text-align: left"&gt;方向&lt;/th&gt;
 &lt;th style="text-align: left"&gt;Flags&lt;/th&gt;
 &lt;th style="text-align: left"&gt;备注&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;1&lt;/td&gt;
 &lt;td style="text-align: left"&gt;17:02:43.004897&lt;/td&gt;
 &lt;td style="text-align: left"&gt;客户端→服务端&lt;/td&gt;
 &lt;td style="text-align: left"&gt;&lt;code&gt;.&lt;/code&gt; ACK&lt;/td&gt;
 &lt;td style="text-align: left"&gt;客户端发ACK（15s间隔）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;2&lt;/td&gt;
 &lt;td style="text-align: left"&gt;17:02:43.004960&lt;/td&gt;
 &lt;td style="text-align: left"&gt;服务端→客户端&lt;/td&gt;
 &lt;td style="text-align: left"&gt;&lt;code&gt;.&lt;/code&gt; ACK&lt;/td&gt;
 &lt;td style="text-align: left"&gt;服务端回ACK&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;3&lt;/td&gt;
 &lt;td style="text-align: left"&gt;17:02:58.043896&lt;/td&gt;
 &lt;td style="text-align: left"&gt;客户端→服务端&lt;/td&gt;
 &lt;td style="text-align: left"&gt;&lt;code&gt;.&lt;/code&gt; ACK&lt;/td&gt;
 &lt;td style="text-align: left"&gt;客户端Keep-Alive（15s间隔）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;4&lt;/td&gt;
 &lt;td style="text-align: left"&gt;17:02:58.043953&lt;/td&gt;
 &lt;td style="text-align: left"&gt;服务端→客户端&lt;/td&gt;
 &lt;td style="text-align: left"&gt;&lt;code&gt;.&lt;/code&gt; ACK&lt;/td&gt;
 &lt;td style="text-align: left"&gt;服务端回ACK&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;5&lt;/td&gt;
 &lt;td style="text-align: left"&gt;17:02:58.063214&lt;/td&gt;
 &lt;td style="text-align: left"&gt;服务端→客户端&lt;/td&gt;
 &lt;td style="text-align: left"&gt;&lt;code&gt;.&lt;/code&gt; ACK&lt;/td&gt;
 &lt;td style="text-align: left"&gt;服务端重复ACK&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;6&lt;/td&gt;
 &lt;td style="text-align: left"&gt;17:02:58.063234&lt;/td&gt;
 &lt;td style="text-align: left"&gt;客户端→服务端&lt;/td&gt;
 &lt;td style="text-align: left"&gt;&lt;code&gt;.&lt;/code&gt; ACK&lt;/td&gt;
 &lt;td style="text-align: left"&gt;客户端回ACK&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;7&lt;/td&gt;
 &lt;td style="text-align: left"&gt;17:03:13.051905&lt;/td&gt;
 &lt;td style="text-align: left"&gt;客户端→服务端&lt;/td&gt;
 &lt;td style="text-align: left"&gt;&lt;code&gt;.&lt;/code&gt; ACK&lt;/td&gt;
 &lt;td style="text-align: left"&gt;客户端Keep-Alive（15s间隔）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;8&lt;/td&gt;
 &lt;td style="text-align: left"&gt;17:03:18.059901&lt;/td&gt;
 &lt;td style="text-align: left"&gt;客户端→服务端&lt;/td&gt;
 &lt;td style="text-align: left"&gt;&lt;code&gt;.&lt;/code&gt; ACK&lt;/td&gt;
 &lt;td style="text-align: left"&gt;客户端Keep-Alive（5s间隔）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;9&lt;/td&gt;
 &lt;td style="text-align: left"&gt;17:03:23.067901&lt;/td&gt;
 &lt;td style="text-align: left"&gt;客户端→服务端&lt;/td&gt;
 &lt;td style="text-align: left"&gt;&lt;code&gt;.&lt;/code&gt; ACK&lt;/td&gt;
 &lt;td style="text-align: left"&gt;客户端Keep-Alive（5s间隔）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;10&lt;/td&gt;
 &lt;td style="text-align: left"&gt;17:03:28.075899&lt;/td&gt;
 &lt;td style="text-align: left"&gt;客户端→服务端&lt;/td&gt;
 &lt;td style="text-align: left"&gt;[R.] RST+ACK&lt;/td&gt;
 &lt;td style="text-align: left"&gt;客户端主动断开（5s间隔）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;redis-cli没有keepalive的配置，但&lt;a href="https://raw.githubusercontent.com/redis/redis/refs/heads/4.0/src/redis-cli.c" target="_blank" rel="noreferrer"&gt;redis-cli源码&lt;/a&gt;中写死：&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#define REDIS_CLI_KEEPALIVE_INTERVAL 15 &lt;/span&gt;&lt;span style="color:#75715e"&gt;/* seconds */&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;redis-cli的keepalive是代码中写死的15s一次，所以能看到15秒一次的keepalive包。&lt;/p&gt;
&lt;p&gt;在抓包期间有服务端IP下线操作但没有收到任何断连信息，最后由客户端Keepalive探测出socket异常，客户端主动RST。&lt;/p&gt;
&lt;p&gt;（redis server段同样可以发起keepalive，但是这次没有触发）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;测试结论：直接下线IP，内核可能不会有任何FIN/RST动作。&lt;/strong&gt;&lt;/p&gt;

&lt;h3 class="relative group"&gt;测试：正常数据通信是否干扰tcp_keepalive周期？
 &lt;div id="测试正常数据通信是否干扰tcp_keepalive周期" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e6%b5%8b%e8%af%95%e6%ad%a3%e5%b8%b8%e6%95%b0%e6%8d%ae%e9%80%9a%e4%bf%a1%e6%98%af%e5%90%a6%e5%b9%b2%e6%89%b0tcp_keepalive%e5%91%a8%e6%9c%9f" aria-label="锚点"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;结论：会。数据通信不仅有PSH包发送到对端，也含有ACK包。&lt;/p&gt;
&lt;p&gt;以下用redis-cli测试，redis-cli的keepalive=15s，redis-server的keepalive=2h：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;客户端触发&lt;/th&gt;
 &lt;th&gt;tcp时间戳&lt;/th&gt;
 &lt;th&gt;客户端发&lt;/th&gt;
 &lt;th&gt;服务端发&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;tcp_keepalive&lt;/td&gt;
 &lt;td&gt;17:16:05.558570-17:16:15.048701&lt;/td&gt;
 &lt;td&gt;ACK&lt;/td&gt;
 &lt;td&gt;ACK&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;PING&lt;/td&gt;
 &lt;td&gt;17:16:15.048312-17:16:15.048701&lt;/td&gt;
 &lt;td&gt;PSH&lt;/td&gt;
 &lt;td&gt;PSH&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;tcp_keepalive&lt;/td&gt;
 &lt;td&gt;17:16:15.048433-17:16:30.071278&lt;/td&gt;
 &lt;td&gt;ACK&lt;/td&gt;
 &lt;td&gt;ACK&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;tcp_keepalive&lt;/td&gt;
 &lt;td&gt;17:16:30.070906-17:16:30.071278&lt;/td&gt;
 &lt;td&gt;ACK&lt;/td&gt;
 &lt;td&gt;ACK&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 class="relative group"&gt;测试：idle_in_transaction 和长时间 SQL 会发 keepalive 吗？
 &lt;div id="测试idle_in_transaction-和长时间-sql-会发-keepalive-吗" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e6%b5%8b%e8%af%95idle_in_transaction-%e5%92%8c%e9%95%bf%e6%97%b6%e9%97%b4-sql-%e4%bc%9a%e5%8f%91-keepalive-%e5%90%97" aria-label="锚点"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;测试环境：Rocky 10.1 + PG 18.2，客户端 libpq 设置 &lt;code&gt;keepalives_idle=5 keepalives_interval=3&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;idle_in_transaction：&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;16:32:11.611 最后一条数据 ACK
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;16:32:16.927 客户端 → 服务端 [.] ACK ← 5.3s 后，第一次 keepalive 探测
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;16:32:16.927 服务端 → 客户端 [.] ACK
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;16:32:21.983 客户端 → 服务端 [.] ACK ← 5s 后，第二次探测
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;16:32:21.983 服务端 → 客户端 [.] ACK
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;16:32:27.039 客户端 → 服务端 [.] ACK ← 5s 后，第三次探测
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;16:32:27.039 服务端 → 客户端 [.] ACK&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;结论：idle_in_transaction &lt;strong&gt;会发 keepalive&lt;/strong&gt;。每 5 秒一对探测+回应，除此之外没有任何其他 TCP 包。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;长时间 SQL（服务端 &lt;code&gt;tcp_keepalives_idle=10&lt;/code&gt;）：&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;16:32:43.148 最后一条 ACK（客户端发出 SELECT pg_sleep(30) 后）
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ← 中间 10 秒零 TCP 包 ← SQL 在跑，但无数据回传
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;16:32:53.279 服务端 → 客户端 [.] ACK ← 10.1s 后，服务端发 keepalive 探测
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;16:32:53.279 客户端 → 服务端 [.] ACK&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;结论：&lt;strong&gt;SQL 在跑 ≠ TCP 有包。&lt;/strong&gt; &lt;code&gt;pg_sleep(30)&lt;/code&gt; 期间无任何 TCP 通信，keepalive 照样触发——它只看 TCP 层有没有数据交换，不看数据库在干什么。&lt;/p&gt;
&lt;p&gt;如果一个报表查询跑了 5 分钟且中间不返回结果，对于防火墙/NAT/负载均衡来说，这个 TCP 连接就是 5 分钟的死连接——不配 keepalive 就会被掐断。&lt;/p&gt;

&lt;h2 class="relative group"&gt;连接探活
 &lt;div id="连接探活" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e8%bf%9e%e6%8e%a5%e6%8e%a2%e6%b4%bb" aria-label="锚点"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;客户端的死连接问题只能由客户端解决——服务端已经访问不到了，不可能指望它来通知你。&lt;/p&gt;
&lt;p&gt;连接池的两个关键概念：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;socket.close()&lt;/code&gt; ≠ 连接池 &lt;code&gt;close()&lt;/code&gt;&lt;/strong&gt;：前者是 TCP 四次挥手彻底断开，后者是把连接归还给连接池，连接保持 ESTABLISHED，状态变为 idle&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;探活的目标&lt;/strong&gt;：及时发现那些 socket 已断、但连接池还以为是活着的&amp;quot;僵尸连接&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;两个常见 socket 错误状态：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ESTABLISHED 但实际不可用&lt;/strong&gt;：连接池未感知到 socket 已失效，应用层操作时才报错&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TIME_WAIT&lt;/strong&gt;：感知到 socket 不可用但未及时释放，大量 TIME_WAIT 会耗尽端口&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;总体来说探活机制按网络层分为三种：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;类型&lt;/th&gt;
 &lt;th&gt;动作&lt;/th&gt;
 &lt;th&gt;触发方式&lt;/th&gt;
 &lt;th&gt;发送内容&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;4 层探活&lt;/td&gt;
 &lt;td&gt;内核层 TCP 包&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;tcp_keepalive&lt;/code&gt; 系列参数；连接池自身的 keepalive&lt;/td&gt;
 &lt;td&gt;ACK 包（空包探测对端是否存活）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;7 层探活&lt;/td&gt;
 &lt;td&gt;应用层数据库命令&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;testOnBorrow&lt;/code&gt; / &lt;code&gt;testOnReturn&lt;/code&gt; / &lt;code&gt;testWhileIdle&lt;/code&gt; / &lt;code&gt;PING&lt;/code&gt;/配置test-query&lt;/td&gt;
 &lt;td&gt;视驱动而定，如 &lt;code&gt;SELECT 1&lt;/code&gt;、&lt;code&gt;PING&lt;/code&gt;；&lt;code&gt;SELECT NOT pg_is_in_recovery()&lt;/code&gt; / &lt;code&gt;SELECT @@READ_ONLY&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 class="relative group"&gt;4 层探活
 &lt;div id="4-层探活" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#4-%e5%b1%82%e6%8e%a2%e6%b4%bb" aria-label="锚点"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;Linux 的 &lt;code&gt;tcp_keepalive&lt;/code&gt; 是 4 层探活的基础：&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;net.ipv4.tcp_keepalive_time &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;7200&lt;/span&gt; &lt;span style="color:#75715e"&gt;# 空闲 2 小时后才开始探测&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;net.ipv4.tcp_keepalive_intvl &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;75&lt;/span&gt; &lt;span style="color:#75715e"&gt;# 探测间隔 75 秒&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;net.ipv4.tcp_keepalive_probes &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;9&lt;/span&gt; &lt;span style="color:#75715e"&gt;# 探测 9 次失败后断开&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;默认值的问题：7200 秒（2 小时）才开始探测，中间防火墙早就把连接掐了，探测毫无意义。生产环境通常需要调小到分钟级。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如果链路中有代理（Nginx、HAProxy 等），TCP keepalive 只到代理，不到后端数据库。&lt;/strong&gt; 代理到数据库那一段需要代理自己配 keepalive，否则代理挂了连接池感知不到。&lt;/p&gt;
&lt;p&gt;实际通信中，有数据交互时，PSH/ACK 包本身就充当了&amp;quot;保活&amp;quot;的角色。keepalive 只在连接完全空闲时才触发——如果有持续的数据收发，keepalive 计时器会被重置，不会发送 ACK 探测包。&lt;/p&gt;

&lt;h3 class="relative group"&gt;7 层探活
 &lt;div id="7-层探活" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#7-%e5%b1%82%e6%8e%a2%e6%b4%bb" aria-label="锚点"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;7 层探活是应用主动发数据库命令验证连接。各连接池的代表参数（不全面）：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;连接池&lt;/th&gt;
 &lt;th&gt;参数&lt;/th&gt;
 &lt;th&gt;说明&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;JDBC 通用&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;testOnBorrow&lt;/code&gt;、&lt;code&gt;testOnReturn&lt;/code&gt;、&lt;code&gt;testWhileIdle&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;借出/归还/空闲时验证&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;HikariCP&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;connectionTestQuery&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;验证 SQL，常用 &lt;code&gt;SELECT 1&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Jedis&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;testOnBorrow&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;借出时验证&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Lettuce&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;pingBeforeActivateConnection&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;激活前 PING&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Redisson&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;pingConnectionInterval&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;定时 PING 间隔&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Apache Commons Pool2&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;testOnBorrow&lt;/code&gt; 等&lt;/td&gt;
 &lt;td&gt;通用对象池验证&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;close()&lt;/code&gt; 和 &lt;code&gt;returnObject()&lt;/code&gt; 都是将连接归还给连接池，不是真正关闭 TCP。归还后连接处于 idle 状态，socket 仍然 ESTABLISHED。Apache Commons Pool2 通过标准化的对象池管理机制来维护这些连接。&lt;/p&gt;
&lt;p&gt;关于 testOnBorrow 的性能影响： 每次借连接都发 &lt;code&gt;SELECT 1&lt;/code&gt;，高并发下有额外开销。通常用 &lt;code&gt;testWhileIdle&lt;/code&gt; + 合理检测间隔来平衡。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4 层 vs 7 层的选择：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;4 层：直连数据库，链路中无代理，TCP keepalive 配小即可&lt;/li&gt;
&lt;li&gt;7 层：链路中有代理、需要确认数据库真的能执行 SQL（不只是 TCP 通），且可确保整个链路是打通的。&lt;/li&gt;
&lt;li&gt;7层+角色：需要主从区分时，不能执行简单sql比如select 1来识别数据库的角色，此时需要配置自定义SQL。比如redis PING不能得知从库状态&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 class="relative group"&gt;单域名 vs 双域名
 &lt;div id="单域名-vs-双域名" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e5%8d%95%e5%9f%9f%e5%90%8d-vs-%e5%8f%8c%e5%9f%9f%e5%90%8d" aria-label="锚点"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;驱动配置主从地址（JDBC 的 &lt;code&gt;read-write&lt;/code&gt; + &lt;code&gt;read-only&lt;/code&gt; 或 Lettuce 的 Master/Replica）时，可以自动识别主从并路由。&lt;/p&gt;
&lt;p&gt;单域名的问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;无法识别主从切换&lt;/li&gt;
&lt;li&gt;被 JVM/OS 的 DNS 缓存限制（&lt;code&gt;networkaddress.cache.ttl&lt;/code&gt;），主从切换后可能长时间连旧 IP&lt;/li&gt;
&lt;li&gt;7 层探活配 &lt;code&gt;SELECT NOT pg_is_in_recovery()&lt;/code&gt; 可以检测到主从变化，但不如双域名灵活&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;总结
 &lt;div id="总结" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e6%80%bb%e7%bb%93" aria-label="锚点"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;FIN 和 RST 的发生场景：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;FIN 是进程退出时内核代发的（包括 kill -9），走四次挥手优雅关闭&lt;/li&gt;
&lt;li&gt;RST 是网络不可达时产生的：端口无监听、keepalive 超时、防火墙 REJECT等&lt;/li&gt;
&lt;li&gt;IP 直接下线不会有任何 FIN/RST，只能靠 keepalive 探测出来&lt;/li&gt;
&lt;li&gt;防火墙 DROP 静默丢包，没有 RST，客户端只能超时&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4 层和 7 层的探活机制：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;4 层（TCP keepalive）：默认 2h 才探测，生产环境必须调小。只到代理，不到后端&lt;/li&gt;
&lt;li&gt;7 层（应用层 PING/SQL）：能确认数据库真的能执行命令，但高并发下有性能开销&lt;/li&gt;
&lt;li&gt;有代理 / 需要主从区分 → 必须 7 层&lt;/li&gt;
&lt;li&gt;直连数据库 → 4 层配小即可&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;idle_in_transaction 和长时间 SQL 的 keepalive 行为：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;两者都会发 keepalive——触发条件是 TCP 层无数据交换，不是数据库状态&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SQL 在跑 ≠ TCP 有包&lt;/strong&gt;：长时间报表查询如果不返回中间结果，TCP 层等同于死连接&lt;/li&gt;
&lt;li&gt;不配 keepalive 的话，防火墙可能在 SQL 还在跑的时候就掐断连接&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;一些注意事项：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;socket.close()&lt;/code&gt; ≠ 连接池归还：前者断开 TCP，后者只是把连接标记为 idle&lt;/li&gt;
&lt;li&gt;连接池探活的目标就是发现那些 socket 已断但连接池还以为活着的僵尸连接&lt;/li&gt;
&lt;li&gt;&lt;code&gt;testOnBorrow&lt;/code&gt; 每次借连接都查库，高并发有开销；&lt;code&gt;testWhileIdle&lt;/code&gt; + 合理间隔更实用&lt;/li&gt;
&lt;li&gt;链路中有代理时，每段都需要独立配置 keepalive，一段断了另一端感知不到&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 class="relative group"&gt;ref
 &lt;div id="ref" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#ref" aria-label="锚点"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://raw.githubusercontent.com/redis/redis/refs/heads/4.0/src/redis-cli.c" target="_blank" rel="noreferrer"&gt;https://raw.githubusercontent.com/redis/redis/refs/heads/4.0/src/redis-cli.c&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://redisson.pro/docs/configuration/" target="_blank" rel="noreferrer"&gt;https://redisson.pro/docs/configuration/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.paic.com.cn/#/post/57844638" target="_blank" rel="noreferrer"&gt;https://docs.paic.com.cn/#/post/57844638&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://support.huaweicloud.com/intl/en-us/dcs_faq/dcs-faq-211230001.html" target="_blank" rel="noreferrer"&gt;https://support.huaweicloud.com/intl/en-us/dcs_faq/dcs-faq-211230001.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://howtodoinjava.com/spring-data/spring-boot-redis-with-lettuce-jedis/" target="_blank" rel="noreferrer"&gt;https://howtodoinjava.com/spring-data/spring-boot-redis-with-lettuce-jedis/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/redis/lettuce/wiki/Connection-Pooling" target="_blank" rel="noreferrer"&gt;https://github.com/redis/lettuce/wiki/Connection-Pooling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://redis.github.io/lettuce/advanced-usage/client-options/" target="_blank" rel="noreferrer"&gt;https://redis.github.io/lettuce/advanced-usage/client-options/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://redis.github.io/lettuce/advanced-usage/connection-pooling/" target="_blank" rel="noreferrer"&gt;https://redis.github.io/lettuce/advanced-usage/connection-pooling/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.csdn.net/u014495560/article/details/103576786" target="_blank" rel="noreferrer"&gt;https://blog.csdn.net/u014495560/article/details/103576786&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.man7.org/linux/man-pages/man7/socket.7.html" target="_blank" rel="noreferrer"&gt;https://www.man7.org/linux/man-pages/man7/socket.7.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.man7.org/linux/man-pages/man7/tcp.7.html" target="_blank" rel="noreferrer"&gt;https://www.man7.org/linux/man-pages/man7/tcp.7.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/18/runtime-config-connection.html" target="_blank" rel="noreferrer"&gt;https://www.postgresql.org/docs/18/runtime-config-connection.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/18/libpq-connect.html" target="_blank" rel="noreferrer"&gt;https://www.postgresql.org/docs/18/libpq-connect.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://linuxvox.com/blog/what-is-the-reason-and-how-to-avoid-the-fin-ack-rst-and-rst-ack/" target="_blank" rel="noreferrer"&gt;https://linuxvox.com/blog/what-is-the-reason-and-how-to-avoid-the-fin-ack-rst-and-rst-ack/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.oracle.com/cd/E13189_01/kodo/docs324/ref_guide_dbsetup.html" target="_blank" rel="noreferrer"&gt;https://docs.oracle.com/cd/E13189_01/kodo/docs324/ref_guide_dbsetup.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>