Redis实践篇(二)优惠卷秒杀 一人一单、分布锁

news2024/11/24 14:53:16

目录

 全局ID生成器​编辑

实现优惠卷下单

优惠卷超卖问题

乐观锁

 一人一单

分布式锁

分布锁的实现

 基于Redis的分布锁

 Redis的Lua脚本

 再次改进Redis的分布锁

基于Redis的分布锁优化

Redisson分布式框架

 引入依赖

Redisson可重入锁原理

 Redisson分布锁原理​编辑


 全局ID生成器

 


@Component
public class RedisIdWorker {

    /**
     * 开始时间戳
     */
    private static  final long BEGIN_TIMESTAMP = 1640995200L;

    /**
     * 序列号的位数
     */
    private static  final int COUNT_BITS= 32;

    private StringRedisTemplate stringRedisTemplate;

    public RedisIdWorker(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    public long nextId(String keyPrefix){
        //1.生成时间戳

        LocalDateTime now = LocalDateTime.now();
        long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
        long timestamp = nowSecond - BEGIN_TIMESTAMP;
        //2.生成序列号
        //2.1获取当前日期,精确到天
        String date = now.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        //2.2自增长
        Long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);


        //3.拼接并返回
        return timestamp << COUNT_BITS | count;
    }


}

实现优惠卷下单

 

优惠卷超卖问题

 

乐观锁

 

 

 一人一单

 

 


@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {

    @Resource
    private ISeckillVoucherService seckillVoucherService;

    @Resource
    private RedisIdWorker redisIdWorker;

    @Override
    public Result seckillVoucher(Long voucherId) {
        //1.查询优惠卷
        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
        //2.判断秒杀是否开始
        if ( voucher.getBeginTime().isAfter(LocalDateTime.now()) ) {
//            秒杀尚未开始
            return Result.fail ("秒杀尚未开始!");
        }

        //3.判断秒杀是否已经结束
        if ( voucher.getEndTime().isBefore(LocalDateTime.now()) ) {
            return Result.fail ("秒杀已结束!");
        }
        //4.判断库存是否充足
        if ( voucher.getStock()<1 ) {
            //库存不足
            return Result.fail("库存不足");
        }
        Long userId = UserHolder.getUser().getId();
        synchronized(userId.toString().intern()) {//intern返回字符串的规范表示
            //获取代理对象(事务)事务生效
            //获取锁之后再创建事务
            IVoucherOrderService proxy = (IVoucherOrderService)AopContext.currentProxy();
            //事务提交完再释放锁
            return proxy.createVoucherOrder(voucherId);//事务能够
            //可以避免事务没提交就释放锁的安全问题
        }
    }

    @Transactional//加入事务
    public   Result createVoucherOrder(Long voucherId) {
        //TODO 6.一人一单
        Long userId = UserHolder.getUser().getId();
//        userId.toString()底层代码是创建对象,所以有可能还是不一样的
//        synchronized(userId.toString().intern()) {//intern返回字符串的规范表示


            //TODO 6.1查询订单
            Integer count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
            //TODO 6.2判断是否存在
            if ( count > 0 ) {
                //用户已经买过了
                return Result.fail("用户已经买过了一次了");
            }


            //5.扣减库存
            boolean sucess = seckillVoucherService.update()
                    .setSql("stock = stock-1")//set stock = stock-1
                    .gt("stock", "0")//可以解决超卖 where id ?and stock >0
                    .eq("voucher_id", voucherId).update();//
//                .eq("stock",voucher.getStock()).update();//where id ?and stock = ?

            if ( !sucess ) {
                //扣减不足
                return Result.fail("库存不足");

            }


            //6.创建订单
            VoucherOrder voucherOrder = new VoucherOrder();
            //6.1订单di
            long orderId = redisIdWorker.nextId("order");
            voucherOrder.setId(orderId);
            //6.2用户id
//        Long userId = UserHolder.getUser().getId();
            voucherOrder.setUserId(userId);
            //6.3代金卷id
            voucherOrder.setVoucherId(voucherId);
            save(voucherOrder);
            //7.返回订单
            return Result.ok(orderId);

    }
}

分布式锁

 

分布锁的实现

 基于Redis的分布锁

      //TODO 分布锁
        //TODO 创建锁对象
        SimpleRedisLock lock = new SimpleRedisLock("order" + userId, stringRedisTemplate);
        //获取锁
        boolean isLock = lock.tryLock(1200);
        if ( !isLock ){
            //获取锁失败,返回错误或重试
            return Result.fail("不允许重复下单");

        }
        try {
            //获取代理对象(事务)事务生效
            //TODO 获取锁之后再创建事务
            IVoucherOrderService proxy = (IVoucherOrderService)AopContext.currentProxy();
            //TODO 事务提交完再释放锁
            return proxy.createVoucherOrder(voucherId);//事务能够
            //TODO 可以避免事务没提交就释放锁的安全问题
        } finally {
            //释放锁
            lock.unlock();
        }
