ERR unknown command `KEYS`, with args beginning with:
1:问题描述
使用缓存注解@Cacheable进行缓存,@CacheEvict进行匹配删除时,由于条件是前缀 + *,于是发生了如下错误:
2:原因
这个错误已经说明了问题,就是你的redis禁用了keys命令
3:解决方案
3.1:重写源码的clean方法,把里面的keys改为scan
源码地址:org.springframework.data.redis.cache.DefaultRedisCacheWriter
重写源码的方法:我只把需要重写的复制过来了,其他的方法还是和源码一样的,直接把源码全部复制过来即可
/** * 缓存配置 * * @author*/ @Slf4j public class RedisCacheClearConfig implements RedisCacheWriter { @Override public void clean(String name, byte[] pattern) { Assert.notNull(name, "Name must not be null!"); Assert.notNull(pattern, "Pattern must not be null!"); execute(name, connection -> { boolean wasLocked = false; try { if (isLockingCacheWriter()) { doLock(name, connection); wasLocked = true; } long start = System.currentTimeMillis(); // 使用scan命令代替原本的keys命令搜索key count(10000) 这个值不能太小,但是也不能过大,通常在5000-10000之间即可 源码地址:org.springframework.data.redis.cache.DefaultRedisCacheWriter Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder().match(new String(pattern)) .count(10000).build()); Set<byte[]> byteSet = new HashSet<>(); while (cursor.hasNext()) { byteSet.add(cursor.next()); } byte[][] keys = byteSet.toArray(new byte[0][]); if (keys.length > 0) { connection.del(keys); log.warn("redis缓存清理-数量:{}", keys.length); } else { log.warn("redis缓存清理-无清理项"); } log.info("redis缓存清理-执行时间:{}", (System.currentTimeMillis() - start)); } finally { if (wasLocked && isLockingCacheWriter()) { doUnlock(name, connection); } } return "OK"; }); } }
重新赋值RedisCacheManager,需要把重写的设置给redis缓存管理
首先写一个配置类,里面增加如下方法:
/** * @param redisConnectionFactory * @return 重写后的执行实现 主要是把clean方法中KEYS* 改为了scan的方式 */ @Bean public RedisCacheWriter redisCacheWriter(RedisConnectionFactory redisConnectionFactory) { return new RedisCacheClearConfig(redisConnectionFactory, Integer.valueOf(scanCount)); }
设置给管理器
@Bean public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {//重写后 改为scan的模式 RedisCacheWriter redisCacheWriter = redisCacheWriter(redisTemplate.getConnectionFactory()); CacheProperties cacheProperties = new CacheProperties();
//过期时间 cacheProperties.setTtl(600L); RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig() .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer())) .entryTtl(Duration.ofSeconds(cacheProperties.getTtl())); RedisCacheManager manager = new RedisCacheManagerResolver(redisCacheWriter, redisCacheConfiguration, getRedisCacheCustomConfiguration(redisTemplate, cacheProperties.getCustomTtl())); return manager; }
然后打断点,如果进入了,则说明重写成功!当然scan只是比keys要好一些而已。代码级别优化建议:一个是自己重写一个缓存逻辑,把查询的数据按照前缀 + id的具体方式存储,然后删除的时候,就直接使用具体的key而不是匹配模式