Spring之——spring-data-redis 1.8.11之前@Cacheable 大并发下返回null

转载请注明出处:https://blog.csdn.net/l1028386804/article/details/81434949

在高并发下@Cacheable 注解返回的内容是null。查看了一下源代码,在使用注解获取缓存的时候,RedisCache的get方法会先去判断key是否存在,然后再去获取值。这了就有一个漏铜,当线程1判断了key是存在的,紧接着这个时候这个key过期了,这时线程1再去获取值的时候返回的是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))); 
} 
// 获取值 
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); 
}

解决方案

这个流程有问题,解决方案就是把这个流程倒过来,先去获取值,然后去判断这个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方法

package com.lyz.redis.cache; 
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 liuyazhuang 
 */ 
 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()); 
	 } 
}

重写RedisCacheManager

package com.lyz.redis.cache; 
import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 
import org.springframework.cache.Cache; 
import org.springframework.data.redis.cache.RedisCacheManager; 
import org.springframework.data.redis.core.RedisOperations; 
import org.springframework.data.redis.core.StringRedisTemplate; 
import java.util.Collection; 
/** 
* 自定义的redis缓存管理器 
* @author liuyazhuang 
*/ 
public class CustomizedRedisCacheManager extends RedisCacheManager { 
	private static final Logger logger = LoggerFactory.getLogger(CustomizedRedisCacheManager.class); 
	public CustomizedRedisCacheManager(RedisOperations redisOperations) { 
		super(redisOperations); 
	} 
	public CustomizedRedisCacheManager(RedisOperations redisOperations, Collection<String> cacheNames) {
		super(redisOperations, cacheNames); 
	} 
	@Override 
	protected Cache getMissingCache(String name) { 
		long expiration = computeExpiration(name); 
		return new CustomizedRedisCache( name, (this.isUsePrefix() ? this.getCachePrefix().prefix(name) : null), this.getRedisOperations(), expiration);
	} 
 }

配置Redis管理器

@Configuration 
public class RedisConfig { 

	// redis缓存的有效时间单位是秒 
	@Value("${redis.default.expiration:3600}") 
	private long redisDefaultExpiration; 

	@Bean 
	public RedisCacheManager cacheManager(RedisTemplate<Object, Object> redisTemplate) { 
		RedisCacheManager redisCacheManager = new CustomizedRedisCacheManager(redisTemplate); 
		redisCacheManager.setUsePrefix(true); 
		//这里可以设置一个默认的过期时间 单位是秒 
		redisCacheManager.setDefaultExpiration(redisDefaultExpiration); return redisCacheManager; 
	} 
	/** 
	* 显示声明缓存key生成器 
	* 
	* @return 
	*/ 
	@Bean public KeyGenerator keyGenerator() { 
		return new SimpleKeyGenerator(); 
	} 
}

注意:

spring-data-redis官方已在1.8.11修复,详见:
https://jira.spring.io/browse/DATAREDIS-777

猜你喜欢

转载自blog.csdn.net/l1028386804/article/details/81434949