You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
88 lines
3.1 KiB
88 lines
3.1 KiB
package user
|
|
|
|
/*
|
|
缓存组件:统一负责把高频访问的引用数据加载到 Redis,并对外提供读取接口。
|
|
|
|
数据库(admin MySQL)部署在海外,直连查询延迟高;Redis 与服务同区域部署,读取几乎无网络开销。
|
|
本组件采用"全量预热 + 定时刷新 + 事件驱动刷新"策略,使 api 层的读请求恒命中 Redis:
|
|
- 预热:Start() 时全量写入 Redis
|
|
- 定时刷新:内部 cron 每 10 分钟全量刷新
|
|
- 事件驱动:后台增删改触发 Rpc_ModifyAppConifg,由 modelConfigComp 转发调用 Refresh()
|
|
|
|
api 层不再直查 DB,统一通过本组件的 GetXxx 方法读取。
|
|
*/
|
|
|
|
import (
|
|
"context"
|
|
"yunyan/comm"
|
|
"yunyan/lego/core"
|
|
"yunyan/lego/core/cbase"
|
|
"yunyan/lego/sys/cron"
|
|
"yunyan/lego/sys/log"
|
|
"yunyan/pb"
|
|
"yunyan/sys/cache"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// 缓存兜底 TTL:正常由定时/事件全量刷新覆盖,TTL 仅作异常情况下的过期保护
|
|
const cacheTTL = time.Hour * 24
|
|
|
|
type modelCacheComp struct {
|
|
cbase.ModuleCompBase
|
|
module *User
|
|
}
|
|
|
|
func (this *modelCacheComp) Init(service core.IService, module core.IModule, comp core.IModuleComp, opt core.IModuleOptions) (err error) {
|
|
this.ModuleCompBase.Init(service, module, comp, opt)
|
|
this.module = module.(*User)
|
|
return
|
|
}
|
|
|
|
func (this *modelCacheComp) Start() (err error) {
|
|
if err = this.ModuleCompBase.Start(); err != nil {
|
|
return
|
|
}
|
|
go this.Refresh() // 启动预热(异步,避免海外 DB 慢查询阻塞模块启动)
|
|
cron.AddFunc("0 */10 * * * ?", this.Refresh) // 每 10 分钟全量刷新
|
|
return
|
|
}
|
|
|
|
// Refresh 全量刷新所有缓存数据集到 Redis。best-effort:单个数据集失败仅记日志,不影响其它
|
|
func (this *modelCacheComp) Refresh() {
|
|
this.refreshProduct()
|
|
}
|
|
|
|
// ---- product ----------------------------------------------------------------
|
|
|
|
// refreshProduct 全量加载 product 写入 Redis
|
|
func (this *modelCacheComp) refreshProduct() {
|
|
models, err := this.module.model.getProducts()
|
|
if err != nil {
|
|
this.module.Error("refreshProduct load db", log.Field{Key: "err", Value: err.Error()})
|
|
return
|
|
}
|
|
items := make(map[string]any, len(models))
|
|
for _, m := range models {
|
|
items[fmt.Sprintf("%d", m.Id)] = m
|
|
}
|
|
if err = cache.ReplaceAll(context.Background(), comm.Cache_Product, items, cacheTTL); err != nil {
|
|
this.module.Error("refreshProduct write redis", log.Field{Key: "err", Value: err.Error()})
|
|
}
|
|
}
|
|
|
|
// GetProduct 按 id 读取设备信息:缓存优先,未命中或 Redis 异常时回退查 DB
|
|
func (this *modelCacheComp) GetProduct(id int32) (model *pb.DBProduct, err error) {
|
|
if v, found, cerr := cache.GetOne[pb.DBProduct](context.Background(), comm.Cache_Product, fmt.Sprintf("%d", id)); cerr == nil && found {
|
|
return v, nil
|
|
}
|
|
return this.module.model.getProduct(id)
|
|
}
|
|
|
|
// GetProducts 读取全部设备信息:缓存优先,缓存为空或异常时回退查 DB
|
|
func (this *modelCacheComp) GetProducts() (models []*pb.DBProduct, err error) {
|
|
if list, cerr := cache.GetAll[pb.DBProduct](context.Background(), comm.Cache_Product); cerr == nil && len(list) > 0 {
|
|
return list, nil
|
|
}
|
|
return this.module.model.getProducts()
|
|
}
|
|
|