想象你经营一家 AI 流水席饭店:大模型是后厨,用户是坐在桌边的客人。一次提问不是端上一道菜就结束,而是从一条长长的传送带上,持续不断地把汤、主菜、甜点一份份送到客人面前——这就是流式输出。WebSocket 是为每桌客人架设的“双向传菜电梯”,客人可以随时催菜、加菜;SSE 则是一扇“只出不进”的单向出菜窗口。长连接测试,就是检验这些传送带在几百上千桌同时开饭时,会不会卡住、断掉、堆盘,甚至把后厨的仓库撑爆。 你要关心的事很具体:传送带怎么架起来?多久没人端菜就要撤走?后厨出菜太快、客人来不及吃怎么办?不同传送带会占多少仓库空间?怎么调参数让整条流水线稳如老狗?这篇文章就用这套“传送带”思维,把 WebSocket/SSE 长连接压测的核心方法讲清楚。

岗位关联

对 AI 从业者而言,掌握长连接测试意味着你能在模型上线前发现流式服务的连接瓶颈、内存泄漏和背压风险,真正把“能跑”变成“能扛住生产流量”。

目录
  1. 可视化演示
  2. 连接建立管理
  3. 超时与心跳
  4. 流式背压处理
  5. 内存消耗评估
  6. 参数调优实践
  7. 压测方法验证
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示WebSocket全双工与SSE单向流协议差异、Ping/Pong心跳保活机制,以及流式输出场景的背压与缓冲处理。

本节配有交互动效(进入视口后自动加载,约 3KB)。

WebSocket全双工通信 客户端与服务端双向随时发送 full-duplex SSE为单向流式输出,仅服务端推送。

连接建立管理

传送带不是一开机就凭空出现的,它需要“按下启动按钮、从出菜口拉到客人桌边”。WebSocket 的连接建立就像先拨通电话,再在通话中升级成专线;SSE 则简单一些,客人只需要打开一扇“单向出菜窗”,后厨就能把菜源源不断送出来。这段从无到有的过程,决定了第一道菜的等待时间,也决定了系统能不能快速接纳大量新客人。

技术上看,一条长连接会经历 Connecting → Open → Closing → Closed 的生命周期。压测时不能只看“连上了没有”,还要统计握手耗时、升级成功率、被动关闭比例。服务端管理连接的方式也分两种:传统线程模型是一个服务员盯一条传送带,连接一多就累垮;而 epoll 事件驱动模型是中央控制台同时盯着成千上万条传送带,只有真正有菜进出时才处理。长连接压测要验证的就是:中央控制台到底能扛住多少条“同时在线”的传送带。

规划连接数时,我们不能凭感觉拍脑袋,而要参考到达率和连接时长:

搭建压测脚本后,要记录“已建立连接数 / 尝试连接数”,如果成功率低于 99.9%,说明大门开启的速度跟不上客流,需要排查握手超时或服务端 accept 队列溢出。传送带架设得快,宴会才能按时开席。

C=λ×T\displaystyle C = \lambda \times T
并发连接数 C 等于每秒新建连接数 λ 乘以平均连接时长 T。就像传送带总量取决于“每秒新拉几条带子”和“每条带子用多久”。
  1. λ(连接到达率)每秒由客户端发起的新连接数量,可以从业务统计或历史日志中得到。
    λ=NtotalTperiod\displaystyle \lambda = \frac{N_{\text{total}}}{T_{\text{period}}}
  2. T(平均连接时长)所有连接从建立到关闭的平均存活秒数,体现业务会话的长短。
    T=∑connDconnNtotal\displaystyle T = \frac{\sum_{\text{conn}} D_{\text{conn}}}{N_{\text{total}}}
  3. C(并发连接数)用到达率乘以连接时长,就得到稳定状态下同时挂着的传送带数量,是容量规划的基础。
    C=λ×T\displaystyle C = \lambda \times T

超时与心跳

