- 新增 RoutingCache 组件,使用 sync.Map 缓存 Provider 和 Model - 新增 StatsBuffer 组件,使用 sync.Map + atomic.Int64 缓冲统计数据 - 扩展 StatsRepository.BatchUpdate 支持批量增量更新 - 改造 RoutingService/StatsService/ProviderService/ModelService 集成缓存 - 更新 usage-statistics spec,新增 routing-cache 和 stats-buffer spec - 新增单元测试覆盖缓存命中/失效/并发场景
135 lines
2.9 KiB
Go
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
|
|
}
|