1、背景
目前在查询时并发量较高,qps 6000左右,数据查出来后缓存在redis中,有效期5分钟,为防止redis失效的瞬间有太多到达数据库,给数据库造成冲击,在查询时使用redis分布式锁,保证一种查询条件只有一个查询能请求到数据库。
2、使用
2.1引入jedis
<!--jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.4.1</version>
</dependency>
2.2 redis分布式锁工具类
package com.soul.open.portal.utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;
import javax.annotation.Resource;
import java.util.Collections;
/**
* @Description redis实现分布式锁
* @Author liujunzhong@soulapp.cn
* @Date 2021/7/14 下午8:49
**/
@Component
public class RedisDistributedLock {
private static Logger logger = LoggerFactory.getLogger(RedisDistributedLock.class);
/**
* 锁超时时间,1000ms
*/
public static final Integer LOCK_EXPIRE_TIME = 1000;
private static final String LOCK_SUCCESS = "OK";
private static final Long RELEASE_SUCCESS = 1L;
/**
* 分布式锁前缀
*/
public static final String LOCK_PREFIX = "lock:prefix:";
@Resource(name = "mediaSourceJedisPool")
private JedisPool mediaSourceJedisPool;
/**
* 获取分布式锁
*
* @param lockKey 锁
* @param requestId 请求标识
* @param expireTime 超期时间
* @return 是否获取成功
*/
public boolean tryGetDistributedLock(String lockKey, String requestId, long expireTime) {
try (Jedis jedis = mediaSourceJedisPool.getResource()) {
SetParams params = new SetParams().px(expireTime).nx();
String result = jedis.set(lockKey, requestId, params);
logger.info("tryGetDistributedLock result:{},lockKey:{},requestId:{}", result, lockKey, requestId);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
} catch (Exception e) {
logger.error("tryGetDistributedLock failed,lockKey:{},requestId:{}", lockKey, requestId, e);
}
return false;
}
/**
* 释放锁
*
* @param lockKey 锁
* @param requestId 请求标识
* @return 是否释放成功
*/
public boolean releaseDistributedLock(String lockKey, String requestId) {
try (Jedis jedis = mediaSourceJedisPool.getResource()) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
logger.info("releaseDistributedLock result:{},lockKey:{},requestId:{},", result, lockKey, requestId);
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
} catch (Exception e) {
String errMsg = "releaseDistributedLock failed,lockKey:" + lockKey + ",requestId:" + requestId;
logger.error(errMsg, e);
}
return false;
}
}
2.3使用分布式锁
/**
* 使用分布式锁,查询数据库
*/
private SourceDataResult getDataByDistributedLock(String mediaKey, String requestId, AppSourceParam param) {
String lockKey = RedisDistributedLock.LOCK_PREFIX + mediaKey;
//1、获取锁,从数据库查询
if (redisDistributedLock.tryGetDistributedLock(lockKey, requestId, RedisDistributedLock.LOCK_EXPIRE_TIME)) {
try {
List<SourceGroupVO> sourceGroups = getDataFromDb(param);
SourceDataResult result = sourceListConvertMap(sourceGroups);
try (Jedis jedis = mediaSourceJedisPool.getResource()) {
jedis.setex(mediaKey, SourceConstants.SOURCE_EXPIRE_TIME, JSONObject.toJSONString(result));
mediaSourceCache.put(mediaKey, result);
} catch (Exception e) {
logger.error("set data to redis error, param:{}", JSONObject.toJSONString(param), e);
}
return result;
} catch (Exception e) {
logger.error("get data from db by distributed lock error, param:{}", JSONObject.toJSONString(param), e);
} finally {
redisDistributedLock.releaseDistributedLock(lockKey, requestId);
}
}
//2、未获取锁,最多循环等待3次
int count = 0;
do {
try {
Thread.sleep(30);
} catch (InterruptedException e) {
logger.error("thread interrupted", e);
}
SourceDataResult dataResultFromRedis = getDataResultFromRedis(mediaKey);
if (dataResultFromRedis != null) {
logger.info("loop wait for data success,count:{}", count);
return dataResultFromRedis;
}
count++;
} while (count < 3);
return new SourceDataResult();
}
3、缺点
这种锁比较重,会同时锁住多台机器,在这里是为了防止太多请求同时查询数据库,如果有几个请求同时请求也是可以容忍的,比如一台机器请求一次,可以替换成java 本地的锁,不至于锁住整个集群,下篇文章介绍。