一条传送带如果长时间没有菜经过,你分不清它是“暂时没单”还是“卡在某个角落没人管”。为了避免死连接越积越多,我们需要安排巡线机器人:每隔一段时间敲一下传送带,看看有没有回应。这个“敲一下”就是心跳 PING/PONG;如果敲了多次都没回音,机器人就会把这条传送带撤掉,回收占用的人力与空间。

在实际系统中,空闲超时和心跳必须成对配置。如果空闲超时设得太短,活连接会被误杀;如果心跳太频繁,又会浪费带宽和 CPU。业界常见的经验是让心跳间隔小于空闲超时的一半,给网络往返留足余量:

压测时还要专门验证断线重连:把服务端或负载均衡器的空闲超时调小,观察客户端是否能在规定时间内重新建立传送带,并且不丢状态。真实 AI 流式服务中,长连接常跨多个网关节点,每一层都有自己的超时,任何一层“悄悄拆带子”,用户都会看到“输出中断”。所以超时与心跳的配置,必须放在全链路里一起测。

Thb≤Ttimeout2\displaystyle T_{\text{hb}} \le \frac{T_{\text{timeout}}}{2}
心跳间隔不能超过空闲超时的一半。这样即使某次心跳在网络中迷路,服务器也有足够时间再等下一次心跳,而不是立刻把好端端的连接砍掉。
  1. T_timeout(空闲超时)连接在没有任何业务消息时,服务端允许它保留的最长秒数。
    Ttimeout=max idle seconds\displaystyle T_{\text{timeout}} = \text{max idle seconds}
  2. T_hb(心跳间隔)每隔这么多秒发送一次 PING 帧,用来探活并续期。
    Thb=heartbeat interval\displaystyle T_{\text{hb}} = \text{heartbeat interval}
  3. 不等式约束心跳间隔小于超时时间的一半,保证在超时回收前至少有一次心跳能完成“敲一下-回一声”的往返。
    Thb≤Ttimeout2\displaystyle T_{\text{hb}} \le \frac{T_{\text{timeout}}}{2}

流式背压处理

后厨出菜速度再快,也要看客人能不能吃得过来。如果传送带不停地上菜,客人来不及端走,出锅口的托盘就会越堆越高,最终把桌面和地面全部占满。这种“供大于求”导致的拥堵,在流式系统里就是背压(Backpressure)。AI 流式场景尤其常见:模型生成 token 的速度远高于前端 UI 渲染或调用方消费的速度。

处理背压最优雅的方式不是硬扛,而是让上游感知下游的速度。实现上常用滑动窗口:客户端告诉服务端“我最多能同时接收 N 条未确认消息”,服务端发满 N 条后就必须等待 ACK,窗口滑动了才能继续发。这样队列长度就不会无限膨胀。背压的严重程度可以用一个非常经典的公式来量化:

压测时,我们要故意制造“慢消费者”—让客户端每隔几十毫秒才读一条消息,观察服务端发送队列是否上涨、内存是否飙升、是否会丢弃最旧消息。合理的背压策略是在队列满时让生产者降速,或丢弃过期消息并记录告警,而不是无限扩大缓冲区。传送带不是仓库,能即时流动才是健康的。

L=λ×W\displaystyle L = \lambda \times W
平均队列长度 L 等于消息到达率 λ 乘以平均等待时间 W。传送带上堆着的菜越多,说明要么上菜太快,要么客人吃得太慢,背压已经形成。
  1. λ(消息到达率)单位时间内服务端生成并投递给客户端的消息数量。
    λ=messagessecond\displaystyle \lambda = \frac{\text{messages}}{\text{second}}
  2. W(平均等待时间)一条消息从进入发送队列到被客户端取走的平均秒数。
    W=∑Dwaitmessages\displaystyle W = \frac{\sum D_{\text{wait}}}{\text{messages}}
  3. L(队列长度)λ 乘 W 得到平均在途/排队消息数,是背压的直接指标;L 持续上涨就说明需要限流。
    L=λ×W\displaystyle L = \lambda \times W

内存消耗评估

每条传送带旁边都会摆几个中转台,用来临时放菜。中转台越多、堆的菜越多,占用的仓库空间就越大。长连接服务的内存消耗,本质上就是“每连接固定开销 + 每连接缓冲/队列开销 + 全局共享开销”的总和。压测时如果只盯着整体内存,很容易漏掉某一条连接悄悄吃掉的隐形空间。

评估内存时必须逐层拆解:内核层面的 Socket 收发缓冲区、用户态的编解码器与连接对象、等待发送的业务消息队列、以及事件循环和连接池等共享结构。当背压出现时,消息队列长度 L 会增大,内存也会随之线性上涨。由此我们可以做容量估算:

在长时间压测中,内存曲线如果是“锯齿形”——涨一点又因 GC 回落一点,属于正常;如果整体斜率持续向上且不回落,就要怀疑连接对象泄漏或队列积压无法释放。仓库面积是有限的,压测的最终目标就是算清楚:最多能同时开多少条传送带,才不会把仓库挤爆。

Mtotal=C×(mfix+Bbuf+L×smsg)+Mshared\displaystyle M_{\text{total}} = C \times (m_{\text{fix}} + B_{\text{buf}} + L \times s_{\text{msg}}) + M_{\text{shared}}
总内存约等于连接数 C 乘以“每连接固定开销 m_fix、收发缓冲 B_buf、排队消息数 L × 单条消息大小 s_msg”之和,再加上共享内存 M_shared。
  1. C(连接数)当前同时在线长连接的数量,来自第一小节的容量规划。
    C=λ×T\displaystyle C = \lambda \times T
  2. m_fix(固定开销)每个连接对象、Socket 通道、事件注册等不随消息增长的内存。
    mfix=per connection overhead\displaystyle m_{\text{fix}} = \text{per connection overhead}
  3. B_buf(读写缓冲)内核与用户态为每条连接预留的收发缓冲区大小。
    Bbuf=send buffer+recv buffer\displaystyle B_{\text{buf}} = \text{send buffer} + \text{recv buffer}
  4. L × s_msg(排队消息内存)在途消息数量乘以单条消息大小,是背压造成内存上涨的部分。
    L×smsg=queued message memory\displaystyle L \times s_{\text{msg}} = \text{queued message memory}
  5. M_shared(共享内存)连接管理器、定时器、监控等所有连接共享的全局结构。
    Mshared=global structures\displaystyle M_{\text{shared}} = \text{global structures}

参数调优实践

光会测还不够,还得会调。就像传送带运行一段时间后,我们要调整它的转速、中转台大小和巡检频率。长连接服务的性能调优,核心是对超时、心跳、缓冲区和连接池做组合配置。调优的目标不是某个参数最大,而是“资源占用和稳定性”之间的平衡。

这里分享几个真实场景里的调优方向:空闲超时不要设太大,否则死连接会像没人管的传送带一样占着位置;心跳间隔按超时的一半来配;读写缓冲不要盲目调大,OS 层 SO_SNDBUF/SO_RCVBUF 翻倍会立刻体现在每连接内存上;连接池则要根据连接建立速率和握手耗时来估算。连接池大小可参考下面的经验公式:

还要强调资源隔离:不要让长连接和普通短请求共用同一个线程池或连接池。AI 流式推理的一次请求可能持续几十秒,如果它把线程池占满,普通健康检查接口也会跟着超时。给长连接单独划分线程、队列和连接池,就像给传送带开设独立通道,避免“一条带子卡死,整条流水线瘫痪”。

