初始提交:OCI 面板后端(含 GenAI 网关一期)

This commit is contained in:
Wang Defa
2026-07-09 15:31:04 +08:00
commit b9a3e97e84
168 changed files with 31794 additions and 0 deletions
+129
View File
@@ -0,0 +1,129 @@
// Package cache 提供进程内 TTL 缓存与 singleflight 合并,服务 OCI 读放大
// 与审计原始事件暂存等场景;单进程部署,无跨进程共享需求(调研①方案 A)。
package cache
import (
"math/rand/v2"
"strings"
"sync"
"time"
)
type entry struct {
val any
exp time.Time
}
// flight 是一次进行中的回源;后到的同 key 调用等待它完成。
type flight struct {
wg sync.WaitGroup
val any
err error
}
// Cache 是带键数上限的进程内 TTL 缓存;并发 miss 同 key 只回源一次。
type Cache struct {
mu sync.Mutex
items map[string]entry
flights map[string]*flight
max int
}
// New 创建缓存;max 为键数上限,到限时先清过期项、仍满则任意驱逐。
func New(max int) *Cache {
return &Cache{items: map[string]entry{}, flights: map[string]*flight{}, max: max}
}
// Get 返回未过期的缓存值。
func (c *Cache) Get(key string) (any, bool) {
c.mu.Lock()
defer c.mu.Unlock()
e, ok := c.items[key]
if !ok || time.Now().After(e.exp) {
return nil, false
}
return e.val, true
}
// Set 写入缓存;TTL 附加至多 1/10 的随机抖动,避免整批同时过期。
func (c *Cache) Set(key string, val any, ttl time.Duration) {
c.mu.Lock()
defer c.mu.Unlock()
if len(c.items) >= c.max {
c.evictLocked()
}
jitter := time.Duration(rand.Int64N(int64(ttl)/10 + 1))
c.items[key] = entry{val: val, exp: time.Now().Add(ttl + jitter)}
}
// evictLocked 先清全部过期项,仍到上限则任意删除腾出空位。
func (c *Cache) evictLocked() {
now := time.Now()
for k, e := range c.items {
if now.After(e.exp) {
delete(c.items, k)
}
}
for k := range c.items {
if len(c.items) < c.max {
return
}
delete(c.items, k)
}
}
// DeletePrefix 删除全部以 prefix 开头的键(写操作后按租户前缀失效)。
func (c *Cache) DeletePrefix(prefix string) {
c.mu.Lock()
defer c.mu.Unlock()
for k := range c.items {
if strings.HasPrefix(k, prefix) {
delete(c.items, k)
}
}
}
// join 登记或加入 key 的进行中回源;返回 true 表示已有同 key 回源在跑。
func (c *Cache) join(key string) (*flight, bool) {
c.mu.Lock()
defer c.mu.Unlock()
if f, ok := c.flights[key]; ok {
return f, true
}
f := &flight{}
f.wg.Add(1)
c.flights[key] = f
return f, false
}
// settle 结束回源登记并唤醒等待者。
func (c *Cache) settle(key string, f *flight) {
c.mu.Lock()
delete(c.flights, key)
c.mu.Unlock()
f.wg.Done()
}
// Do 返回缓存值;miss 时经 singleflight 回源,成功写缓存,错误不缓存
// (等待者共享同一错误,下次调用重新回源)。
func Do[T any](c *Cache, key string, ttl time.Duration, fn func() (T, error)) (T, error) {
var zero T
if v, ok := c.Get(key); ok {
return v.(T), nil
}
f, joined := c.join(key)
if joined {
f.wg.Wait()
if f.err != nil {
return zero, f.err
}
return f.val.(T), nil
}
f.val, f.err = fn()
c.settle(key, f)
if f.err != nil {
return zero, f.err
}
c.Set(key, f.val, ttl)
return f.val.(T), nil
}
+95
View File
@@ -0,0 +1,95 @@
package cache
import (
"errors"
"sync"
"sync/atomic"
"testing"
"time"
)
func TestGetSetExpiry(t *testing.T) {
c := New(10)
if _, ok := c.Get("k"); ok {
t.Fatal("空缓存不应命中")
}
c.Set("k", "v", 50*time.Millisecond)
if v, ok := c.Get("k"); !ok || v.(string) != "v" {
t.Fatalf("Get = %v %v, want v true", v, ok)
}
// jitter ≤ TTL/10,60ms 覆盖 50+5ms 上限
time.Sleep(60 * time.Millisecond)
if _, ok := c.Get("k"); ok {
t.Error("过期后不应命中")
}
}
func TestDeletePrefix(t *testing.T) {
c := New(10)
c.Set("t1|a", 1, time.Minute)
c.Set("t1|b", 2, time.Minute)
c.Set("t2|a", 3, time.Minute)
c.DeletePrefix("t1|")
if _, ok := c.Get("t1|a"); ok {
t.Error("t1|a 应被前缀删除")
}
if _, ok := c.Get("t2|a"); !ok {
t.Error("t2|a 不应被删除")
}
}
func TestEvictAtMax(t *testing.T) {
c := New(2)
c.Set("a", 1, time.Minute)
c.Set("b", 2, time.Minute)
c.Set("c", 3, time.Minute)
if n := len(c.items); n > 2 {
t.Errorf("键数 = %d, 应不超过上限 2", n)
}
if _, ok := c.Get("c"); !ok {
t.Error("最新写入的键应存在")
}
}
func TestDoSingleflight(t *testing.T) {
c := New(10)
var calls atomic.Int32
var wg sync.WaitGroup
for i := 0; i < 8; i++ {
wg.Add(1)
go func() {
defer wg.Done()
v, err := Do(c, "k", time.Minute, func() (int, error) {
calls.Add(1)
time.Sleep(30 * time.Millisecond)
return 42, nil
})
if err != nil || v != 42 {
t.Errorf("Do = %d, %v", v, err)
}
}()
}
wg.Wait()
if n := calls.Load(); n != 1 {
t.Errorf("并发 miss 回源 %d 次, want 1", n)
}
// 命中缓存不再回源
if _, err := Do(c, "k", time.Minute, func() (int, error) { calls.Add(1); return 0, nil }); err != nil {
t.Fatal(err)
}
if n := calls.Load(); n != 1 {
t.Errorf("命中后回源 %d 次, want 1", n)
}
}
func TestDoErrorNotCached(t *testing.T) {
c := New(10)
boom := errors.New("boom")
if _, err := Do(c, "k", time.Minute, func() (int, error) { return 0, boom }); !errors.Is(err, boom) {
t.Fatalf("err = %v, want boom", err)
}
v, err := Do(c, "k", time.Minute, func() (int, error) { return 7, nil })
if err != nil || v != 7 {
t.Errorf("错误不应入缓存,重试 = %d, %v", v, err)
}
}