秒杀的异步优化

news2024/11/26 21:26:19

在jvm以外的服务,不受jvm内存的限制

不仅仅做数据的存储,还保证了数据的安全,持久化

 

1.基于List结构模拟消息队列

 

优点:

利用Redis存储,不受JVM内存限制

基于Redis的持久化机制,数据安全性有保证

可以满足消息的有序性

缺点:

无法避免消息丢失

只支持单消费者

2.基于PubSub的消息队列

 

优点:

采用发布订阅模型,支持多生产、多消费

缺点:

不支持数据持久化

无法避免消息丢失

消息堆积有上限,超出时数据丢失

3.基于Stream的消息队列

 

 

STREAM类型消息队列的XREAD命令特点:

消息可回溯

一个消息可以被多个消费者读取

可以阻塞读取

有消息漏读的风险

4.基于Stream的消息队列——消费者组

 

 

命令特点:

1.消息可回溯

2.可以多消费者争抢消息,加快消费速度

3.可以阻塞读取

4没有消息漏读的风险

5.有消息确认机制,保证消息至少被消费一次

 

5.基于Redis的Stream结构作为消息队列,实现异步下单秒杀

 

@Slf4j
@SuppressWarnings("all")
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
    @Autowired
    private ISeckillVoucherService seckillVoucherService;
    @Autowired
    private RedisIdWorked redisIdWorked;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RedissonClient redissonClient;



    //创建线程池
    private static final ExecutorService SECKILL_ORDER_EXECUTOR= Executors.newSingleThreadExecutor();

    @PostConstruct//spring初始化完毕之后执行该方法
    private void init(){
        SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());
    }
    //创建线程任务
    private class VoucherOrderHandler implements Runnable{
        @Override
        public void run() {
            while (true) {
                try {
                    //1.获取消息队列中的订单信息
                    List<MapRecord<String, Object, Object>> list = stringRedisTemplate.opsForStream().read(
                            Consumer.from("g1", "c1"),
                            StreamReadOptions.empty().count(1).block(Duration.ofSeconds(2)),
                            StreamOffset.create("stream.orders", ReadOffset.lastConsumed())
                    );
                    //2.判断订单是否为空
                    if (list==null || list.isEmpty()){
                        //如果为null,说明没有消息,继续下一次循环
                        continue;
                    }
                    //解析数据
                    MapRecord<String, Object, Object> entries = list.get(0);
                    Map<Object, Object> value = entries.getValue();
                    VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(value, new VoucherOrder(), true);
                    //3.创建订单
                    handlerVoucherOreder(voucherOrder);
                    //4.确认消息
                    stringRedisTemplate.opsForStream().acknowledge("stream.orders","g1",entries.getId());
                } catch (Exception e) {
                    log.error("处理订单异常信息", e);
                    //处理异常
                    handlePendingList();
                }

            }
        }

        private void handlePendingList() {
            while (true) {
                try {
                    //1.获取pending-list中的订单信息
                    List<MapRecord<String, Object, Object>> list = stringRedisTemplate.opsForStream().read(
                            Consumer.from("g1", "c1"),
                            StreamReadOptions.empty().count(1),
                            StreamOffset.create("stream.orders", ReadOffset.from("0"))
                    );
                    //2.没有异常消息
                    if (list==null || list.isEmpty()){
                        break;
                    }
                    //解析数据
                    MapRecord<String, Object, Object> entries = list.get(0);
                    Map<Object, Object> value = entries.getValue();
                    VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(value, new VoucherOrder(), true);
                    //3.创建订单
                    handlerVoucherOreder(voucherOrder);
                    //4.确认消息
                    stringRedisTemplate.opsForStream().acknowledge("stream.orders","g1",entries.getId());
                } catch (Exception e) {
                    log.error("处理订单异常信息", e);
                    try {
                        Thread.sleep(20);
                    } catch (InterruptedException interruptedException) {
                        interruptedException.printStackTrace();
                    }
                }

            }
        }
 
    }
    private void handlerVoucherOreder(VoucherOrder voucherOrder) {
        Long userId = voucherOrder.getUserId();

        //创建锁对象
        //SimpleRedisLock lock = new SimpleRedisLock("order:"+id,stringRedisTemplate);
        RLock lock = redissonClient.getLock("lock:order"+userId);
        //尝试获取锁
        boolean flag = lock.tryLock();
        if(!flag){
            //获取锁失败
            log.error("不允许重复下单");
           return;
        }
        try {
            //如果这个类本身调用是不具备管理事务的,如果使用Spring管理可以控制事务的一致性
            //获取一个spring的代理对象 是基于ThreadLocal实现的 所以在子线程中获取不到

            //IVoucherOrderService proxy =(IVoucherOrderService) AopContext.currentProxy();
            //利用spring代理对象确保事务的一致性
            proxy.createVoucherOrder(voucherOrder);
        } finally {

            //释放锁
            lock.unlock();
        }
    }
    //加载lua脚本
    private static final DefaultRedisScript<Long> SECKILL_SCRIPT;

    static {
        SECKILL_SCRIPT = new DefaultRedisScript<>();
        SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));
        SECKILL_SCRIPT.setResultType(Long.class);
    }
    IVoucherOrderService proxy;

    @Override
    public Result seckillVoucher(Long voucherId) {
        //1.执行lua脚本
        //获取用户id
        Long userId = UserHolder.getUser().getId();
        //获得订单id
        long orderId = redisIdWorked.nextId("order");
        Long luaRes= stringRedisTemplate.execute(
                SECKILL_SCRIPT,
                Collections.emptyList(),
                voucherId.toString(), userId.toString(),String.valueOf(orderId)
        );
        //2.判断结果是否为0
        //2.1库存不足,返回1
        if(luaRes.intValue()!=0){
            return Result.fail(luaRes.intValue()==1 ?"库存不足":"一人只能下一单");
        }
        proxy = (IVoucherOrderService) AopContext.currentProxy();
        return Result.ok(orderId);
    }

   
    @Transactional
    public void createVoucherOrder(VoucherOrder voucherOrder){
        //5.保证一人一单
        //5.1用户id
        Long userId = voucherOrder.getUserId();
        Integer count = query().eq("user_id", userId).eq("voucher_id", voucherOrder.getVoucherId()).count();
        if(count>0){
            log.error("该用户已经下单");
            return ;
        }
        //6.扣减库存
        seckillVoucherService.update().setSql("stock=stock-1")
                .eq("voucher_id",voucherOrder.getVoucherId()).gt("stock",0)//stock>0
                .update();
        this.save(voucherOrder);

    }
   

}

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

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

