1
0
Files
nex/backend/internal/service/routing_cache.go
lanyuanxiaoyao df253559a5 feat(cache): 实现 RoutingCache 和 StatsBuffer 优化数据库写入
- 新增 RoutingCache 组件,使用 sync.Map 缓存 Provider 和 Model
- 新增 StatsBuffer 组件,使用 sync.Map + atomic.Int64 缓冲统计数据
- 扩展 StatsRepository.BatchUpdate 支持批量增量更新
- 改造 RoutingService/StatsService/ProviderService/ModelService 集成缓存
- 更新 usage-statistics spec,新增 routing-cache 和 stats-buffer spec
- 新增单元测试覆盖缓存命中/失效/并发场景
2026-04-22 19:24:36 +08:00

135 lines
2.9 KiB
Go

package service
import (
"strings"
"sync"
"go.uber.org/zap"
"nex/backend/internal/domain"
"nex/backend/internal/repository"
)
type RoutingCache struct {
providers sync.Map
models sync.Map
modelRepo repository.ModelRepository
providerRepo repository.ProviderRepository
logger *zap.Logger
}
func NewRoutingCache(
modelRepo repository.ModelRepository,
providerRepo repository.ProviderRepository,
logger *zap.Logger,
) *RoutingCache {
return &RoutingCache{
modelRepo: modelRepo,
providerRepo: providerRepo,
logger: logger,
}
}
func (c *RoutingCache) GetProvider(id string) (*domain.Provider, error) {
if v, ok := c.providers.Load(id); ok {
return v.(*domain.Provider), nil
}
provider, err := c.providerRepo.GetByID(id)
if err != nil {
return nil, err
}
if v, ok := c.providers.Load(id); ok {
return v.(*domain.Provider), nil
}
c.providers.Store(id, provider)
return provider, nil
}
func (c *RoutingCache) GetModel(providerID, modelName string) (*domain.Model, error) {
key := providerID + "/" + modelName
if v, ok := c.models.Load(key); ok {
return v.(*domain.Model), nil
}
model, err := c.modelRepo.FindByProviderAndModelName(providerID, modelName)
if err != nil {
return nil, err
}
if v, ok := c.models.Load(key); ok {
return v.(*domain.Model), nil
}
c.models.Store(key, model)
return model, nil
}
func (c *RoutingCache) SetProvider(provider *domain.Provider) {
c.providers.Store(provider.ID, provider)
}
func (c *RoutingCache) SetModel(model *domain.Model) {
key := model.ProviderID + "/" + model.ModelName
c.models.Store(key, model)
}
func (c *RoutingCache) InvalidateProvider(id string) {
c.providers.Delete(id)
c.invalidateModelsByProvider(id)
c.logger.Debug("Provider 缓存失效", zap.String("provider_id", id))
}
func (c *RoutingCache) InvalidateModel(providerID, modelName string) {
key := providerID + "/" + modelName
c.models.Delete(key)
c.logger.Debug("Model 缓存失效",
zap.String("provider_id", providerID),
zap.String("model_name", modelName))
}
func (c *RoutingCache) invalidateModelsByProvider(providerID string) {
prefix := providerID + "/"
count := 0
c.models.Range(func(key, value interface{}) bool {
if strings.HasPrefix(key.(string), prefix) {
c.models.Delete(key)
count++
}
return true
})
if count > 0 {
c.logger.Debug("清除 Provider 相关 Model 缓存",
zap.String("provider_id", providerID),
zap.Int("count", count))
}
}
func (c *RoutingCache) Preload() error {
providers, err := c.providerRepo.List()
if err != nil {
return err
}
for i := range providers {
c.providers.Store(providers[i].ID, &providers[i])
}
models, err := c.modelRepo.List("")
if err != nil {
return err
}
for i := range models {
key := models[i].ProviderID + "/" + models[i].ModelName
c.models.Store(key, &models[i])
}
c.logger.Info("缓存预热完成",
zap.Int("providers", len(providers)),
zap.Int("models", len(models)))
return nil
}