
大模型输出是一条长连接,客户端却不一定一直快。移动网络暂停几秒,网关如果继续接收上游输出,就必须有人存放这些还没发走的内容。把队列容量写成 100 条并不能限制内存:一条事件可能只有几十字节,也可能装着整段工具结果。
先确定队列里放的是什么
SSE 标准定义的是 UTF-8 事件流,以及 data: 等字段和事件分隔规则。它没有替业务方规定缓冲上限。网络读到的一段字节、一个模型增量和一条完整 SSE 事件也不是同一个单位。
我的做法是先在编码层生成完整事件字节,再交给有界发送队列。预算按已编码字节计,不能用 Java 字符串的 length() 代替。中文和 emoji 会让“字符数等于字节数”的估算失真。

一个能检查预算归还的队列
下面只演示单连接的待发送队列。调用者拿走一项代表这部分缓冲预算已经移交给下一层;下一层仍需有自己的上限。
import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
public class Demo {
static class Queue {
final int limit;
int used;
final ArrayDeque<byte[]> pending = new ArrayDeque<>();
Queue(int limit) {
if (limit <= 0) throw new IllegalArgumentException();
this.limit = limit;
}
synchronized boolean offer(String event) {
byte[] bytes = event.getBytes(StandardCharsets.UTF_8);
if (bytes.length > limit - used) return false;
pending.add(bytes);
used += bytes.length;
return true;
}
synchronized byte[] take() {
byte[] bytes = pending.poll();
if (bytes != null) used -= bytes.length;
return bytes;
}
}
public static void main(String[] args) {
Queue q = new Queue(4);
if (!q.offer("😀") || q.used != 4) throw new AssertionError();
if (q.offer("a")) throw new AssertionError("overflow admitted");
if (q.take().length != 4 || q.used != 0) throw new AssertionError();
if (!q.offer("中文".substring(0, 1))) throw new AssertionError();
if (q.offer("中文")) throw new AssertionError("oversized event admitted");
System.out.println("UTF-8 byte cap enforced and budget returned");
}
}
保存为 Demo.java,执行 javac --release 11 Demo.java、java Demo。用 emoji 检查四字节占用,再检查取出后预算归还以及大事件拒绝,比只往队列里塞十个短字符串更容易发现口径错误。
满了以后,谁来做决定
入队失败不能只记日志然后继续读。可以暂停上游读取,在可取消的 API 上请求取消,或在协议仍允许时返回明确的流中断状态。对 HTTP 客户端、代理和浏览器的具体实现,需要分别确认流控与取消接口;offer=false 本身不会取消任何模型计费。
还应给连接数、单事件大小、积压时间和全局缓冲设上限。上例在转换字符串时会临时分配字节数组,它限制的是队列内持有的有效载荷,不是 JVM 总堆内存。传输层写缓冲、TLS 缓冲和重复编码都可能额外占内存。本文没有进行浏览器慢读或十万连接压测,不能从这个队列推导并发容量。
背压的关键是把“暂时发不出去”变成有界状态,并能沿调用链停下来。先把每一层的所有者和预算写清楚,再谈吞吐量。
本地运行环境为 Java/javac 12.0.1,使用 --release 11 编译并执行了文中的检查;没有安装新依赖。该结果只覆盖示例列出的逻辑,不代表真实模型、浏览器或生产链路已经验证。
本文探讨SSE流式传输中队列限100条却仍导致内存上涨的原因,指出关键在于事件字节预算而非条数限制。强调UTF-8编码下中文和emoji对字节数的影响,提出基于已编码字节的有界队列设计、预算归还验证机制,以及上游暂停、取消请求等背压策略。分析了传输层缓冲、TLS开销等隐性内存占用,并指出需分层明确所有者与预算边界。

395

被折叠的 条评论
为什么被折叠?