相关文章

MQTT搭建笔记

提示&#xff1a;记录mqtt服务搭建及访问教程 文章目录 前言一、MQTT是什么&#xff1f;二、使用步骤1.MQTT服务器搭建2.MQTT集成 总结 前言 一直想了解下mqtt&#xff0c;本人又懒&#xff0c;被动型学习&#xff0c;刚好项目需要&#xff0c;此篇记录下MQTT搭建过程及心得体…

MS5147/MS5148模数转换器可pin对pin兼容ADS1247/ADS1248

‎ADS1246、ADS1247 和 ADS1248 是高度集成的精密 24 位模数转换器 &#xff08;ADC&#xff09;。这些器件具有一个板载、低噪声、可编程增益放大器 &#xff08;PGA&#xff09;、一个带有单周期建立数字滤波器的精密三角积分 &#xff08;ΔΣ&#xff09; ADC 和一个内部振…

【原创】H7-TOOL的CANFD Trace操作说明,不需要目标板额外做任何代码, 支持在线和脱机玩法(2023-05-15)

【原创】H7-TOOL的CANFD Trace操作说明&#xff0c;不需要目标板额外做任何代码, 支持在线和脱机玩法&#xff08;2023-05-15&#xff09; 【当前支持功能】 1、LUA小程序控制&#xff0c;使用灵活。 2、采用SWD接口直接访问目标板芯片的CANFD外设寄存器和CANFD RAM区实现&…

【Linux】自动化构建工具--make/Makefile调试器--gdb的使用

目录 一、自动化构建工具--make/Makefile使用原理项目清理 二、调试器--gdb的使用使用 一、自动化构建工具–make/Makefile 使用 一个工程的源文件不计其数&#xff0c;其按类型、功能、模块分别放在若干个目录中&#xff0c;makefile定义了一系列的规则来指定&#xff0c;哪…

聊个简单的话题:如何分析性能需求?

目录 前言 需求评估分析 性能测试方案 前言 前几天还在北京出差时候&#xff0c;微信群有个同学问了一个问题&#xff0c;为什么800并发压测&#xff0c;服务器还没有报错&#xff1f;当时群里其他同学提了很多观点&#xff0c;比如&#xff1a; 并发不够&#xff0c;加并…

某医院Pad网络故障分析

分析背景 某医院为了加强信息安全管理&#xff0c;防止病人隐私信息泄露&#xff0c;采用部署“零信任”安全架构设计理念的企业移动安全支撑平台方案。 但在部署前期测试时&#xff0c;遇到了严重的性能问题。 在本次测试环境中&#xff0c;通过PAD访问患者转运业务&#x…

【深入浅出 Yarn 架构与实现】6-4 Container 生命周期源码分析

本文将深入探讨 AM 向 RM 申请并获得 Container 资源后&#xff0c;在 NM 节点上如何启动和清理 Container。将详细分析整个过程的源码实现。 一、Container 生命周期介绍 Container 的启动由 ApplicationMaster 通过调用 RPC 函数 ContainerManagementProtocol#startContain…

SWLLOE

前面讲过了workman&#xff0c;现在我们再了解另外一个swoole&#xff0c;首先我们要了解swoole是个啥&#xff1f;swoole其实是一个面向生产环境的 PHP 异步网络通信引擎&#xff0c;PHP Swoole 作为网络通信框架可以使 PHP 开发人员可以编写高性能的异步并发 TCP、UDP、Unix…