Npool=⌈Q×Tsetup1000⌉+1\displaystyle N_{\text{pool}} = \left\lceil \frac{Q \times T_{\text{setup}}}{1000} \right\rceil + 1
长连接池的最小规模约等于“每秒重建连接数 Q × 单次建连耗时 T_setup 毫秒”除以 1000 后向上取整,再留 1 条余量。
  1. Q(每秒建连数)峰值时可容忍的连接重建速率,例如断线重连风暴时的 QPS。
    Q=connections per second\displaystyle Q = \text{connections per second}
  2. T_setup(单次建连耗时)从发起握手到连接可用的平均毫秒数,包含 TCP 握手和协议升级。
    Tsetup=handshake time in ms\displaystyle T_{\text{setup}} = \text{handshake time in ms}
  3. Q × T_setup / 1000把毫秒换算成秒,得到在途建连并发量。
    Q×Tsetup1000=in-flight handshakes\displaystyle \frac{Q \times T_{\text{setup}}}{1000} = \text{in-flight handshakes}
  4. 向上取整并加 1保证连接池在峰值波动时仍有富余能力。
    Npool=⌈Q×Tsetup1000⌉+1\displaystyle N_{\text{pool}} = \left\lceil \frac{Q \times T_{\text{setup}}}{1000} \right\rceil + 1

压测方法验证

最后一步是“全链路演习”:先让少量传送带跑起来,再逐步加压,接着持续运行数小时,看传送带会不会在长时间运转中慢慢卡死。压测方法的核心不是把并发数调到最大,而是用科学的场景设计暴露问题。常用场景包括:逐步加压找出连接数拐点、峰值冲击测试极限、以及长时间稳定测试验证无泄漏。

压测过程中需要监控的指标也要遵循长连接特点:连接建立成功率与耗时、PING/PONG 往返时延、消息推送延迟、断线重连次数、发送队列长度、GC 频率和堆内存曲线。稳定性是否合格,可以用错误率来量化:

真正的大规模验证,要在接近生产环境的网络上进行:模拟真实客户端数量、真实消息大小、真实心跳周期。比如某 AI 客服系统上线前,用 10 万条 WebSocket 连接连续压测 24 小时,把发现的内存锯齿和队列堆积问题提前修复。传送带只有在真实客流中连续转一天不断线,才配得上“稳定”二字。

Erate=Ntimeout+Nreset+NrejectNtotal×100%<0.1%\displaystyle E_{\text{rate}} = \frac{N_{\text{timeout}} + N_{\text{reset}} + N_{\text{reject}}}{N_{\text{total}}} \times 100\% < 0.1\%
长连接压测的错误率应低于 0.1%。这里的错误包括心跳超时、连接被重置和服务端拒绝建连,任何一个都不该被忽略。
  1. N_timeout(超时错误)空闲超时或心跳超时导致的连接中断次数。
    Ntimeout=timeout errors\displaystyle N_{\text{timeout}} = \text{timeout errors}
  2. N_reset(重置错误)对端 RST 导致的连接被强制断开次数。
    Nreset=reset errors\displaystyle N_{\text{reset}} = \text{reset errors}
  3. N_reject(拒绝错误)服务端因连接数满、限流等原因拒绝握手的次数。
    Nreject=reject errors\displaystyle N_{\text{reject}} = \text{reject errors}
  4. N_total(总请求数)压测期间所有尝试建立的长连接总数。
    Ntotal=total connection attempts\displaystyle N_{\text{total}} = \text{total connection attempts}
  5. 百分比判据错误总数除以总连接数,必须小于 0.1% 才具备生产上线条件。
    Erate<0.1%\displaystyle E_{\text{rate}} < 0.1\%

AI 应用场景

大模型流式对话服务压测

阶段:模型推理服务上线前

解决的问题:LLM 对话服务通过 SSE 把 token 流式推送给前端,传统短连接压测只看单次响应时间,无法发现“连接长时间挂起、心跳超时、队列积压”等问题。长连接测试能模拟真实用户持续监听流式输出的行为,提前暴露网关超时配置不当和内存泄漏。

具体案例:某智能助手平台在 GPT 类模型上线前,用 5000 条 SSE 连接同时模拟用户提问。压测发现当模型思考时间超过 30 秒时,网关会主动断开空闲连接,导致前端反复重连。通过把心跳间隔设为 15 秒、空闲超时改为 90 秒,问题消失。

实时 Agent 任务流推送网关

阶段:Agent 架构运行监控

解决的问题:Agent 在规划、调用工具、生成结果时会持续向客户端推送状态,WebSocket 承担大量双向消息。压测需要验证高并发下是否存在背压:当客户端消费速度跟不上 Agent 事件流时,消息队列是否堆积、内存是否飙升、是否触发丢弃策略。

