Spring-data-redis cacheable并发导致的null,版本低于1.8.11会导致该问题
1.8.11之前的版本通过@cacheable缓存获取内容,代码层面是先判断缓存key值是否存在,存在在进行get缓存值,这就会导致非原子性操作。
问题场景:(高并发情况下,多线程操作同一个key)
步骤:
1.线程1获取缓存值,刚判断key值存在
2.线程2在此期间删除了缓存中的该key值
3.线程1继续执行,这时候获取缓存中的key值为null
1.8.11版本一下源代码如下:
RedisCache类中的get方法:
public RedisCacheElement get(final RedisCacheKey cacheKey) {
Assert.notNull(cacheKey, "CacheKey must not be null!");
Boolean exists = (Boolean)this.redisOperations.execute(new RedisCallback<Boolean>() {
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
return connection.exists(cacheKey.getKeyBytes());
}
});
return !exists ? null : new RedisCacheElement(cacheKey, this.fromStoreValue(this.lookup(cacheKey)));
}
解决方案一:
自定义RedisCache,覆写get方法
public class CustomRedisCache extends RedisCache {
@Override
public RedisCacheElement get(RedisCacheKey cacheKey) {
Assert.notNull(cacheKey, "CacheKey must not be null!");
//获取值代码提前
RedisCacheElement redisCacheElement=new RedisCacheElement(cacheKey, this.fromStoreValue(this.lookup(cacheKey)))
Boolean exists = (Boolean)this.redisOperations.execute(new RedisCallback<Boolean>() {
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
return connection.exists(cacheKey.getKeyBytes());
}
});
if(!exists){
return null;
}
return redisCacheElement;
}
}
自定义 RedisCacheManager 覆写createCache方法
public class CustomRedisCacheManager extends RedisCacheManager {
private boolean cacheNullValues;
public CustomRedisCacheManager(RedisOperations redisOperations) {
super(redisOperations);
}
public CustomRedisCacheManager(RedisOperations redisOperations, Collection<String> cacheNames) {
super(redisOperations, cacheNames);
}
public CustomRedisCacheManager(RedisOperations redisOperations, Collection<String> cacheNames, boolean cacheNullValues) {
super(redisOperations, cacheNames, cacheNullValues);
this.cacheNullValues = cacheNullValues;
}
@Override
protected RedisCache createCache(String cacheName) {
long expiration = computeExpiration(cacheName);
return new CustomRedisCache(cacheName, (isUsePrefix() ? getCachePrefix().prefix(cacheName) : null), getRedisOperations(), expiration,
cacheNullValues);
}
}
解决方案二:
讲spring-data-redis升级到1.8.11以上
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.8.22</version>
</dependency>
1.8.22版本源码如下:
public RedisCacheElement get(final RedisCacheKey cacheKey) {
Assert.notNull(cacheKey, "CacheKey must not be null!");
Boolean exists = (Boolean)this.redisOperations.execute(new RedisCallback<Boolean>() {
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
return connection.exists(cacheKey.getKeyBytes());
}
});
if (!exists) {
return null;
} else {
byte[] bytes = this.doLookup(cacheKey);
return bytes == null ? null : new RedisCacheElement(cacheKey, this.fromStoreValue(this.deserialize(bytes)));
}
}