//        }

public class SimpleRedisLock implements ILock{

    private String name;
    private StringRedisTemplate stringRedisTemplate;

    public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {
        this.name = name;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    private static  final String KEY_PREFIX="lock:";
    @Override
    public boolean tryLock(long timeoutSec) {
       //获取线程标识
        long threadId = Thread.currentThread().getId();
        String key = KEY_PREFIX + name;
        //获取锁
        Boolean success = stringRedisTemplate.opsForValue()
                .setIfAbsent(key, threadId + "", timeoutSec, TimeUnit.SECONDS);

        return Boolean.TRUE.equals(success);
//        //防止拆箱
//        return BooleanUtil.isTrue(success);
    }

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

 

 Redis的Lua脚本

 再次改进Redis的分布锁

 

基于Redis的分布锁优化

Redisson分布式框架

 

 引入依赖

    @Resource
    private RedissonClient redissonClient;

   //TODO 分布锁
        //TODO 创建锁对象
//        SimpleRedisLock lock = new SimpleRedisLock("order" + userId, stringRedisTemplate);
//        TODO 使用工具Redisson
        RLock lock = redissonClient.getLock("lock:order" + userId);
        //获取锁
        //TODO
        boolean isLock = lock.tryLock();
        if ( !isLock ){
            //获取锁失败,返回错误或重试
            return Result.fail("不允许重复下单");

        }
        try {
            //获取代理对象(事务)事务生效
            //TODO 获取锁之后再创建事务
            IVoucherOrderService proxy = (IVoucherOrderService)AopContext.currentProxy();
            //TODO 事务提交完再释放锁
            return proxy.createVoucherOrder(voucherId);//事务能够
            //TODO 可以避免事务没提交就释放锁的安全问题
        } finally {
            //释放锁
            lock.unlock();
        }
//        }

Redisson可重入锁原理

 Redisson分布锁原理

leaseTime:释放时间

 

Redisson分布锁主从一致性问题

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于Hadoop的疫情信息分析与可视化研究——包含大屏可视化及预测算法

需要本项目的全套环境、代码、文档、资源、数据和部署调试的私信博主&#xff01;&#xff01;&#xff01; 本研究基于中国新冠疫情2020-01-11至2022-12-20的全国整体数据进行疫情大数据分析&#xff0c;通过对历史的数据进行大数据分析&#xff0c;可以有效的掌握过去疫情数据…

4、JAVA 嵌套for循环 while do-while

1 嵌套for循环 1.1 概述 存在至少2层for循环,根据外层的条件&#xff0c;判断里层能否执行 如果能执行&#xff0c;就把里层代码都循环完毕后&#xff0c;再继续判断是否执行外层循环的下一次循环 1.2 嵌套for形式 1.3 练习&#xff1a;嵌套for循环入门案例 创建包: cn.tedu…

Jupyter notebook安装运行(详解)

目录 Jupyter notebook 概念 官方文档 特点 使用Anaconda安装 使用pip安装 运行Jupyter Notebook 指定端口启动 Jupyter notebook修改主目录 Jupyter notebook 概念 Jupyter Notebook是基于网页的用于交互计算的应用程序。其可被应用于全过程计算&#xff1a;开发…

Dlib —— 对图片进行人脸检测(附源码)

效果 代码 Vs2017下使用Dlib检测人脸&#xff0c;并通过OpenCv将结果绘制出来。&#xff08;由于Dlib库已编译好&#xff0c;Vs工程环境自行搭建&#xff0c;OPenCv环境参考本人之前的专栏文章&#xff09; #include <iostream>#include <dlib/image_processing/fron…

生产环境使用HBase,你必须知道的最佳实践

需要关注的一些最佳实践经验。 **Schema设计七大原则 ** 1&#xff09;每个region的大小应该控制在10G到50G之间&#xff1b; 2&#xff09;一个表最好保持在 50到100个 region的规模&#xff1b; 3&#xff09;每个cell最大不应该超过10MB&#xff0c;如果超过&#xff0c;…

一次性供应商是否可以创建采购信息记录?

近期有读者提出这个问题。我的第一反应就是&#xff0c;为什么自己不试一下呢&#xff1f;如果不能&#xff0c;系统应该会有提示。不过反正我也好久没写了&#xff0c;找篇素材测试下&#xff0c;写写也好。 自行测试一下&#xff0c;在事务ME11中创建信息记录&#xff0c;选择…

基于java的助农在线商城的设计与实现(源码+展示视频+文档+报告)

电商助农受到了广泛的关注&#xff0c;已成为新时期农产品销售的主要发展路径。推进“互联网农业”的深入发展&#xff0c;是促进农业现代化发展的关键。助农在线商城基于Springboot框架Mysql数据库实现&#xff0c;以Jdk1.8Tomcat8为开发环境&#xff0c;实现一个基于Java开发…

【离群点检测算法】离群点|异常值 检测算法——局部离群因子LOF算法

every blog every motto: You can do more than you think. https://blog.csdn.net/weixin_39190382?typeblog 0. 前言 离群点检测&#xff0c;理解起来也比较容易。 同学都考70分&#xff0c;你也考70分&#xff0c;可以。 同学都考90分&#xff0c;你考70分&#xff0c;不…

算法笔记——排序算法

&#x1f44c;&#xff0c;begin&#xff1a; 排序算法很重要&#xff0c;它可以使数据按照一定的规律进行排序&#xff0c;各个语言的代码都有自己的排序函数&#xff0c;那么排序到底有哪几种方法&#xff0c;✌&#xff0c;如下&#xff1a; 按照效率分类如上图&#xff1a…

为什么要提前报考CSPM项目管理专业人员能力评价

2021年10月&#xff0c;中共中央、国务院发布的《国家标准化发展纲要》明确提出构建多层次从业人员培养培训体系&#xff0c;开展专业人才培养培训和国家质量基础设施综合教育。建立健全人才的职业能力评价和激励机制。由中国标准化协会&#xff08;CAS&#xff09;组织开展的项…

《移动互联网技术》第八章 消息与服务:掌握不同类型广播监听方式,以及创建通知的方法

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…

k8s中kubectl陈述式资源管理

陈述式管理资源的方法 1&#xff0c;陈述时资源管理集群资源的唯一入口是通过相应的方法调用的apiserver的接口 2&#xff0c;kubectl 是官方的ctl命令&#xff0c;用于与 apiserver 进行通信&#xff0c;将用户在命令行输入的命令&#xff0c;组织并转化为 apiserver 能识别…

基于Python+MySQL所写的智慧校园考试系统设计

点击以下链接获取源码资源&#xff1a; https://download.csdn.net/download/qq_64505944/87971718?spm1001.2014.3001.5503 《智慧校园考试系统》程序使用说明 在虚拟环境下输入命令“python manage.py runserver”启动项目&#xff0c;然后&#xff0c;访问“http://127.0.…

java之static关键字

本文是根据沉默王二前辈所发的一篇博客中所学到的 教妹学Java(二十六)&#xff1a;static 关键字解析_java中static_沉默王二的博客-CSDN博客 1&#xff1a;static关键字比较难以理解&#xff0c;借用沉默王二前辈的一句话就是&#xff1a;方便在没有创建对象的情况下进行调用…

SpirngBoot测试

一、依赖 <spring-boot.version>2.4.2</spring-boot.version> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>…

给家里装监控将录像存储到nas中

文章目录 前言一、挑选合适的摄像头1.1、了解一下摄像头1.1.1、类别1.1.2、供电 1.2、配置摄像头 二、设置nas2.1、安装surveillance station2.2、配置群晖存储摄像数据2.3、使用体验 三、其他 前言 之前的房间太小&#xff0c;端午节换到对面小区去住了&#xff0c;每月贵了2…

AirServer手机投屏电脑工具好不好用?有哪些功能优势

在日常生活和工作中&#xff0c;我们常常需要将手机屏幕投屏到电脑上&#xff0c;一般都是通过连接数据线或是电脑与手机上同时下载某款软件来实现。这样操作起来非常麻烦&#xff0c;而且无法实现多画面投屏以及跨设备投屏的需求。 AirServer是适用于Mac和PC的先进的屏幕投屏…

conda、python与人工智能学习过程中的一些基础性问题

一个不知名大学生&#xff0c;江湖人称菜狗original author: Jacky LiEmail : 3435673055qq.com Time of completion&#xff1a;2023.6.30 Last edited: 2023.6.30 目录 pip install XXX与conda install XXX的区别 conda install xxx pip install xxx 为什么要建立虚拟环境…

TinyViT: 一种高效的蒸馏方法

目录 背景方法大意快速预训练蒸馏(Fast Pretraining Distillation, FPD)如何实现快速三个细节深入理解FPD 模型架构训练trick预训练参数配置&#xff08;Imagenet21k-pretraining&#xff09;finetuning 参数配置&#xff08;Imagenet-1k&#xff09; 消融实验**Q: 数据是否越多…

mysql ——基本约束以及语法 以及 Dbeaver基本使用

1. 规约 说到约束&#xff0c;就不得不想到命名规范&#xff0c;跟java一样&#xff0c;mysql也有一套自己的命名要求 库名尽量与业务名称一致&#xff0c;比如这是一个办公系统&#xff0c;你可以命名 将数据库命名为office, 多个单词组成全小写 例如&#xff1a;officeoa 表…