当前位置: 首页 > article >正文

Redis代金卷(优惠卷)秒杀案例-多应用版

Redis代金卷(优惠卷)秒杀案例-单应用版-CSDN博客

上面这种方案,在多应用时候会出现问题,原因是你通过用户ID加锁

但是在多应用情况下,会出现两个应用的用户都有机会进去

让多个JVM使用同一把锁

这样就需要使用分布式锁

每个JVM都会有一个锁监视器,多个JVM就会有多个锁监视器

那么让所有JVM使用外部同一个锁监视器即可

不同的分布式锁实现方案

Redis分布式锁实现方案一

package com.hmdp.service.impl;

import com.hmdp.dto.Result;
import com.hmdp.entity.SeckillVoucher;
import com.hmdp.entity.VoucherOrder;
import com.hmdp.mapper.VoucherOrderMapper;
import com.hmdp.service.ISeckillVoucherService;
import com.hmdp.service.IVoucherOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.RedisIdWorker;
import com.hmdp.utils.SimpleRedisLock;
import com.hmdp.utils.UserHolder;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {


    @Autowired
    private ISeckillVoucherService seckillVoucherService;

    @Autowired
    private RedisIdWorker redisIdWorker;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    public Result seckillVoucher(Long voucherId) {
        //1.查询优惠券
        SeckillVoucher byId = seckillVoucherService.getById(voucherId);
        if (byId.getBeginTime().isAfter(LocalDateTime.now())) {
            //2.判断秒杀是否开始
            return Result.fail("秒杀尚未开始");
        }
        if (byId.getEndTime().isBefore(LocalDateTime.now())) {
            //3.判断秒杀是否结束
            return Result.fail("秒杀已经结束");
        }
        if (byId.getStock() < 1) {
            //4.判断库存是否充足
            return Result.fail("库存不足");
        }
        Long id = UserHolder.getUser().getId();//表示获取用户id
        SimpleRedisLock simpleRedisLock = new SimpleRedisLock("order:" + id, redisTemplate);
        //id.toString()实际是new了一个String对象,然后调用intern()方法,这个方法会去常量池中查找有没有这个对象,如果有就返回这个对象,如果没有就添加到常量池中,然后返回这个对象
        //synchronized (id.toString().intern()){
        boolean isLock = simpleRedisLock.tryLock(10L);//对于计算机来说10秒已经够了  除非业务非常复杂
        if(!isLock){
            return Result.fail("不允许重复下单");
        }
        //如果这样调用,前面有个this 事务使用代理对象去执行的
            //return createVoucherOrder(voucherId);
        try {
            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
            return proxy.createVoucherOrder(voucherId);
        }catch (Exception e){
            e.printStackTrace();
            return Result.fail(e.getMessage());
        }finally {
            simpleRedisLock.unlock();
        }

       // }
    }
    @Transactional(rollbackFor = Exception.class)
    public Result createVoucherOrder(Long voucherId) {
        Long id = UserHolder.getUser().getId();//
        //一人一单的查询
        Integer count = query().eq("user_id", id)
                .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)
                .update();
        if(!success){
            return Result.fail("库存不足");
        }
        //6.创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        //6.1.订单id
        long order = redisIdWorker.nextId("order");
        voucherOrder.setId(order);
        voucherOrder.setUserId(id);//登录用户先写死
        voucherOrder.setVoucherId(voucherId);
        save(voucherOrder);
        Result.ok(order);
    }
}

以上代码,在极端情况下会出现问题,例如 业务非常复杂,锁提前释放了,但是业务还没完成

可以通过线程标识 判断下是不是当前线程

原先用的是线程id,而线程ID往往在一个JVM中是递增的,但是考虑到多应用时候,可能出现线程ID相同的情况,因此,缓存UUID更好

改造代码

package com.hmdp.utils;

import cn.hutool.core.lang.UUID;
import com.hmdp.service.ILock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;


import java.util.concurrent.TimeUnit;

/**
 * @author hrui
 * @date 2025/1/31 3:09
 */
public class SimpleRedisLock implements ILock {

    private String name;
    private RedisTemplate<String,Object> redisTemplate;//需要对RedisTemplate<String,Object>进行配置注入
    //import cn.hutool.core.lang.UUID;会把UUID中的"-"去掉
    private static final String ID_PREFIX=UUID.randomUUID().toString(true)+"-";//import cn.hutool.core.lang.UUID;
    private static final String KEY_PREFIX = "lock:";

    public SimpleRedisLock(RedisTemplate<String, Object> redisTemplate, String name) {
        this.redisTemplate = redisTemplate;
        this.name = name;
    }

    @Override
    public boolean tryLock(long timeoutSec) {
        Boolean success = redisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name,
                ID_PREFIX + Thread.currentThread().getId(),//用线程ID做为value,可以更加直观些 其实放什么无所谓
                timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(success);//避免空指针
    }

    @Override
    public void unlock() {
        String threadId = ID_PREFIX + Thread.currentThread().getId();
        String id = (String) redisTemplate.opsForValue().get(KEY_PREFIX + name);
        if (threadId.equals(id)) {
            //符合 则删除,不符合就删除不了
            redisTemplate.delete(KEY_PREFIX + name);
        }
    }
}

以上代码还是有问题

原因在于GC回收时候,还是有那么一点点可能因业务时长导致锁的自动删除

要保证加锁和解锁的原子性


http://www.kler.cn/a/527801.html

相关文章:

  • Baklib赋能企业实现高效数字化内容管理提升竞争力
  • 数据分析系列--④RapidMiner进行关联分析(案例)
  • Spring RESTful API 设计与实现
  • 【力扣】49.字母异位词分组
  • SSM开发(三) spring与mybatis整合(含完整运行demo源码)
  • 模型I/O
  • JVM的GC详解
  • 六. Redis当中的“发布” 和 “订阅” 的详细讲解说明(图文并茂)
  • Fiddler(一) - Fiddler简介_fiddler软件
  • Spring--Bean的生命周期和循环依赖
  • leetcode——将有序数组转化为二叉搜索树(java)
  • SFTP 使用方法
  • 【Blazor学习笔记】.NET Blazor学习笔记
  • 【算法-位运算】求数字的补数
  • 知识库管理在提升客户服务质量中的应用与挑战分析
  • 嵌入式八股文之深入理解 C语言中的指针相关概念
  • 04树 + 堆 + 优先队列 + 图(D1_树(D2_二叉树(BT)(D1_基础学习)))
  • 笔记:电机及控制器的功率测量是怎么进行的?
  • 服务器架构设计大全及其优缺点概述
  • 长尾关键词在SEO提升网站流量中的关键角色与应用技巧分析
  • AVL树介绍
  • Java设计模式:行为型模式→观察者模式
  • LeetCode-180. 连续出现的数字
  • 吉首市城区地图政府附近1公里范围高清矢量pdf\cdr\ai内容测评
  • TCP三次握手和四次挥手面试题
  • WordPress eventon-lite插件存在未授权信息泄露漏洞(CVE-2024-0235)