Redis的分布式锁问题(八)基于Redis的分布式锁
分布式锁
什么是分布式锁?
分布式锁:满足分布式系统或集群模式下多进程可见并且互斥的锁。
当多个进程不在同一个系统中,用分布式锁控制多个进程对资源的访问。
在传统单机部署的情况下,可以使用Java并发处理相关的API(如ReentrantLcok或synchronized)进行互斥控制。
但是在分布式系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机并发控制锁策略失效,为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁的由来。
成为分布式锁的必要条件
分布式锁的实现方案
分布式锁的核心是实现多进程之间互斥,而满足这一点的方式有很多,市面上常见大致有三种分布式锁的实现方案
关于MySQL锁机制可以看看这里
【图灵MySQL】深入理解MySQL事务隔离级别与锁机制_面向鸿蒙编程的博客-CSDN博客https://blog.csdn.net/weixin_43715214/article/details/127594907
基于Redis实现分布式锁
关于锁的两个级别操作
关于锁的两个级别操作就是——获取锁、释放锁
获取锁
互斥,要确保只能有一个线程后去锁!
添加锁,利用setnx的互斥特性
setnx lock thread
添加锁的过期时间,避免服务宕机引起的死锁!
expire lock 10
释放锁
手动释放
del lock
超时释放
用expire命令添加了过期时间,等时间到了自动释放!
如果在添加过期时间后宕机了呢?
在redis中,获取锁和添加锁的命令可以一起执行,一条redis的命令中,是具有原子性的!
最优的写法如下
set lock thread1 nx ex 10
Redis分布式锁实现秒杀下单(初级版本)
代码实现
编写ILock接口,定义锁
/**
* redis的分布式锁
*/
public interface ILock {
/**
* 尝试获取锁
* @param timeoutSec 锁持有的超时时间,过期后自动释放
* @return true代表获取锁成功; false代表获取锁失败
*/
boolean tryLock(long timeoutSec);
/**
* 释放锁
*/
void unlock();
}
实现ILock接口,编写tryLock()和unLock()逻辑
/**
* redis的分布式锁
* 实现ILock接口
*/
public class SimpleRedisLock implements ILock {
// 不同的业务有不同的锁名称
private String name;
private StringRedisTemplate stringRedisTemplate;
private static final String KEY_PREFIX = "lock:";
private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";
private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;
public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {
this.name = name;
this.stringRedisTemplate = stringRedisTemplate;
}
static {
UNLOCK_SCRIPT = new DefaultRedisScript<>();
UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));
UNLOCK_SCRIPT.setResultType(Long.class);
}
@Override
public boolean tryLock(long timeoutSec) {
// 获取线程标示
String threadId = ID_PREFIX + Thread.currentThread().getId();
// 获取锁
// set lock thread1 nx ex 10
// nx : setIfAbsent , ex : timeoutSec
Boolean success = stringRedisTemplate.opsForValue()
.setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
// 自动拆箱!!!可能有风险
return Boolean.TRUE.equals(success);
}
@Override
public void unlock() {
stringRedisTemplate.delete(KEY_PREFIX + name);
}
}
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
@Resource
private ISeckillVoucherService seckillVoucherService;
@Resource
private RedisIdWorker redisIdWorker;
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public Result seckillVoucher(Long voucherId) {
// 1. 查询优惠券
SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
LocalDateTime nowTime = LocalDateTime.now();
// 2. 判断秒杀是否开始
if (nowTime.isBefore(voucher.getBeginTime())) {
return Result.fail("活动未开始!");
}
// 3. 判断秒杀是否结束
if (nowTime.isAfter(voucher.getEndTime())) {
return Result.fail("活动已结束!");
}
// 4. 判断库存
if (voucher.getStock() < 1) {
return Result.fail("已买完!");
}
Long userId = UserHolder.getUser().getId();
// 创建锁对象
SimpleRedisLock lock = new SimpleRedisLock("order:" + userId, stringRedisTemplate);
// 获取锁 1200s
boolean isLock = lock.tryLock(1200);
if (!isLock) {
return Result.fail("不允许重复下单!");
}
try {
IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
return proxy.createVoucherOrder(voucherId);
} finally {
lock.unlock();
}
}
@Transactional
public Result createVoucherOrder(Long voucherId) {
// 一人一单
Long userId = UserHolder.getUser().getId();
Integer count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
if (count > 0){
return Result.fail("用户已经购买过一次了!");
}
// 5. 减库存
boolean success = seckillVoucherService.update()
.setSql("stock = stock - 1")
.eq("voucher_id", voucherId).gt("stock", 0) // CAS方案(乐观锁)!
.update();
if (!success) {
return Result.fail("库存不足");
}
// 6. 创建订单
VoucherOrder voucherOrder = new VoucherOrder();
// 6.1 订单id
long orderId = redisIdWorker.nextId("order");
voucherOrder.setId(orderId);
// 6.2 用户id
voucherOrder.setUserId(userId);
// 6.3代金券id
voucherOrder.setVoucherId(voucherId);
save(voucherOrder);
return Result.ok(orderId);
}
}
从之前的 synchronized 到现在的 isLock
// 创建锁对象
SimpleRedisLock lock = new SimpleRedisLock("order:" + userId, stringRedisTemplate);
// 获取锁 1200s
boolean isLock = lock.tryLock(1200);
if (!isLock) {
return Result.fail("不允许重复下单!");
}
try {
IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy(); return proxy.createVoucherOrder(voucherId);
} finally {
lock.unlock();
}
测试结果
存在问题