具体案例:某 AI Agent 平台用 WebSocket 推送“正在思考、正在调用工具”等中间状态。压测脚本故意把客户端消费间隔拉到 100ms,结果发现服务端发送队列从 100 涨到 5 万条,内存直接翻倍。随后为事件流增加滑动窗口和丢弃过旧事件策略,内存恢复平稳。

多租户实时推送服务容量规划

阶段:容量规划与成本控制

解决的问题:云上 AI 基础设施需要为多个租户提供实时推送能力,长连接数量直接影响 CPU、内存和带宽成本。通过连接数规划公式和内存评估模型,可以在采购资源前算清“单机最多支撑多少连接”。

具体案例:某云服务商为 20 个租户规划 WebSocket 推送集群,先用 λ=200 每秒新建连接、T=30 分钟平均时长算出并发连接约 36 万,再按每连接 50KB 内存估算需要约 18GB。据此提前扩容并设置单机连接上限,上线后没有出现 OOM。

Python 演示

python68 行
import asyncioimport time  class LongConnectionProbe:    """A small simulator for long-lived streaming connection load tests."""     def __init__(self, total, concurrent, heartbeat=5.0, idle_timeout=10.0):        self.total = total        self.concurrent = concurrent        self.heartbeat = heartbeat        self.idle_timeout = idle_timeout        self.done_count = 0        self.error_count = 0        self.backpressure_count = 0        self.max_queue_len = 0        self.started_at = time.time()     async def simulate_connection(self, cid):        q = asyncio.Queue(maxsize=4)         async def producer():            for seq in range(6):                if q.full():                    self.backpressure_count += 1                await q.put((cid, seq))                if seq == 0:                    await asyncio.sleep(self.heartbeat / 2)                else:                    await asyncio.sleep(0.02)         async def consumer():            while True:                try:                    _, seq = await asyncio.wait_for(q.get(), timeout=self.idle_timeout)                    self.max_queue_len = max(self.max_queue_len, q.qsize())                except asyncio.TimeoutError:                    self.error_count += 1                    break                if seq == 5:                    break                await asyncio.sleep(0.06)         await asyncio.gather(producer(), consumer())        self.done_count += 1     async def run(self):        sem = asyncio.Semaphore(self.concurrent)         async def guarded(cid):            async with sem:                await self.simulate_connection(cid)         tasks = [asyncio.create_task(guarded(i)) for i in range(self.total)]        await asyncio.gather(*tasks)     def report(self):        print("total connections:", self.done_count)        print("idle timeout errors:", self.error_count)        print("backpressure events:", self.backpressure_count)        print("max queue length:", self.max_queue_len)        print("elapsed seconds:", round(time.time() - self.started_at, 2))  if __name__ == "__main__":    probe = LongConnectionProbe(total=20, concurrent=5)    asyncio.run(probe.run())    probe.report()
目的:用 asyncio 模拟 20 条长连接上“快速生产 + 慢速消费”的流式消息,统计背压事件和队列最大长度,验证长连接系统的稳定性表现。
  1. q = asyncio.Queue(maxsize=4)用容量为 4 的队列模拟服务端发送缓冲,队列满就产生背压。
  2. if q.full(): self.backpressure_count += 1生产者发现队列已满时记录背压事件,相当于“传送带托盘堆满”。
  3. await q.put((cid, seq))模拟服务端向长连接中写入一条业务消息。
  4. await asyncio.wait_for(q.get(), timeout=self.idle_timeout)模拟客户端等待消息,同时检验空闲超时。
  5. await asyncio.sleep(0.06)人为让消费变慢,制造供大于求的背压场景。
  6. asyncio.Semaphore(self.concurrent)限制同时存在的连接探测数,模拟服务端的并发上限。
  7. tasks = [asyncio.create_task(guarded(i)) for i in range(self.total)]并发创建 20 条长连接,模拟压测流量。
  8. self.max_queue_len = max(self.max_queue_len, q.qsize())记录队列最大深度,作为内存压力的关键指标。