Edge浏览器使用ChatGPT,新手这里看(免费快捷)

前言&#xff1a; ChatGPT受到越来越多人的关注&#xff0c;ChatGPT好不好用&#xff0c;我觉得仁者见仁、智者见智吧&#xff0c;不过首先得先使用感受一下才好回答。多数人都想体验/使用ChatGPT一波&#xff0c;但目前付费和各种渠道满天飞&#xff0c;让人很苦恼&#xff0c…

越来越多企业出现网络安全问题,是什么原因导致的?

近年来网络安全问题层出不穷&#xff0c;信息泄露、网络钓鱼、黑客攻击等问题频繁发生。 尽管有证据表明在一些全球知名的企业组织中存在价值数十亿美元的网络安全漏洞&#xff0c;但企业仍然没有认真对待网络安全。大公司在寻找各种理由来减少其网络安全预算&#xff0c;从而…

操作系统学习笔记(二)

目录 你如何理解“临界”这个词&#xff1f; 那你如何理解在计算机领域下的“临界”这个词呢&#xff1f; 如何理解计算机领域中的“同步”这个词呢&#xff1f; 你如何理解critical这个单词&#xff1f; 单标志法&#xff1a; 双标志先检查法 双标志后检查法&#xff0…

Spring Cloud Gateway路由到Amazon S3签名失败处理

Spring Cloud Gateway路由到Amazon S3签名失败处理 背景 最近在预研统一存储网关&#xff0c;想到就是使用Spring Cloud Gateway作为网关的入口&#xff0c;再反向代理到S3对象存储服务器。 软件版本 网关&#xff1a;Spring Cloud Gateway 3.1.2 s3对象存储&#xff1a;m…

语音识别 | kaggle鸟叫识别新赛赛题解析

整理自kaggle平台 赛题题目&#xff1a; BirdCLEF 2023 kaggle - 鸟声识别大赛 赛题链接&#xff1a;https://www.kaggle.com/competitions/birdclef-2023 赛题背景 鸟类是生物多样性变化的极好指标&#xff0c;因为它们具有高度流动性并且具有多样化的栖息地要求。因此物种…

Jmeter和Postman那个工具更适合做接口测试?

软件测试行业做功能测试和接口测试的人相对比较多。在测试工作中&#xff0c;有高手&#xff0c;自然也会有小白&#xff0c;但有一点我们无法否认&#xff0c;就是每一个高手都是从小白开始的&#xff0c;所以今天我们就来谈谈一大部分人在做的接口测试&#xff0c;小白变高手…

推特运营的方法

推特是一个广泛使用的社交媒体平台&#xff0c;可以通过以下方法来运营和营销&#xff1a; 建立一个完整的个人或品牌资料&#xff1a;确保你的推特资料页面清晰、有吸引力&#xff0c;并包含关键信息&#xff0c;如个人简介、网站链接和联系方式。 确定目标受众&#xff1a;…

【重制版】10分钟学会WINDOWS、MAC、LINUX如何安装GPT桌面版

文章目录 1 前言2 Windows版下载安装2.1 安装包2.2 winget下载 &#xff08;注意看&#xff0c;不是wget&#xff01;&#xff09; 3 Mac版下载安装3.1 安装包3.2 homebrew安装 4 Linux版下载安装4.1 安装包4.2 终端下载 5 特点5.1 软件特点5.2 菜单功能&#xff08;个人喜好特…

ChatGPT:【万能话术模板】+99个提示词

一&#xff1a;万能话术提示模板 【方便观看版】 【方便复制版】 现在你是一位[ ]创作者。你的任务是以[ ]为标题写一份[ ]的文章。 文章内容包含[ ] [ ] [ ]3部分。 听众是 [ ]&#xff0c;他们喜欢 [ ]&#xff0c;看重[ ]。 你的写作风…

python3 爬虫相关学习2:网页相关基础知识笔记

1 网页的构成 一般来说&#xff0c;日常看到的网站的网页的组成内容有如下 html 结构的代码css 结构的代码资源&#xff08;文字&#xff0c;图片&#xff0c;音乐&#xff0c;视频等等&#xff09; html 网页结构描述的语言 比如这种写法的文件 <html> <body> …

机器学习基础知识之分类性能评价指标

文章目录 分类性能基本概念1、准确率1、精确率2、召回率3、F1-score4、ROC曲线5、多分类问题中的相关指标6、混淆矩阵 分类性能基本概念 与预测性能评价指标相类似&#xff0c;分类性能评价指标同样也是将模型计算得出的标签值与实际的真实标签值通过数学统计上的公式进行计算…

十三、超时重试机制

目录 超时配置和重试机制 FeignClient 、Ribbon 、 Hystrix三个之间配置优先级的关系 配置常用属性 Ribbon超时和重试配置: Ribbon重试次数计算公式&#xff1a; FeignClient 超时配置&#xff1a; Hystrix超时配置&#xff1a; Hystrix超时计算公式&#xff1a; 超时配…