Spring Redis Cache @Cacheable 大并发下返回null
2017-12-12 00:00
120 查看
问题描述
最近我们用Spring Cache + redis来做缓存。在高并发下@Cacheable 注解返回的内容是null。查看了一下源代码,在使用注解获取缓存的时候,RedisCache的get方法会先去判断key是否存在,然后再去获取值。这了就有一个漏铜,当线程判断了key是存在的,紧接着这个时候这个key过期了,这时线程再去获取值的时候返回的是null。RedisCache的get方法源码:
public RedisCacheElement get(final RedisCacheKey cacheKey) { Assert.notNull(cacheKey, "CacheKey must not be null!"); // 判断Key是否存在 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; } // 获取key对应的值 return new RedisCacheElement(cacheKey, fromStoreValue(lookup(cacheKey))); }
解决方案
这个流程有问题,解决方案就是把这个流程倒过来,先去获取值,然后去判断这个key是否存在。不能直接用获取的值根据是否是NULL判断是否有值,因为Reids可能缓存NULL值。重写RedisCache的get方法:
public RedisCacheElement get(final RedisCacheKey cacheKey) { Assert.notNull(cacheKey, "CacheKey must not be null!"); RedisCacheElement redisCacheElement = new RedisCacheElement(cacheKey, fromStoreValue(lookup(cacheKey))); 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 redisCacheElement; }
完整实现:
重写RedisCache的get方法
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.cache.RedisCache;
import org.springframework.data.redis.cache.RedisCacheElement;
import org.springframework.data.redis.cache.RedisCacheKey;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.util.Assert;
/**
* 自定义的redis缓存
*
* @author yuhao.wang
*/
public class CustomizedRedisCache extends RedisCache {
private final RedisOperations redisOperations;
private final byte[] prefix;
public CustomizedRedisCache(String name, byte[] prefix, RedisOperations<? extends Object, ? extends Object> redisOperations, long expiration) {
super(name, prefix, redisOperations, expiration);
this.redisOperations = redisOperations;
this.prefix = prefix;
}
public CustomizedRedisCache(String name, byte[] prefix, RedisOperations<? extends Object, ? extends Object> redisOperations, long expiration, boolean allowNullValues) {
super(name, prefix, redisOperations, expiration, allowNullValues);
this.redisOperations = redisOperations;
this.prefix = prefix;
}
/**
* 重写父类的get函数。
* 父类的get方法,是先使用exists判断key是否存在,不存在返回null,存在再到redis缓存中去取值。这样会导致并发问题,
* 假如有一个请求调用了exists函数判断key存在,但是在下一时刻这个缓存过期了,或者被删掉了。
* 这时候再去缓存中获取值的时候返回的就是null了。
* 可以先获取缓存的值,再去判断key是否存在。
*
* @param cacheKey
* @return
*/
@Override
public RedisCacheElement get(final RedisCacheKey cacheKey) { Assert.notNull(cacheKey, "CacheKey must not be null!"); RedisCacheElement redisCacheElement = new RedisCacheElement(cacheKey, fromStoreValue(lookup(cacheKey))); 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 redisCacheElement; }
/**
* 获取RedisCacheKey
*
* @param key
* @return
*/
private RedisCacheKey getRedisCacheKey(Object key) {
return new RedisCacheKey(key).usePrefix(this.prefix)
.withKeySerializer(redisOperations.getKeySerializer());
}
}
配置Redis管理器
@Configuration public class RedisConfig { // redis缓存的有效时间单位是秒 @Value("${redis.default.expiration:3600}") private long redisDefaultExpiration; @Bean public RedisCacheManager cacheManager(RedisTemplate<Object, Object> redisTemplate) { //核心代码 new 一个 自定义的 内存管理器 RedisCacheManager redisCacheManager = new CustomizedRedisCacheManager(redisTemplate); return redisCacheManager; } }
相关文章推荐
- Spring Redis Cache @Cacheable 大并发下返回null
- Redis缓存实践:自定义注解、缓存工具类、基于Spring注解@cacheable
- 【redis】5.spring boot项目中,直接在spring data jpa的Repository层使用redis +redis注解@Cacheable直接在Repository层使用,报错问题处理Null key returned for cache operation
- spring boot 整合 redis,使用@Cacheable,@CacheEvict,@CachePut,jedisPool操作redis数据库
- SpringBoot使用Redis做缓存,@Cacheable、@CachePut、@CacheEvict等注解的使用
- spring4集成redis 并使用@cacheable注解
- Spring Cache+Redis实现自定义注解缓存
- 王振威 在 Spring 3.1 中使用 @Cacheable 实现缓存
- spring整合redis缓存,以注解(@Cacheable、@CachePut、@CacheEvict)形式使用
- springboot cache redis 缓存
- Spring-Boot (四) cache/ehcache/redis-cache集成使用
- spring 注解 @ResponseBody 返回JSON 设置不返回为 null 的值
- Springboot中Spring-cache与redis整合
- SpringBoot控制返回的值为null的情况替换为空字符串
- SpringBoot Cache 注解 @Cacheable,@CachePut , @CacheEvict
- spring boot 返回的json中去掉值为null的属性
- Android:解决view.getDrawingCache()返回null的问题
- spring 缓存 @CachePut 和 @Cacheable 区别
- Spring缓存注解@Cacheable、@CacheEvict、@CachePut使用
- Spring Boot cache backed redis