常见误区

误区反正都是长连接,SSE 压测脚本直接照抄 WebSocket 的脚本,消息来回发,结果也一样。

正解WebSocket 是全双工,客户端可以随时上行数据;SSE 是单工,只有服务端能持续推送。背压处理上,WebSocket 需要同时关注“上行窗口”和“下行队列”,SSE 主要关注下行缓冲与断线重连。压测脚本必须按协议方向设计,否则会高估或低估真实负载。

为何常见:因为两者都属于“长连接”,看上去都是挂着的连接,容易忽略传输方向的本质差异。

误区长连接压测就是不断建立连接,把连接数推到 10 万就算成功。

正解连接建立只是开始,长期稳定才是关键。心跳超时、空闲回收、断线重连这些“维持阶段”的行为,才是长连接最常见的故障来源。压测必须包含长时间运行脚本,并主动配置心跳周期来触发探活逻辑。

为何常见:短连接压测的惯性思维让人只关注“打满吞吐”,忘记了长连接真正考验的是时间维度上的稳定性。

误区既然队列会堆满,那把服务端发送队列调大,不就不会丢消息了吗?

正解增大缓冲区只是把问题往后推。队列越大,单条连接占用的内存越高,背压严重时反而更容易触发 OOM。正确的做法是让生产者感知消费者速度,通过滑动窗口、限速、丢弃过期消息来控制队列长度。

为何常见:因为“加内存”是运维里最直观的应急手段,但它没有解决“上游太快、下游太慢”的根本矛盾。

误区压测几小时后,进程 RSS 没有持续上涨,就说明内存没问题。

正解堆内对象会被 GC 回收,RSS 平稳可能只是“锯齿形”的表面结果。长连接的内存风险常发生在堆外缓冲区、Netty ByteBuf、连接对象池等地方,必须同时监控堆内存、直接内存、发送队列长度和 GC 日志。

为何常见:监控面板默认只展示进程总内存,容易让人漏掉隐藏在堆外和队列里的真实消耗。

误区把 wrk 或 ab 的并发数调大、持续时间拉长,就能测试 WebSocket/SSE 服务。

正解短连接工具关注的是“请求-响应”闭环,无法模拟长连接的握手升级、心跳维持、流式消息分片、断线重连等状态机。长连接压测需要专门的客户端工具或自研脚本,能够保持连接、监听主动推送、记录协议级事件。

为何常见:因为这些工具使用简单、报告丰富,初学者容易忽略协议状态与生命周期管理之间的巨大差异。

面试题

Q1. WebSocket 与 SSE 在传输方向上的核心区别是什么?

单选题 · easy

  • A.WebSocket 支持全双工,SSE 只能服务端推送← 正确答案
  • B.WebSocket 只能客户端推送,SSE 支持全双工
  • C.两者都属于短连接轮询,没有本质区别
  • D.两者都依赖 UDP 报文,不能可靠传输

答案:A。WebSocket 是全双工协议,客户端和服务端可以同时互发消息;SSE 是基于 HTTP 的单工流,只能由服务端持续推送给客户端。理解了这一点,压测脚本的设计方向才不会出错。

Q2. 在长连接压测中,心跳机制最主要的作用是什么?

单选题 · easy

  • A.用于探测死连接并触发及时回收← 正确答案
  • B.用于压缩业务数据以减少带宽
  • C.用于替代 TLS 完成身份认证
  • D.用于把消息拆成更小的分片

答案:A。心跳 PING/PONG 是探活手段,能发现那些已经断掉但未被操作系统感知的连接。心跳超时后服务端会回收死连接,避免连接数被僵尸连接占满。它不是加密、认证或数据压缩机制。

Q3. 若服务端空闲超时设为 60 秒,心跳间隔最合理的是?

单选题 · mid

  • A.每隔 90 秒发一次心跳
  • B.每隔 120 秒发一次心跳
  • C.每隔 20 秒发一次心跳← 正确答案
  • D.每隔 61 秒发一次心跳

