Files
utility_go/internal/logic/gameKv/gameKv.go
ayflying 713a63356e 优化游戏数据批量持久化性能
- gameAct: 新增SaveV2Batch批量处理方法
  - 使用Redis MGET批量获取替代逐个GET
  - 使用WHERE uid IN批量查询替代逐个查询
  - 使用Batch批量插入替代逐条SQL
  - 修复: 只有数据库写入成功后才删除Redis key

- gameKv: 新增SavesV2Batch批量处理方法
  - 同样的批量优化策略
  - 修复: 只有数据库写入成功后才删除Redis key

- service: 更新接口定义添加新方法

性能提升: 1000条数据从1000次网络请求减少到2-3次
2026-02-26 15:44:14 +08:00

165 lines
3.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}