继续上一轮性能改造的剩余项: - 会话亲和表分 16 片(原来一个全局 mutex,每个请求都要走一遍), 键改为内联 FNV-1a 的 64 位摘要(原先是拼接字符串 + hex,每请求两次分配), 请求路径上零分配;新增键稳定性/无分配/分片并发测试。 - 记账落库按驱动自适应并发:Recorder 按用户分片到多个 worker(同一用户恒定 同片,流水顺序不变)。SQLite 只有一个写者,实测 4 worker 会互相抢锁把 p99 拖到 19–34ms,所以 SQLite 固定 1 个、PostgreSQL 用 4 个。 新增多分片落库与顺序测试(用显式 worker 数在 SQLite 上验证语义)。 - 访问日志:每请求一次 log.Printf 直写 stderr,高 QPS 下是系统调用热点; 改为 64 KiB 缓冲 + 每秒 flush(写/flush 互斥,-race 已验)。 - 原始体控量:LogRaw 落库的请求体截到 256 KiB、响应体截到 1 MiB 并标注原始 体积,避免单条日志几十 MB 拖慢 usage_logs 查询与备份。 - 渠道加权随机选路从 crypto/rand + big.Int 换成 math/rand/v2 的 IntN, 去掉每请求的系统调用与两次分配。 跳过的两项与原因: - 「PG 下把 model 目录查询合并成一次 JOIN」:目录已缓存 10s,冷路径每模型 10 秒才走一次,合并收益接近 0。 - 「每流一个 heartbeat goroutine」:Go 的 ticker 成本很低,千级并发流仍在 可接受范围,改动收益不抵风险。 实测(16 并发、mock 上游、SQLite):p50 679µs→558µs、p99 2.6ms→2.2ms、 RPS 18.7k→23.9k。验证:go vet、go test ./...、-race(proxy/channel/usage)全绿。
72 lines
1.7 KiB
Go
72 lines
1.7 KiB
Go
package proxy
|
||
|
||
import (
|
||
"bufio"
|
||
"fmt"
|
||
"log"
|
||
"os"
|
||
"sync"
|
||
"time"
|
||
)
|
||
|
||
// 原始体会话与访问日志。
|
||
|
||
// 原始请求/响应落库的上限:开启 LogRaw 时流式响应可能是整段 SSE(百 KB~MB 级),
|
||
// 单条日志几十 MB 会让 usage_logs 膨胀、列表查询和备份都变慢。超出部分截断并标注。
|
||
const (
|
||
rawRequestCap = 256 << 10 // 256 KiB
|
||
rawResponseCap = 1 << 20 // 1 MiB
|
||
)
|
||
|
||
// capRaw 把原始体截到 limit,并在末尾标注原始大小。
|
||
func capRaw(b []byte, limit int) string {
|
||
if len(b) <= limit {
|
||
return string(b)
|
||
}
|
||
return string(b[:limit]) + fmt.Sprintf("\n…(truncated by openteam: %d bytes total)", len(b))
|
||
}
|
||
|
||
// accessLog 访问日志:每个请求一行,直接写 stderr 会在高 QPS 下变成系统调用热点。
|
||
// 这里套一层 64KB 缓冲 + 每秒 flush,log.Logger 自带互斥,写入只是内存拷贝。
|
||
var (
|
||
accessLogOnce sync.Once
|
||
accessLog *log.Logger
|
||
)
|
||
|
||
// syncBuf 让 bufio.Writer 的写与定时 flush 互斥:
|
||
// bufio 本身不支持并发访问,而 flush 在独立 goroutine 里跑。
|
||
type syncBuf struct {
|
||
mu sync.Mutex
|
||
w *bufio.Writer
|
||
}
|
||
|
||
func (s *syncBuf) Write(p []byte) (int, error) {
|
||
s.mu.Lock()
|
||
defer s.mu.Unlock()
|
||
return s.w.Write(p)
|
||
}
|
||
|
||
func (s *syncBuf) Flush() error {
|
||
s.mu.Lock()
|
||
defer s.mu.Unlock()
|
||
return s.w.Flush()
|
||
}
|
||
|
||
func initAccessLog() {
|
||
w := &syncBuf{w: bufio.NewWriterSize(os.Stderr, 64<<10)}
|
||
accessLog = log.New(w, "", log.LstdFlags)
|
||
go func() {
|
||
t := time.NewTicker(time.Second)
|
||
defer t.Stop()
|
||
for range t.C {
|
||
_ = w.Flush()
|
||
}
|
||
}()
|
||
}
|
||
|
||
// accessf 写一行访问日志(带缓冲,不阻塞请求)。
|
||
func accessf(format string, args ...any) {
|
||
accessLogOnce.Do(initAccessLog)
|
||
accessLog.Printf(format, args...)
|
||
}
|