Files
mayfly-go/server/internal/redis/api/key.go

190 lines
5.7 KiB
Go
Raw Normal View History

package api
import (
"context"
"fmt"
"mayfly-go/internal/redis/api/form"
"mayfly-go/internal/redis/api/vo"
"mayfly-go/internal/redis/domain/entity"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ginx"
"mayfly-go/pkg/req"
"strconv"
"strings"
"sync"
"time"
"github.com/redis/go-redis/v9"
)
// scan获取redis的key列表信息
func (r *Redis) Scan(rc *req.Ctx) {
ri := r.getRedisIns(rc)
form := &form.RedisScanForm{}
ginx.BindJsonAndValid(rc.GinCtx, form)
cmd := ri.GetCmdable()
ctx := context.Background()
kis := make([]*vo.KeyInfo, 0)
var cursorRes map[string]uint64 = make(map[string]uint64)
mode := ri.Info.Mode
if mode == "" || mode == entity.RedisModeStandalone || mode == entity.RedisModeSentinel {
redisAddr := ri.Cli.Options().Addr
// 汇总所有的查询出来的键值
var keys []string
// 有通配符或空时使用scan非模糊匹配直接匹配key
if form.Match == "" || strings.ContainsAny(form.Match, "*") {
cursorRes[redisAddr] = form.Cursor[redisAddr]
for {
ks, cursor := ri.Scan(cursorRes[redisAddr], form.Match, form.Count)
cursorRes[redisAddr] = cursor
if len(ks) > 0 {
// 返回了数据则追加总集合中
keys = append(keys, ks...)
}
// 匹配的数量满足用户需求退出
if int32(len(keys)) >= int32(form.Count) {
break
}
// 匹配到最后退出
if cursor == 0 {
break
}
}
} else {
// 精确匹配
keys = append(keys, form.Match)
}
var keyInfoSplit []string
if len(keys) > 0 {
keyInfosLua := `local result = {}
-- KEYS[1]为第1个参数,lua数组下标从1开始
for i = 1, #KEYS do
local ttl = redis.call('ttl', KEYS[i]);
local keyType = redis.call('type', KEYS[i]);
table.insert(result, string.format("%d,%s", ttl, keyType['ok']));
end;
return table.concat(result, ".");`
// 通过lua获取 ttl,type.ttl2,type2格式以便下面切割获取ttl和type。避免多次调用ttl和type函数
keyInfos, err := cmd.Eval(ctx, keyInfosLua, keys).Result()
biz.ErrIsNilAppendErr(err, "执行lua脚本获取key信息失败: %s")
keyInfoSplit = strings.Split(keyInfos.(string), ".")
}
for i, k := range keys {
ttlType := strings.Split(keyInfoSplit[i], ",")
ttl, _ := strconv.Atoi(ttlType[0])
// 没有存在该key,则跳过
if ttl == -2 {
continue
}
ki := &vo.KeyInfo{Key: k, Type: ttlType[1], Ttl: int64(ttl)}
kis = append(kis, ki)
}
} else if mode == entity.RedisModeCluster {
var keys []string
// 有通配符或空时使用scan非模糊匹配直接匹配key
if form.Match == "" || strings.ContainsAny(form.Match, "*") {
mu := &sync.Mutex{}
// 遍历所有master节点并执行scan命令合并keys
ri.ClusterCli.ForEachMaster(ctx, func(ctx context.Context, client *redis.Client) error {
redisAddr := client.Options().Addr
nowCursor := form.Cursor[redisAddr]
for {
ks, cursor, _ := client.Scan(ctx, nowCursor, form.Match, form.Count).Result()
// 遍历节点的内部回调函数使用异步调用,如不加锁会导致集合并发错误
mu.Lock()
cursorRes[redisAddr] = cursor
nowCursor = cursor
if len(ks) > 0 {
// 返回了数据则追加总集合中
keys = append(keys, ks...)
}
mu.Unlock()
// 匹配的数量满足用户需求退出
if int32(len(keys)) >= int32(form.Count) {
break
}
// 匹配到最后退出
if cursor == 0 {
break
}
}
return nil
})
} else {
// 精确匹配
keys = append(keys, form.Match)
}
// 因为redis集群模式执行lua脚本key必须位于同一slot中故单机获取的方式不适合
// 使用lua获取key的ttl以及类型减少网络调用
keyInfoLua := `local ttl = redis.call('ttl', KEYS[1]);
local keyType = redis.call('type', KEYS[1]);
return string.format("%d,%s", ttl, keyType['ok'])`
for _, k := range keys {
keyInfo, err := cmd.Eval(ctx, keyInfoLua, []string{k}).Result()
biz.ErrIsNilAppendErr(err, "执行lua脚本获取key信息失败: %s")
ttlType := strings.Split(keyInfo.(string), ",")
ttl, _ := strconv.Atoi(ttlType[0])
// 没有存在该key,则跳过
if ttl == -2 {
continue
}
ki := &vo.KeyInfo{Key: k, Type: ttlType[1], Ttl: int64(ttl)}
kis = append(kis, ki)
}
}
size, _ := cmd.DBSize(context.TODO()).Result()
rc.ResData = &vo.Keys{Cursor: cursorRes, Keys: kis, DbSize: size}
}
func (r *Redis) TtlKey(rc *req.Ctx) {
ri, key := r.checkKeyAndGetRedisIns(rc)
ttl, err := ri.GetCmdable().TTL(context.Background(), key).Result()
biz.ErrIsNil(err, "ttl失败: %s")
if ttl == -1 {
rc.ResData = -1
} else {
rc.ResData = ttl.Seconds()
}
}
func (r *Redis) DeleteKey(rc *req.Ctx) {
ri, key := r.checkKeyAndGetRedisIns(rc)
rc.ReqParam = fmt.Sprintf("%s -> 删除key: %s", ri.Info.GetLogDesc(), key)
ri.GetCmdable().Del(context.Background(), key)
}
func (r *Redis) RenameKey(rc *req.Ctx) {
form := &form.Rename{}
ginx.BindJsonAndValid(rc.GinCtx, form)
ri := r.getRedisIns(rc)
rc.ReqParam = fmt.Sprintf("%s -> 重命名key[%s] -> [%s]", ri.Info.GetLogDesc(), form.Key, form.NewKey)
ri.GetCmdable().Rename(context.Background(), form.Key, form.NewKey)
}
func (r *Redis) ExpireKey(rc *req.Ctx) {
form := &form.Expire{}
ginx.BindJsonAndValid(rc.GinCtx, form)
ri := r.getRedisIns(rc)
rc.ReqParam = fmt.Sprintf("%s -> 重置key[%s]过期时间为%d", ri.Info.GetLogDesc(), form.Key, form.Seconds)
ri.GetCmdable().Expire(context.Background(), form.Key, time.Duration(form.Seconds)*time.Second)
}
// 移除过期时间
func (r *Redis) PersistKey(rc *req.Ctx) {
ri, key := r.checkKeyAndGetRedisIns(rc)
rc.ReqParam = fmt.Sprintf("%s -> 移除key[%s]的过期时间", ri.Info.GetLogDesc(), key)
ri.GetCmdable().Persist(context.Background(), key)
}