Files
openteam/server/internal/proxy/rawlog.go
T
Sakurasan f1181150aa perf: 亲和表分片、记账按驱动并发、访问日志与原始体控量
继续上一轮性能改造的剩余项:

- 会话亲和表分 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)全绿。
2026-10-30 14:25:10 +08:00

72 lines
1.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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...)
}