问题描述
最近我们用Spring Cache + redis来做缓存,使用的是1.8.10.RELEASE版本的spring-data-redis。在高并发下数据库存在数据但是@Cacheable 注解返回的内容是null。查看了一下源代码,在使用注解获取缓存的时候,RedisCache的get方法会先去判断key是否存在,然后再去获取值。这了就有一个漏铜,当线程1判断了key是存在的,紧接着这个时候这个key过期了,这时线程1再去获取值的时候返回的是null。
org.springframework.data.redis.cache.RedisCache.get(RedisCacheKey)方法源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| public RedisCacheElement get(final RedisCacheKey cacheKey) {
Assert.notNull(cacheKey, "CacheKey must not be null!");
Boolean exists = (Boolean) redisOperations.execute(new RedisCallback<Boolean>() {
@Override public Boolean doInRedis(RedisConnection connection) throws DataAccessException { return connection.exists(cacheKey.getKeyBytes()); } });
if (!exists.booleanValue()) { return null; } return new RedisCacheElement(cacheKey, fromStoreValue(lookup(cacheKey))); }
protected Object lookup(Object key) {
RedisCacheKey cacheKey = key instanceof RedisCacheKey ? (RedisCacheKey) key : getRedisCacheKey(key);
byte[] bytes = (byte[]) redisOperations.execute(new AbstractRedisCacheCallback<byte[]>( new BinaryRedisCacheElement(new RedisCacheElement(cacheKey, null), cacheValueAccessor), cacheMetadata) {
@Override public byte[] doInRedis(BinaryRedisCacheElement element, RedisConnection connection) throws DataAccessException { return connection.get(element.getKeyBytes()); } });
return bytes == null ? null : cacheValueAccessor.deserializeIfNecessary(bytes); }
|
解决方案
更换版本:spring-data-redis在1.8.11的版本中修复了该问题
1 2 3 4 5
| <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>1.8.11.RELEASE</version> </dependency>
|
1.8.11的源代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| public RedisCacheElement get(final RedisCacheKey cacheKey) {
Assert.notNull(cacheKey, "CacheKey must not be null!");
Boolean exists = (Boolean) redisOperations.execute(new RedisCallback<Boolean>() {
@Override public Boolean doInRedis(RedisConnection connection) throws DataAccessException { return connection.exists(cacheKey.getKeyBytes()); } });
if (!exists) { return null; }
byte[] bytes = doLookup(cacheKey);
if (bytes == null) { return null; }
return new RedisCacheElement(cacheKey, fromStoreValue(deserialize(bytes))); }
private byte[] doLookup(Object key) { RedisCacheKey cacheKey = key instanceof RedisCacheKey ? (RedisCacheKey) key : getRedisCacheKey(key);
return (byte[]) redisOperations.execute(new AbstractRedisCacheCallback<byte[]>( new BinaryRedisCacheElement(new RedisCacheElement(cacheKey, null), cacheValueAccessor), cacheMetadata) {
@Override public byte[] doInRedis(BinaryRedisCacheElement element, RedisConnection connection) throws DataAccessException { return connection.get(element.getKeyBytes()); } }); }
|
配置Redis管理器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Configuration public class RedisConfig {
@Bean public CacheManager cacheManager(RedisTemplate<String,?> redisTemplate) { RedisCacheManager cacheManager= new RedisCacheManager(redisTemplate); cacheManager.setUsePrefix(true); cacheManager.setDefaultExpiration(60); return cacheManager; } }
|