欢迎光临
我们一直在努力

Agent 流式连接遇到慢客户端:先限制一次写能占住多久

流式连接的慢写边界

模型还在生成,浏览器却停止读取。服务端的发送 goroutine 一直等,应用队列继续积累 token。连接数量看上去没有异常,内存和待发送任务却在上涨。处理这个问题,比继续调整连接上限更实际的是明确:允许积压多少字节,一次写最多等多久,关闭由谁负责。

Go net.Conn 的 deadline 同时影响已经阻塞和后续的 I/O;Close 会让阻塞的读写返回错误。Write 也可能在超时时返回 n > 0:已有部分字节写出,并非整条消息都没发生。下面只验证这一层契约,不冒充 WebSocket 压测。

一个可运行的有界写入例子

保存为 bounded_test.go,运行 GO111MODULE=off go test -race。net.Pipe 是内存连接,另一端不读时,写会被阻塞;它没有 TCP 内核缓冲区,也没有 WebSocket 帧。

package bounded

import (
"errors"
"io"
"net"
"os"
"testing"
"time"
)

func WriteBounded(c net.Conn, message []byte, maxBytes int, budget time.Duration) (int, error) {
if maxBytes <= 0 || budget <= 0 || len(message) > maxBytes {
return 0, errors.New("invalid budget or oversized message")
}
if err := c.SetWriteDeadline(time.Now().Add(budget)); err != nil {
return 0, err
}
total := 0
for total < len(message) {
n, err := c.Write(message[total:])
total += n
if err != nil { return total, err }
if n == 0 { return total, io.ErrNoProgress }
}
return total, nil
}

func TestSlowReader(t *testing.T) {
writer, reader := net.Pipe()
defer writer.Close()
defer reader.Close()
n, err := WriteBounded(writer, []byte("text"), 4, 10*time.Millisecond)
if n != 0 || !errors.Is(err, os.ErrDeadlineExceeded) {
t.Fatalf("expected blocked write deadline, n=%d err=%v", n, err)
}
}

func TestOversized(t *testing.T) {
writer, reader := net.Pipe()
defer writer.Close()
defer reader.Close()
if n, err := WriteBounded(writer, []byte("large"), 4, time.Second); n != 0 || err == nil {
t.Fatal("oversized message must be rejected before writing")
}
}

消息大小与写入时间分别设边界

本地 Go 1.26.5 的 race 测试通过。4 字节和 10 毫秒是反例参数,不是生产建议;没有验证十万连接、浏览器断连或真实代理链路。本函数要求同一连接的写和 deadline 由单一所有者管理。它保留已设置的 deadline;若后续复用连接,所有者必须重新设置或清除。示例测试结束直接关闭连接。

写超时之后,不能只加一个重试循环

如果一个应用消息已部分写出,再把整段内容重发,可能造成重复片段或破坏应用协议。更稳妥的处理是按协议关闭本次流,客户端用事件游标或应用层消息 ID 恢复;具体是否允许恢复,需要单独定义。写入本地连接成功也不等于用户已经看见文本。

写 deadline 只限制一个阻塞点。应用队列还需要同时限制消息数量和总字节数,并限制单条最大体积;队满后要选择暂停上游、终止流,或采用明确的丢弃协议。无法补偿的业务消息不能静默丢弃。WebSocket 库通常还要求单写者以及 ping/pong 和控制帧协作,应按具体库的契约实现。

TCP KeepAlive 是空闲连接探测,不是“快速释放 TIME_WAIT”的开关;关闭 Nagle 也不保证 token 零延迟。先把应用层的积压、写期限和关闭责任做清楚,再谈内核参数。

参考:Go net.Conn 与 TCP KeepAlive 文档。

赞(0)
未经允许不得转载:171主机测评 » Agent 流式连接遇到慢客户端:先限制一次写能占住多久
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址