答案:C。心跳间隔应小于空闲超时的一半,即小于 30 秒。20 秒能保证在超时前至少完成一次心跳往返;而 90 秒、120 秒或 61 秒都会让连接在第二次心跳前超时断开。

Q4. 流式场景中,以下哪个现象最能说明背压已经产生?

单选题 · mid

  • A.发送端待确认队列长度不断上升← 正确答案
  • B.服务端 CPU 使用率降为零
  • C.磁盘 IO 长时间处于空闲
  • D.网络丢包率始终保持为零

答案:A。背压的本质是生产者速度大于消费者速度,消息在队列中堆积。因此“待确认队列长度上升”是最直接的信号。CPU 下降、磁盘空闲和零丢包都不代表背压,反而可能是流量太低。

Q5. 在长连接系统中,Little 定律 L = λ × W 中的 L 表示什么?

单选题 · hard

  • A.平均在途排队消息数量← 正确答案
  • B.每秒新建连接总数
  • C.单个连接的最大带宽
  • D.服务器磁盘总容量

答案:A。Little 定律把“消息到达率 λ”和“平均等待时间 W”相乘,得到系统内平均停留的消息数量 L。它常被用来评估发送队列或在途消息的堆积程度,是背压和内存分析的理论基础。

Q6. 长连接压测时,应当持续观察哪些指标?

多选题 · mid

  • A.连接建立成功率和耗时← 正确答案
  • B.心跳超时次数← 正确答案
  • C.消息推送延迟和队列长度← 正确答案
  • D.SQL 索引碎片率

答案:ABC。长连接压测关注连接生命周期、探活能力和消息流是否顺畅,因此 A、B、C 都是核心指标。SQL 索引碎片率与长连接稳定性没有直接关系,属于数据库调优范畴。

Q7. 单条长连接的内存评估中,固定开销通常不包括什么?

单选题 · mid

  • A.内核 Socket 收发缓冲
  • B.连接对象与解码器状态
  • C.已排队业务消息实体← 正确答案
  • D.epoll 事件的上下文信息

答案:C。固定开销指不随消息数量变化的每连接成本,包括 Socket 缓冲、连接对象、解码器状态和事件上下文。已排队的业务消息属于随队列长度变化的动态内存,应单独用“消息数 × 单条消息大小”估算。

Q8. 大规模长连接稳定性验证,以下做法正确的是?

多选题 · hard

  • A.逐步加压并观察连接数拐点← 正确答案
  • B.压测期间动态开关心跳观察恢复← 正确答案
  • C.持续数小时检查内存锯齿曲线← 正确答案
  • D.只压测一秒然后推算峰值

答案:ABC。逐步加压能找出服务端能撑住的连接数上限,动态开关心跳验证探活恢复,长时间观察内存曲线能发现泄漏。只压测一秒无法覆盖长连接真正的风险点,不能作为稳定性依据。

Q9. 与 WebSocket 相比,SSE 的主要优势是什么?

单选题 · easy

  • A.基于 HTTP,兼容性更好且免自定义协议← 正确答案
  • B.支持客户端任意双向发送二进制
  • C.可以在 UDP 上实现组播和广播
  • D.无需心跳也能永不超时断线

答案:A。SSE 直接跑在 HTTP 上,复用现有网关、代理和鉴权设施,实现简单,还自带断线重连机制。它不支持双向通信和任意二进制帧,也不能避免超时断线,只是协议实现更轻量。

Q10. 当 AI 推理输出速度远超客户端消费速度时,合理的背压策略有哪些?

多选题 · extreme

  • A.客户端按窗口大小确认并降低在途消息← 正确答案
  • B.服务端检测队列堆积后主动降速← 正确答案
  • C.丢弃过旧消息并记录丢弃告警← 正确答案
  • D.无限扩大内存队列直到写满

答案:ABC。滑动窗口、生产者降速和丢弃过期消息都是成熟的背压控制手段,能保护服务端内存稳定。无限扩大内存队列只会把压力后移,最终导致 OOM,是典型的错误做法。