每日一坑(0630)

55 阅读1分钟

在使用redis做数据的缓存时,有时还需要及时更新缓存。如果需要更新的key很多,没法直接获取,但是他们是符合一定正则表达式的,redis中也提供了 keys pattern 方法来为我们查询所有匹配 pattern 的keys,但是如果Redis的key太多的话,直接使用keys扫描会引起阻塞,这种情况再生产环境是非常危险的,所以需要进行分批扫描,再删除所有拿到的keys

import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.Set;

public class RedisKeyCleaner {

    private final RedisTemplate<String, Object> redisTemplate;

    public RedisKeyCleaner(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    // 批量删除以指定前缀开头的键
    public long deleteKeysByPrefix(String prefix) {
        Set<String> keysToDelete = scanKeysByPrefix(prefix);
        if (!keysToDelete.isEmpty()) {
            return redisTemplate.delete(keysToDelete); // 批量删除,返回删除数量
        }
        return 0;
    }

    // 使用 SCAN 安全获取匹配的键
    private Set<String> scanKeysByPrefix(String prefix) {
        Set<String> keys = new HashSet<>();
        // 构建扫描选项:匹配模式 + 每批数量
        ScanOptions options = ScanOptions.scanOptions()
                .match(prefix + "*")      // 匹配前缀,如 "temp:*"
                .count(1000)              // 每批扫描数量(建议500~5000)
                .build();

        RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
        try (Cursor<byte[]> cursor = connection.scan(options)) {
            while (cursor.hasNext()) {
                // 将字节数组转为字符串键名
                String key = new String(cursor.next(), StandardCharsets.UTF_8);
                keys.add(key);
            }
        } catch (Exception e) {
            throw new RuntimeException("SCAN 遍历异常", e);
        }
        return keys;
    }
}