- gameAct: 新增SaveV2Batch批量处理方法 - 使用Redis MGET批量获取替代逐个GET - 使用WHERE uid IN批量查询替代逐个查询 - 使用Batch批量插入替代逐条SQL - 修复: 只有数据库写入成功后才删除Redis key - gameKv: 新增SavesV2Batch批量处理方法 - 同样的批量优化策略 - 修复: 只有数据库写入成功后才删除Redis key - service: 更新接口定义添加新方法 性能提升: 1000条数据从1000次网络请求减少到2-3次
165 lines
3.3 KiB
Go
165 lines
3.3 KiB
Go
package gameKv
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"fmt"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
|
||
"github.com/ayflying/utility_go/pkg"
|
||
"github.com/ayflying/utility_go/service"
|
||
"github.com/ayflying/utility_go/tools"
|
||
"github.com/gogf/gf/v2/frame/g"
|
||
"github.com/gogf/gf/v2/os/gctx"
|
||
"github.com/gogf/gf/v2/os/gtime"
|
||
"github.com/gogf/gf/v2/util/gconv"
|
||
)
|
||
|
||
var (
|
||
Name = "game_kv"
|
||
RunTimeMax *gtime.Time
|
||
)
|
||
|
||
type sGameKv struct {
|
||
Lock sync.Mutex
|
||
}
|
||
|
||
func New() *sGameKv {
|
||
return &sGameKv{}
|
||
}
|
||
|
||
func init() {
|
||
service.RegisterGameKv(New())
|
||
}
|
||
|
||
// SavesV1 方法
|
||
//
|
||
// @Description: 保存用户KV数据列表。
|
||
// @receiver s: sGameKv的实例。
|
||
// @return err: 错误信息,如果操作成功,则为nil。
|
||
func (s *sGameKv) SavesV1() (err error) {
|
||
var ctx = gctx.New()
|
||
RunTimeMax = gtime.Now().Add(time.Minute * 30)
|
||
g.Log().Debug(ctx, "开始执行游戏kv数据保存")
|
||
|
||
err = tools.Redis.RedisScanV2("user:kv:*", func(keys []string) (err error) {
|
||
if gtime.Now().After(RunTimeMax) {
|
||
g.Log().Error(ctx, "kv执行超时了,停止执行!")
|
||
err = errors.New("kv执行超时了,停止执行!")
|
||
return
|
||
}
|
||
if err = s.SavesV2Batch(ctx, keys); err != nil {
|
||
g.Log().Errorf(ctx, "批量保存KV失败: %v", err)
|
||
}
|
||
return
|
||
})
|
||
|
||
return
|
||
}
|
||
|
||
// 删除缓存key
|
||
func (s *sGameKv) DelCacheKey(ctx context.Context, uid int64) {
|
||
//如果有活跃,跳过删除
|
||
if getBool, _ := pkg.Cache("redis").
|
||
Contains(ctx, fmt.Sprintf("act:update:%d", uid)); getBool {
|
||
return
|
||
}
|
||
|
||
cacheKey := fmt.Sprintf("user:kv:%v", uid)
|
||
_, err := g.Redis().Del(ctx, cacheKey)
|
||
if err != nil {
|
||
g.Log().Error(ctx, err)
|
||
}
|
||
}
|
||
|
||
// SavesV2Batch 批量保存游戏KV数据 (优化版)
|
||
//
|
||
// @Description: 使用批量Redis MGET和批量数据库操作提升性能
|
||
// @param ctx context.Context: 上下文对象
|
||
// @param cacheKeys []string: 缓存键列表
|
||
// @return err error: 返回错误信息
|
||
func (s *sGameKv) SavesV2Batch(ctx context.Context, cacheKeys []string) (err error) {
|
||
if len(cacheKeys) == 0 {
|
||
return nil
|
||
}
|
||
|
||
type keyInfo struct {
|
||
cacheKey string
|
||
uid int64
|
||
}
|
||
|
||
var keyInfos []keyInfo
|
||
for _, cacheKey := range cacheKeys {
|
||
result := strings.Split(cacheKey, ":")
|
||
if len(result) < 3 {
|
||
continue
|
||
}
|
||
uid := gconv.Int64(result[2])
|
||
if uid == 0 {
|
||
continue
|
||
}
|
||
|
||
if getBool, _ := pkg.Cache("redis").Contains(ctx, fmt.Sprintf("act:update:%d", uid)); getBool {
|
||
continue
|
||
}
|
||
|
||
keyInfos = append(keyInfos, keyInfo{
|
||
cacheKey: cacheKey,
|
||
uid: uid,
|
||
})
|
||
}
|
||
|
||
if len(keyInfos) == 0 {
|
||
return nil
|
||
}
|
||
|
||
redisValues, err := g.Redis().MGet(ctx, cacheKeys...)
|
||
if err != nil {
|
||
g.Log().Errorf(ctx, "批量获取Redis失败: %v", err)
|
||
return err
|
||
}
|
||
|
||
type ListData struct {
|
||
Uid int64 `json:"uid"`
|
||
Kv interface{} `json:"kv"`
|
||
}
|
||
|
||
var list []*ListData
|
||
for _, ki := range keyInfos {
|
||
val, ok := redisValues[ki.cacheKey]
|
||
if !ok {
|
||
continue
|
||
}
|
||
var data interface{}
|
||
if val != nil {
|
||
gconv.Scan(val, &data)
|
||
}
|
||
if data == nil {
|
||
continue
|
||
}
|
||
list = append(list, &ListData{
|
||
Uid: ki.uid,
|
||
Kv: data,
|
||
})
|
||
}
|
||
|
||
if len(list) == 0 {
|
||
return nil
|
||
}
|
||
|
||
_, err = g.Model(Name).Data(list).Save()
|
||
if err != nil {
|
||
g.Log().Errorf(ctx, "批量保存KV失败: %v", err)
|
||
return err
|
||
}
|
||
|
||
for _, v := range list {
|
||
s.DelCacheKey(ctx, v.Uid)
|
||
}
|
||
|
||
g.Log().Debugf(ctx, "SavesV2Batch完成: 保存%d条", len(list))
|
||
return nil
|
||
}
|