spring-cache 使用 redis 作为缓存实现时,如果通过 @CacheEvict(allEntries = true)
批量删除缓存,默认会使用redis的 keys
命令来匹配需要删除的key。
定义一个缓存实现类,通过 @CacheEvict(allEntries = true)
注解来删除所有符合条件的缓存。
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Component;
@Component
public class FooCache {
@CacheEvict(cacheNames = "app::cache", allEntries = true)
public void clear () {};
}
运行测试方法,观察输出日志。
@Autowired
private FooCache fooCache;
@Test
public void test () {
this.fooCache.clear();
}
通过输出的日志,可以看到使用了 keys
命令进行匹配。
io.lettuce.core.AbstractRedisClient : Connecting to Redis at localhost:6379: Success
io.lettuce.core.RedisChannelHandler : dispatching command AsyncCommand [type=KEYS, output=KeyListOutput [output=[], error='null'], commandType=io.lettuce.core.protocol.Command]
i.lettuce.core.protocol.DefaultEndpoint : [channel=0x727bd766, /127.0.0.1:49186 -> localhost/127.0.0.1:6379, epid=0x1] write() writeAndFlush command AsyncCommand [type=KEYS, output=KeyListOutput [output=[], error='null'], commandType=io.lettuce.core.protocol.Command]
io.lettuce.core.protocol.CommandHandler : [channel=0x727bd766, /127.0.0.1:49186 -> localhost/127.0.0.1:6379, epid=0x1, chid=0x1] write(ctx, AsyncCommand [type=KEYS, output=KeyListOutput [output=[], error='null'], commandType=io.lettuce.core.protocol.Command], promise)
io.lettuce.core.protocol.CommandEncoder : [channel=0x727bd766, /127.0.0.1:49186 -> localhost/127.0.0.1:6379] writing command AsyncCommand [type=KEYS, output=KeyListOutput [output=[], error='null'], commandType=io.lettuce.core.protocol.Command]
io.lettuce.core.protocol.CommandHandler : [channel=0x727bd766, /127.0.0.1:49186 -> localhost/127.0.0.1:6379, epid=0x1, chid=0x1] Received: 4 bytes, 1 commands in the stack
io.lettuce.core.protocol.CommandHandler : [channel=0x727bd766, /127.0.0.1:49186 -> localhost/127.0.0.1:6379, epid=0x1, chid=0x1] Stack contains: 1 commands
在生产环境中执行 KEYS 命令的时,因为Redis是单线程的,KEYS 命令的性能随着数据库数据的增多而越来越慢,使用 KEYS 命令时会占用唯一的一个线程的大量处理时间,引发Redis阻塞并且增加Redis的CPU占用,导致所有的请求都被拖慢,可能造成Redis所在的服务器宕机。情况是很恶劣的,在实际生产运用的过程中应该禁用这个命令。试想如果Redis阻塞超过10秒,如果是在集群的场景下,可能导致集群判断Redis已经故障,从而进行故障切换。
修改key的匹配命令为 scan
spring-cache 提供了 RedisCacheManagerBuilderCustomizer
配置类,可以自定义 redis cache 的一些行为。
通过配置 BatchStrategy
启用 scan
。
import org.springframework.boot.autoconfigure.cache.RedisCacheManagerBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.BatchStrategies;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
@Configuration
public class RedisCacheScanConfiguration {
@Bean
public RedisCacheManagerBuilderCustomizer RedisCacheManagerBuilderCustomizer(RedisConnectionFactory redisConnectionFactory) {
return builder -> {
builder.cacheWriter(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory,
BatchStrategies.scan(100)));
};
}
}
添加配置后,再次运行测试方法,观察输出日志。
io.lettuce.core.AbstractRedisClient : Connecting to Redis at localhost:6379: Success
io.lettuce.core.RedisChannelHandler : dispatching command AsyncCommand [type=SCAN, output=KeyScanOutput [output=io.lettuce.core.KeyScanCursor@34f7b44f, error='null'], commandType=io.lettuce.core.protocol.Command]
i.lettuce.core.protocol.DefaultEndpoint : [channel=0x518f4d4d, /127.0.0.1:49753 -> localhost/127.0.0.1:6379, epid=0x1] write() writeAndFlush command AsyncCommand [type=SCAN, output=KeyScanOutput [output=io.lettuce.core.KeyScanCursor@34f7b44f, error='null'], commandType=io.lettuce.core.protocol.Command]
io.lettuce.core.protocol.CommandHandler : [channel=0x518f4d4d, /127.0.0.1:49753 -> localhost/127.0.0.1:6379, epid=0x1, chid=0x1] write(ctx, AsyncCommand [type=SCAN, output=KeyScanOutput [output=io.lettuce.core.KeyScanCursor@34f7b44f, error='null'], commandType=io.lettuce.core.protocol.Command], promise)
io.lettuce.core.protocol.CommandEncoder : [channel=0x518f4d4d, /127.0.0.1:49753 -> localhost/127.0.0.1:6379] writing command AsyncCommand [type=SCAN, output=KeyScanOutput [output=io.lettuce.core.KeyScanCursor@34f7b44f, error='null'], commandType=io.lettuce.core.protocol.Command]
io.lettuce.core.protocol.CommandHandler : [channel=0x518f4d4d, /127.0.0.1:49753 -> localhost/127.0.0.1:6379, epid=0x1, chid=0x1] Received: 15 bytes, 1 commands in the stack
io.lettuce.core.protocol.CommandHandler : [channel=0x518f4d4d, /127.0.0.1:49753 -> localhost/127.0.0.1:6379, epid=0x1, chid=0x1] Stack contains: 1 commands
可以看到,spring-cache 在批量删除时,是使用 scan
来匹配要删除的key。