2

springboot(20)Redis缓存@Cacheable对存在的数据返回null

 2 years ago
source link: https://wakzz.cn/2018/06/08/springboot/(20)Redis%E7%BC%93%E5%AD%98@Cacheable%E5%AF%B9%E5%AD%98%E5%9C%A8%E7%9A%84%E6%95%B0%E6%8D%AE%E8%BF%94%E5%9B%9Enull/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

springboot(20)Redis缓存@Cacheable对存在的数据返回null

祈雨的博客
2018-06-08

最近我们用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)方法源码:

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的版本中修复了该问题

<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.8.11.RELEASE</version>
</dependency>

1.8.11的源代码:

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);

// safeguard if key gets deleted between EXISTS and GET calls.
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管理器

@Configuration
public class RedisConfig {

/**
* ehcache默认过期时间
* @param redisTemplate
* @return
*/
@Bean
public CacheManager cacheManager(RedisTemplate<String,?> redisTemplate) {
RedisCacheManager cacheManager= new RedisCacheManager(redisTemplate);
// 开启使用缓存名称最为key前缀
cacheManager.setUsePrefix(true);
cacheManager.setDefaultExpiration(60);
return cacheManager;
}

}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK