redis 模糊删除key

微笑兔 发布于 08/10 17:30
阅读 480
收藏 0

【Gopher China万字分享】华为云的Go语言云原生实战经验!>>>

场景:

    我这边需要做一个用户账号体系,涉及到的权限信息我这边在登陆的时候会缓存,后续如果有人改动权限,则需要全部清空。比如:角色关联的菜单有人修改了,那么依赖这个角色的全部人员都要删除缓存。

网上大多是keys先查key在通过mulit批量删除,这样也可以,不过redis的keys查询很慢,数据量比较大的时候可能会导致redis卡死,但是springboot集成的redis组件,redistemplate并没有scan这个操作。

用pipelined 会好点,但是还有有点疑问,只能这么批量查询再删除吗?或者通过lua脚本模糊删除?

各位是怎么实现的?有好的设计和建议吗?谢谢

加载中
0
innerloop
innerloop

我们遇到 也没有正面解决,通过python 做任务 删除

微笑兔
微笑兔
这个我想了一下 我还是将缓存的时间缩短到30分钟,点击登出时清除,登陆时重新缓存,登陆期间的操作会默认刷新缓存时间,这样就不用考虑清除的问题,只是需要人员主动退出或等待半小时后登陆,我们这没有明确的时间要求,允许这种场景。或者你可以参考别人的回答,每次修改后,修改缓存的key末尾多加一个标识,老的缓存等失效后自动清除。这个办法我觉得目前是最好的,批量操作 不管怎么样都会有问题的。
0
自由PHP
自由PHP

修改key的前缀就可以了,每次清理缓存后自动变更一次前缀,比如清理前key是a_id_xx,修改后再查询就变成b_id_xx。当然特别提醒设置好redis的内存限制,所有的key都设置过期时间,redis会自行慢慢清理。

再一种,在redis建立单独的库进行缓存,需要删除的时候,直接用 FLUSHDB 命令达到全部清空的目的

微笑兔
微笑兔
谢谢!这个是我看到最好的办法了!非常感谢。
0
木九天
木九天
角色关联的菜单有人修改了,为什么要一下子删除:删除所有缓存? 

如果是我的话,我认为谁登陆,这个人相关数据在删,或者重新配置,而不是删除所有人

jump--jump
jump--jump
企业服务中这样修改的复杂度远高于删除重取的简单。而且他后面解释了是:角色关联的菜单有人修改了,那么依赖这个角色的全部人员都要删除缓存。
0
熏红基
熏红基

很简单,自己扩展一个类就行

package uml.tech.bigdata.sdkredis.ext;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 继承自StringRedisTemplate,用于重写某些不支持集群的方法
 *
 * @author SunYu
 */
@Component
public class StringRedisTemplateExt extends StringRedisTemplate {
    public static final int FETCH_BATCH = 500;//批量抓取多少条

    public StringRedisTemplateExt(RedisConnectionFactory connectionFactory) {
        super(connectionFactory);
    }

    /**
     * 重写keys方法,使用scan方式,避免redis实例卡死
     *
     * @param pattern *正则
     * @return
     */
    @Override
    public Set<String> keys(String pattern) {
        return new HashSet<>(keysByPattern(pattern));
    }

    /**
     * 批量删除key,使用scan方式,避免redis实例卡死
     *
     * @param pattern *正则
     * @return
     */
    public Long delByPattern(String pattern) {
        return delete(keysByPattern(pattern));
    }

    /**
     * 使用scan方式,查找keys
     *
     * @param pattern *正则
     * @return
     */
    public List<String> keysByPattern(String pattern) {
        List<String> list = new ArrayList<>();
        RedisConnectionFactory connectionFactory = getConnectionFactory();
        RedisConnection connection = connectionFactory.getConnection();
        Cursor<byte[]> scan = connection.scan(ScanOptions.scanOptions().count(FETCH_BATCH).match(pattern).build());
        scan.forEachRemaining(bytes -> list.add(StrUtil.utf8Str(bytes)));
        return list;
    }

