一:具体实现
/**
* 存在在高并发情况下,同一个key大量查询缓存,但是此时缓存过期,为了保证同一个key回源,其他请求不等待
* 这个采用以空间换时间的概念,为了保证不阻塞,且业务数据能够正常返回,这边利用到了缓存副本,切保证缓存副本的过期时间
* 和正式缓存错开
* 如果没有获取到锁,切副本中也没有数据,这时候再回源查询
* @param param
* @return
*/
private List<Integer> redisLock(int param) {
if (param <= 0) {
return Collections.emptyList()
}
List<Integer> dataList = new ArrayList<>()
Jedis jedis = jedisPool.getResource()
String redisKey1 = "key" + param
var value = jedis.get(redisKey1)
if (null == value) {
final String identifier = UUID.randomUUID().toString()
String lockKey = "lock" + param + identifier
String redisKeyCopy = "keyCopy" + param
if (RedisLockUtils.tryLock(redisStoreClient, lockKey, identifier, 30, true)) {
try {
// db 回源
list = commonService.getDataList()
// 设置redis值
redisStoreClient.set(storeKey, list)
// 设置redis 副本值
redisStoreClient.set(storeKeyCopy, list)
} finally {
// 解锁
RedisLockUtils.unLock(redisStoreClient, lockKey, identifier)
}
} else {
list = redisStoreClient.get(storeKeyCopy)
if (null == list) {
log.info("storeKey redisKeyCopy is null")
list = commonService.getDataList()
}
}
}
Optional.ofNullable(Lists.newArrayList()).ifPresent(dataList::retainAll)
if (CollectionUtils.isNotEmpty(dataList) && dataList.size() > 10) {
dataList = dataList.subList(0, 10)
}
return dataList
}
二:方法中RedisLockUtils 工具类
package com.maoyan.show.hk.ticketdata.service.cache;
import com.maoyan.myredis.clientV2.MYStoreKey;
import com.maoyan.myredis.clientV2.impl.redis.MYRedisStoreClient;
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timeout;
import io.netty.util.TimerTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.TimeUnit;
public class RedisLockUtils {
private static final Logger log = LoggerFactory.getLogger(RedisLockUtils.class);
private static final HashedWheelTimer timer = new HashedWheelTimer();
private RedisLockUtils() {
throw new AssertionError("No util.RedisLockUtils instances for you!");
}
private static boolean isEmpty(CharSequence cs) {
return cs == null || cs.length() == 0;
}
public static Boolean tryLock(MYRedisStoreClient redisStoreClient, MYStoreKey storeKey, String identifier, int lockTimeout,String logTitle) {
return tryLock(redisStoreClient, storeKey, identifier, lockTimeout, 5, false,logTitle);
}
public static Boolean tryLock(MYRedisStoreClient redisStoreClient, MYStoreKey storeKey, String identifier, int lockTimeout, boolean isRenewal,String logTitle) {
return tryLock(redisStoreClient, storeKey, identifier, lockTimeout, 1, isRenewal,logTitle);
}
public static Boolean tryLock(MYRedisStoreClient redisStoreClient, MYStoreKey storeKey, String identifier, int lockTimeout, int acquireTimeout, boolean isRenewal, String logTitle) {
return tryLock(redisStoreClient, storeKey, identifier, lockTimeout, acquireTimeout, 1000L, isRenewal,logTitle);
}
public static Boolean tryLock(MYRedisStoreClient redisStoreClient, MYStoreKey storeKey, String identifier, int lockTimeout, int acquireTimeout, long retryDuration, boolean isRenewal,String logTitle) {
if (storeKey != null && lockTimeout > 0) {
Long endTime = System.currentTimeMillis() + acquireTimeout * 1000L;
log.info("{} :Try to acquire the lock. lockKey={},acquireTimeout={}s,lockTimeout={}s", logTitle,storeKey, acquireTimeout, lockTimeout);
do {
boolean acquiredLock = redisStoreClient.setnx(storeKey, identifier, lockTimeout);
if (acquiredLock) {
if (isRenewal) {
scheduleExpirationRenewal(redisStoreClient, storeKey, identifier, lockTimeout, logTitle);
}
log.info("{} : acquired lock. lockKey={}", logTitle,storeKey);
return true;
}
log.info("{} : Retry to acquire the lock. lockKey={},acquireTimeout={}s,lockTimeout={}s",logTitle, storeKey, acquireTimeout, lockTimeout);
try {
log.info("{} : wait 1000 milliseconds before retry. lockKey={}", logTitle, storeKey);
Thread.sleep(retryDuration);
} catch (InterruptedException ignored) {
;
}
} while (System.currentTimeMillis() < endTime);
}
return false;
}
public static void unLock(MYRedisStoreClient redisStoreClient, final MYStoreKey storeKey, final String userTag) {
if (storeKey != null && userTag != null) {
redisStoreClient.compareAndDelete(storeKey, userTag);
}
}
private static void scheduleExpirationRenewal(MYRedisStoreClient redisStoreClient, MYStoreKey storeKey, String identifier, int lockTimeout, String logTitle) {
Timeout task = timer.newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
try {
if (redisStoreClient.compareAndSet(storeKey, identifier, identifier, lockTimeout)) {
log.info("{} : lock time renewal success. lockKey={}", logTitle, storeKey);
scheduleExpirationRenewal(redisStoreClient, storeKey, identifier, lockTimeout, logTitle);
}
} catch (Exception e) {
log.info("{} : lock time renewal failed. lockKey={}", logTitle, storeKey);
}
}
}, lockTimeout * 1000L * 2 / 3, TimeUnit.MILLISECONDS);
}
}