获取锁:
基于setnx命令,此命令是一个原子性操作,并发请求锁时,因为redis是单线程的,并发的请求会串行执行,只有第一个set值成功的线程才能获取到锁,其他线程获取锁失败
SET resourde_name random_value NX PX 30000
resource_name(key):资源名称,可根据不同的业务区分不同的锁
random value(value):随机值,每个线程的随机值都不同,用于释放锁时的校验(防止并发时顺序混乱,防止线程释放了不属于自己的锁)
NX:key不存在时设置成功,key存在则设置不成功
PX:过期时间,防止出现异常情况锁无法释放,导致死锁
总结:
释放锁:
采用Redis的delete命令,在释放锁时要校验value的随机数是否是获取锁时set进去的随机数,两者相同才能释放锁,释放锁时可以采用LUA脚本
if redis.call("get",KEYS[1])==ARGV[1]
then return redis.call("del",KEYS[1])
else return 0
end
说明:
KEYS[1]:set资源时的key
ARGV[1]:当前传入的value
lua脚本通过设置分布式锁时的key来获取redis中对应的value值,如果这个值和当前传入的值相等,就执行delete操作,删除分布式锁,否则返回0不做任何操作
思考:为什么要校验value是否相同?
如图:若不校验value可能会导致并发情况下锁顺序混乱
@Slf4j
@Component
public class RedisLock {
@Resource
private RedisTemplate<String, Object> redisTemplate;
private final String value = UUID.randomUUID().toString();
public boolean rLock(String key, Long time) {
boolean locked = false;
//重试次数
int tryCount = 3;
//分布式锁,失败重试
while (!locked && tryCount > 0) {
locked = redisTemplate
.opsForValue()
.setIfAbsent(key, this.value, time, TimeUnit.MINUTES);
tryCount--;
if (!locked) {
log.info("抢夺锁失败,等待重试!");
}
try {
Thread.sleep(300);
} catch (InterruptedException e) {
log.error("线程被中断" + Thread.currentThread().getId(), e);
}
}
return locked;
}
public Boolean unLock(String key) {
String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
" return redis.call(\"del\",KEYS[1])\n" +
"else\n" +
" return 0\n" +
"end";
RedisScript<Boolean> redisScript = RedisScript.of(script, Boolean.class);
List<String> keys = Collections.singletonList(key);
Boolean result = redisTemplate.execute(redisScript, keys, value);
log.info("释放锁成功!" + result);
return result;
}
}
使用
@GetMapping("/lock")
public String lock() throws InterruptedException {
boolean lock = redisLock.rLock("order-xxx", 10L);
if (lock){
log.info("抢夺锁成功!执行任务!");
Thread.sleep(5000L);
boolean unLock = redisLock.unLock("order-xxx");
if (unLock){
log.info("释放锁成功!");
}
}else {
log.info("抢夺锁失败!");
}
return "分布式锁执行!";
}
org.redisson
redisson-spring-boot-starter
3.13.1
redis: database: 1
host: 123.25.252.220
port: 6379
@Autowired
private RedissonClient redissonClient;
/**
* 扣库存
*
* @param specId 商品规格id
* @param buyCounts 购买件数
* @return
*/
@Override
public int decreaseItemSpecStock(String specId, int buyCounts) {
//获取redis分布式锁
RLock lock = redissonClient.getLock("item_lock" + specId);
//设置超时时间和单位
lock.lock(3000, TimeUnit.MILLISECONDS);
int result = 0;
try {
//模拟扣库存操作
result = itemsMapperCustom.decreaseItemSpecStock(specId, buyCounts);
if (result != 1) {
throw new RuntimeException("订单创建失败,原因:库存不足!");
}
} catch (RuntimeException e) {
log.error("【扣减库存失败】",e);
}finally {
//无论执行是否成功都要释放锁
lock.unlock();
}
return result;
}