优惠卷秒杀——分布式锁

 

在集群的模式下,有多个jvm,每个jvm内部有他自己的锁,导致并行执行存在线程安全问题  

分布式锁:满足分布式系统或集群模式下多线程可见并且互斥的锁

 基于Redis实现分布式锁

 基于redis锁的初级版本

public interface ILock {


    /**
     * 尝试获取锁
     * @param secnodTime 锁特有的超时时间过期会自动释放
     * @return
     */
    public boolean tryLock(long secnodTime);

    /**
     * 释放锁
     */
    public void unlock();

}

 

public class SimpleRedisLock implements ILock{
    private String name;
    private StringRedisTemplate stringRedisTemplate;
    private static final String KEY_PREFIX="lock:";
    public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {
        this.name = name;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public boolean tryLock(long secnodTime) {
        //获取当前线程
        long threadId = Thread.currentThread().getId();
        //写入Redis 加入过期时间防止宕机发生在写入时间和过期时间之中
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadId + "", secnodTime, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(flag);
    }

    @Override
    public void unlock() {
        stringRedisTemplate.delete(KEY_PREFIX + name);
    }
}

 

@Autowired
private ISeckillVoucherService seckillVoucherService;
@Autowired
private RedisIdWorked redisIdWorked;
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
 * 限时优惠卷的秒杀
 * @param voucherId
 * @return
 */
@Override
public Result seckillVoucher(Long voucherId) {
    //1.查询优惠卷
    SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
    if(voucher==null){
        //该卷不存在
        return Result.fail("秒杀卷不存在");
    }
    //2.判断在秒杀开始前
    //得到秒杀开始时间
    LocalDateTime beginTime = voucher.getBeginTime();
    if (beginTime.isAfter(LocalDateTime.now())){
        //活动尚未开始
        return Result.ok("活动尚未开始");
    }
    //3.判断在秒杀开始后
    LocalDateTime endTime = voucher.getEndTime();
    if(endTime.isBefore(LocalDateTime.now())){
        return Result.ok("活动已经结束");
    }
    //4.判断库存是否充足
    Integer stock = voucher.getStock();
    if(stock<1){
        return Result.fail("库存不足");
    }
    UserDTO user = UserHolder.getUser();
    Long id = user.getId();
    SimpleRedisLock lock = new SimpleRedisLock("order:"+id,stringRedisTemplate);
    //尝试获取锁
    boolean flag = lock.tryLock(1200L);
    if(!flag){
        return Result.fail("一个人只能下一单");
    }
    try {
        //如果这个类本身调用是不具备管理事务的,如果使用Spring管理可以控制事务的一致性
        //获取一个spring的代理对象
        IVoucherOrderService proxy =(IVoucherOrderService) AopContext.currentProxy();
        //利用spring代理对象确保事务的一致性
        return proxy.createVoucherOrder(voucherId);
    } finally {
        //释放锁
        lock.unlock();
    }
}
@Transactional
public Result createVoucherOrder(Long voucherId){
    //5.保证一人一单
    //5.1用户id
    UserDTO user = UserHolder.getUser();
    Long id = user.getId();
    Integer count = query().eq("user_id", id).eq("voucher_id", voucherId).count();
    if(count>0){
        return Result.fail("该用户已经购买");
    }
    //6.扣减库存
    seckillVoucherService.update().setSql("stock=stock-1")
            .eq("voucher_id",voucherId).gt("stock",0)//stock>0
            .update();
    //7.创建订单
    VoucherOrder voucherOrder = new VoucherOrder();
    //7.1订单id
    long order = redisIdWorked.nextId("order");
    voucherOrder.setId(order);
    //7.2用户id
    voucherOrder.setUserId(id);
    //7.3购买代金卷的id
    voucherOrder.setVoucherId(voucherId);
    save(voucherOrder);
    //8.返回订单id
    return Result.ok(order);
}

分布式锁的误删问题

线程1获取了锁但业务发生阻塞导致,锁被超时释放带来的问题

 

 改进的点是 添加线程标识做出相应的判断,判断是不是自己的锁,是就释放

public class SimpleRedisLock implements ILock{
    private String name;
    private StringRedisTemplate stringRedisTemplate;
    private static final String KEY_PREFIX="lock:";
    //使用UUID来区分不同的JVM
    private static final String ID_PREFIX= UUID.randomUUID().toString(true)+"-";
    public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {
        this.name = name;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public boolean tryLock(long secnodTime) {
        //获取当前线程
        String threadId =ID_PREFIX +Thread.currentThread().getId();
        //写入Redis 加入过期时间防止宕机发生在写入时间和过期时间之中
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadId, secnodTime, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(flag);
    }

    @Override
    public void unlock() {
        //获取当前线程标识
        String threadId =ID_PREFIX +Thread.currentThread().getId();
        //获取redis中的线程标识然后做比较
        String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);
        if(id.equals(threadId)){
            stringRedisTemplate.delete(KEY_PREFIX + name);
        }

    }
}

判断锁和释放锁是两个动作,他们两个之间产生了阻塞导致产生问题,为了避免这个问题必须使这两个动作为原子性一起执行不能产生间隔

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值