Files
mayfly-go/server/internal/redis/api/key.go
2023-04-16 00:50:36 +08:00

190 lines
5.7 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 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)
}