    /**
     * 查找keys
     *
     * @param pattern
     * @param cursorCallback
     */
    public void keysByPattern(String pattern, CursorCallback cursorCallback) {
        RedisConnectionFactory connectionFactory = getConnectionFactory();
        RedisConnection connection = connectionFactory.getConnection();
        Cursor<byte[]> scan = connection.scan(ScanOptions.scanOptions().count(FETCH_BATCH).match(pattern).build());
        scan.forEachRemaining(bytes -> {
            String key = StrUtil.utf8Str(bytes);
            cursorCallback.execute(key);
        });
    }

    /**
     * 返回匹配的key数量
     *
     * @param pattern *正则
     * @return
     */
    public Long size(String pattern) {
        AtomicLong al = new AtomicLong(0);
        RedisConnectionFactory connectionFactory = getConnectionFactory();
        RedisConnection connection = connectionFactory.getConnection();
        Cursor<byte[]> scan = connection.scan(ScanOptions.scanOptions().count(FETCH_BATCH).match(pattern).build());
        scan.forEachRemaining(bytes -> al.incrementAndGet());
        return al.get();
    }

    /**
     * 获得多个key信息,返回key,value
     *
     * @param pattern *key正则
     * @return
     */
    public Map<String, String> mgetByPattern(String pattern) {
        Map<String, String> m = new HashMap<>();
        RedisConnectionFactory connectionFactory = getConnectionFactory();
        RedisConnection connection = connectionFactory.getConnection();
        Cursor<byte[]> scan = connection.scan(ScanOptions.scanOptions().count(FETCH_BATCH).match(pattern).build());
        List<String> keys = new ArrayList<>();
        scan.forEachRemaining(bytes -> {
            keys.add(StrUtil.utf8Str(bytes));
            if (keys.size() == FETCH_BATCH) {
                List<String> values = opsForValue().multiGet(keys);
                for (int i = 0; i < keys.size(); i++) {
                    m.put(keys.get(i), values.get(i));
                }
                keys.clear();
            }
        });
        List<String> values = opsForValue().multiGet(keys);
        for (int i = 0; i < keys.size(); i++) {
            m.put(keys.get(i), values.get(i));
        }
        return m;
    }

    /**
     * 获得多个key信息,返回key,value
     *
     * @param keys *key集合
     * @return
     */
    public Map<String, String> mgetByKeys(Collection<String> keys) {
        Map<String, String> m = new HashMap<>();
        CollUtil
                .split(keys, FETCH_BATCH)
                .forEach(keyList -> {
                    List<String> values = opsForValue().multiGet(keyList);
                    for (int i = 0; i < keyList.size(); i++) {
                        m.put(keyList.get(i), values.get(i));
                    }
                });
        return m;
    }

    /**
     * 获取hash类型的field与value
     *
     * @param key    *hash key
     * @param fields *多个field
     * @return map field, value
     */
    public Map<String, String> hmget(String key, Collection<String> fields) {
        Map<String, String> m = new HashMap<>();
        HashOperations<String, String, String> hash = opsForHash();
        CollUtil
                .split(fields, FETCH_BATCH)
                .forEach(fieldList -> {
                    List<String> values = hash.multiGet(key, fieldList);
                    for (int i = 0; i < fieldList.size(); i++) {
                        m.put(fieldList.get(i), values.get(i));
                    }
                });
        return m;
    }

}

里面用到了工具类

<dependency>
  <groupId>cn.hutool</groupId>
  <artifactId>hutool-all</artifactId>
  <version>5.3.10</version>
</dependency>

或者你直接这样写也行

String pattern="key*";
int FETCH_BATCH = 500;//批量抓取多少条
redisTemplate.execute((RedisCallback<Object>) connection -> {
    Cursor<byte[]> scan = connection.scan(ScanOptions.scanOptions().count(FETCH_BATCH).match(pattern).build());
    scan.forEachRemaining(bytes -> connection.del(bytes));
    return null;
});

 

微笑兔
微笑兔
恩 谢谢 楼上每次变更缓存的key可能更好,避免批量删除操作,对redis的性能也会有影响。而且大量的key对本地也是资源的消耗,不过工具类不错 谢谢
返回顶部
顶部