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回收时候,还是有那么一点点可能因业务时长导致锁的自动删除

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/963549.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

国产之光DeepSeek架构理解与应用分析

目录 初步探索DeepSeek的设计 一、核心架构设计 二、核心原理与优化 三、关键创新点 四、典型应用场景 五、与同类模型的对比优势 六、未来演进方向 从投入行业生产的角度看 一、DeepSeek的核心功能扩展 二、机械电子工程产业中的具体案例 1. 预测性维护&#xff08;Predictive…

基于微信小程序的医院预约挂号系统设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

架构技能(四):需求分析

需求分析&#xff0c;即分析需求&#xff0c;分析软件用户需要解决的问题。 需求分析的下一环节是软件的整体架构设计&#xff0c;需求是输入&#xff0c;架构是输出&#xff0c;需求决定了架构。 决定架构的是软件的所有需求吗&#xff1f;肯定不是&#xff0c;真正决定架构…

【学习笔记】深度学习网络-正则化方法

作者选择了由 Ian Goodfellow、Yoshua Bengio 和 Aaron Courville 三位大佬撰写的《Deep Learning》(人工智能领域的经典教程&#xff0c;深度学习领域研究生必读教材),开始深度学习领域学习&#xff0c;深入全面的理解深度学习的理论知识。 在之前的文章中介绍了深度学习中用…

c/c++高级编程

1.避免变量冗余初始化 结构体初始化为0&#xff0c;等价于对该内存进行一次memset&#xff0c;对于较大的结构体或者热点函数&#xff0c;重复的赋值带来冗余的性能开销。现代编译器对此类冗余初始化代码具有一定的优化能力&#xff0c;因此&#xff0c;打开相关的编译选项的优…

Vue 入门到实战 七

第7章 渲染函数 目录 7.1 DOM树 7.2 什么是渲染函数 7.3 h()函数 7.3.1 基本参数 7.3.2 约束 7.3.3 使用JavaScript代替模板功能 7.1 DOM树 7.2 什么是渲染函数 在多数情况下&#xff0c;Vue推荐使用模板template来创建HTML。然而在一些应用场景中&#xff0c;需要使用J…

小程序-基础加强-自定义组件

前言 这次讲自定义组件 1. 准备今天要用到的项目 2. 初步创建并使用自定义组件 这样就成功在home中引入了test组件 在json中引用了这个组件才能用这个组件 现在我们来实现全局引用组件 在app.json这样使用就可以了 3. 自定义组件的样式 发现页面里面的文本和组件里面的文…

MySQL5.5升级到MySQL5.7

【卸载原来的MySQL】 cmd打开命令提示符窗口&#xff08;管理员身份&#xff09;net stop mysql&#xff08;先停止MySQL服务&#xff09; 3.卸载 切换到原来5.5版本的bin目录&#xff0c;输入mysqld remove卸载服务 测试mysql -V查看Mysql版本还是5.5 查看了环境变量里的…

【memgpt】letta 课程4:基于latta框架构建MemGpt代理并与之交互

Lab 3: Building Agents with memory 基于latta框架构建MemGpt代理并与之交互理解代理状态,例如作为系统提示符、工具和agent的内存查看和编辑代理存档内存MemGPT 代理是有状态的 agents的设计思路 每个步骤都要定义代理行为 Letta agents persist information over time and…

DeepSeek-R1模型1.5b、7b、8b、14b、32b、70b和671b有啥区别?

deepseek-r1的1.5b、7b、8b、14b、32b、70b和671b有啥区别&#xff1f;码笔记mabiji.com分享&#xff1a;1.5B、7B、8B、14B、32B、70B是蒸馏后的小模型&#xff0c;671B是基础大模型&#xff0c;它们的区别主要体现在参数规模、模型容量、性能表现、准确性、训练成本、推理成本…

【TCP协议】流量控制 滑动窗口 拥塞控制

目录 说明&#xff1a; 流量控制 为什么要流量控制 什么是流量控制 如何控制流量&#xff1a;16位窗口大小 如果主机 B 一直没空间呢&#xff1f;标志位 PSH 滑动窗口&#xff1a;全面认识序号和确认序号 为什么需要滑动窗口&#xff1f; 理解滑动窗口 序号和确认序号…

K8S集群架构及主机准备

本次集群部署主机分布K8S集群主机配置主机静态IP设置主机名解析ipvs管理工具安装及模块加载主机系统升级主机间免密登录配置主机基础配置完后最好做个快照备份 2台负载均衡器 Haproxy高可用keepalived3台k8s master节点5台工作节点(至少2及以上)本次集群部署主机分布 K8S集群主…

三、js笔记

(一)JavaScript概述 1、发展历史 ScriptEase.(客户端执行的语言):1992年Nombas开发出C-minus-minus(C--)的嵌入式脚本语言(最初绑定在CEnvi软件中).后将其改名ScriptEase.(客户端执行的语言)Javascript:Netscape(网景)接收Nombas的理念,(Brendan Eich)在其Netscape Navigat…

穷举vs暴搜vs深搜vs回溯vs剪枝系列一>单词搜索

题解如下 题目&#xff1a;解析决策树&#xff1a;代码设计&#xff1a; 代码&#xff1a; 题目&#xff1a; 解析 决策树&#xff1a; 代码设计&#xff1a; 代码&#xff1a; class Solution {private boolean[][] visit;//标记使用过的数据int m,n;//行&#xff0c;列char…

使用Pygame制作“圣诞树”

1. 前言 圣诞节到来之际&#xff0c;来给自己写一个圣诞树小动画吧&#xff01;我们可以利用 Pygame 的绘图功能&#xff0c;轻松地在 2D 屏幕上绘制各种几何形状&#xff0c;并为圣诞树加上灯光闪烁、装饰品等效果。本篇将带领你实现一个简易版本的“屏幕圣诞树”&#xff0c…

Windows电脑本地部署运行DeepSeek R1大模型(基于Ollama和Chatbox)

文章目录 一、环境准备二、安装Ollama2.1 访问Ollama官方网站2.2 下载适用于Windows的安装包2.3 安装Ollama安装包2.4 指定Ollama安装目录2.5 指定Ollama的大模型的存储目录 三、选择DeepSeek R1模型四、下载并运行DeepSeek R1模型五、使用Chatbox进行交互5.1 下载Chatbox安装包…

《AI大模型开发笔记》DeepSeek技术创新点

一、DeepSeek横空出世 DeepSeek V3 以颠覆性技术架构创新强势破局&#xff01;革命性的上下文处理机制实现长文本推理成本断崖式下降&#xff0c;综合算力需求锐减90%&#xff0c;开启高效 AI 新纪元&#xff01; 最新开源的 DeepSeek V3模型不仅以顶尖基准测试成绩比肩业界 …

【深度学习】softmax回归的从零开始实现

softmax回归的从零开始实现 (就像我们从零开始实现线性回归一样&#xff0c;)我们认为softmax回归也是重要的基础&#xff0c;因此(应该知道实现softmax回归的细节)。 本节我们将使用Fashion-MNIST数据集&#xff0c;并设置数据迭代器的批量大小为256。 import torch from IP…

python学opencv|读取图像(五十二)使用cv.matchTemplate()函数实现最佳图像匹配

【1】引言 前序学习了图像的常规读取和基本按位操作技巧&#xff0c;相关文章包括且不限于&#xff1a; python学opencv|读取图像-CSDN博客 python学opencv|读取图像&#xff08;四十九&#xff09;原理探究&#xff1a;使用cv2.bitwise()系列函数实现图像按位运算-CSDN博客…

如果通过认证方式调用Sf的api

导读 OAuth 2.0:是一个开放的授权框架&#xff0c;当用户想要访问Service Provider提供的资源时&#xff0c;OAuth客户端可以从IdP(Identity Provider)获得授权而不需要获取用户名和密码就可以访问该资源题。 作者&#xff1a;vivi&#xff0c;来源&#xff1a;osinnovation …