天机学堂第11天 异步领劵

news2024/11/16 15:27:40

目录

优化思路分析

缓存数据结构

异步领券


优化思路分析

高并发写优化的几种思路

合并写请求比较适合应用在写频率较高,写数据比较简单的场景。而异步写则更适合应用在业务比较复杂,业务链较长的场景。

显然,领券业务更适合使用异步写方案。

当用户请求来领券时,不是直接领券,而是通过MQ发送一个领券消息。有一个监听器监听消息,完成领券动作

判断用户是否具有领劵资格的校验必须前置,只要发送mq 就表明肯定有领劵资格

但是,校验领券资格的部分依然会有多次数据库查询,还需要加锁。效率提升并不明显,怎么办?

为了进一步提高效率,我们可以把优惠券相关数据缓存到Redis中,这样就可以基于Redis完成资格校验,不用访问数据库,效率自然会进一步提高了。

缓存数据结构

优惠券资格校验需要校验的内容包括:

  • 优惠券发放时间

  • 优惠券库存

  • 用户限领数量

因此,为了减少对Redis内存的消耗,在构建优惠券缓存的时候,我们并不需要把所有优惠券信息写入缓存,而是只保存上述字段即可。

为了便于我们修改缓存中的库存数据,这里建议采用Hash结构,将库存作为Hash的一个字段

时间存进去只是为了取的时候方便,所以时间转换为毫秒值

String.valueOf(DateUtils.toEpochMilli(LocalDateTime.now())) 使用DateUtils.toEpochMilli转换localdatetime为毫秒值,然后使用string.valueof转化为string 存起来,因是hash,所以可以直接存入map,这样可以一次性存进去

上述结构中记录了券的每人限领数量:userLimit , 但是用户已经领取的数量并没有记录。因此,我们还需要一个数据结构,来记录某张券,每个用户领取的数量。

一个券可能被多个用户领取,每个用户的已领取数量都需要记录。显然,还是Hash结构更加适合:

优惠券的缓存该何时添加呢?

优惠券一旦发放,就可能有用户来领券,因此应该在发放优惠券的同时直接添加优惠券缓存。而暂停发放时则应该将优惠券的缓存删除,下次再次发放时重新添加。

移除缓存

redisTemplate.delete(PromotionConstants.COUPON_CACHE_KEY_PREFIX + id);

至于过期移除缓存,大家需要编写一个定时任务,定期扫描优惠券并判断是否到达过期时间。如果到达则需要将优惠券状态置为发放结束,并移除Redis缓存。

异步领券

接下来我们就可以开始实现异步领券了。分为两步:

  • 改造领券逻辑,实现基于Redis的领取资格校验,然后发送MQ消息

  • 编写MQ监听器,监听到消息后执行领券逻辑

领劵前先查询缓存,看卷是否存在,查看时间是否可以领取查看库存是否充足,然后查看是否超过个人领劵限制(让个人领劵数量+1,加一后判断是否超过了领劵限制),如果校验都通过,然后让总数量-1   increment(key,userid,-1),发送消息到队列,最后消息队列监听到更新即可(mq队列名叫什么无所谓只要交换机和key对应上就行),消费者消费消息的时候出现异常,看是否需要抛异常,因为配置文件中是否配置了重试机制?mq的stateless 一般开启事务的话 要设置为false 

 public void receiveCoupon(Long couponId) {
        Long userId = UserContext.getUser();
        String redissionkey = "lock:coupon:uid:"+userId;
        RLock lock = redissonClient.getLock(redissionkey);
        try {
            boolean isLock = lock.tryLock();
            if (!isLock) {
                throw new BizIllegalException("操作太频繁了");
            }
            // 1.查询优惠券
//        Coupon coupon = couponMapper.selectById(couponId);
            // 从redis中获取优惠卷信息
            Coupon coupon = queryCacheByCache(couponId);
            if (coupon == null) {
                throw new BadRequestException("优惠券不存在");
            }
            // 2.校验发放时间
            LocalDateTime now = LocalDateTime.now();
            if (now.isBefore(coupon.getIssueBeginTime()) || now.isAfter(coupon.getIssueEndTime())) {
                throw new BadRequestException("优惠券发放已经结束或尚未开始");
            }
            if (coupon.getTotalNum()<=0){
                throw new BadRequestException("库存不足");
            }
            // 4.校验每人限领数量
            // 4.1.查询领取数量
            String key = PromotionConstants.USER_COUPON_CACHE_KEY_PREFIX + couponId;
            // increment代表领取后的  已领数量
            Long count = redisTemplate.opsForHash().increment(key, userId.toString(), 1);
            // 4.2.校验限领数量
            if(count > coupon.getUserLimit()){
                throw new BadRequestException("超出领取数量");
            }
            // 5.扣减优惠券库存
            redisTemplate.opsForHash().increment(
                    PromotionConstants.COUPON_CACHE_KEY_PREFIX + couponId, "totalNum", -1);
            // 6.发送MQ消息
            UserCoupon uc = new UserCoupon();
            uc.setUserId(userId);
            uc.setCouponId(couponId);
            mqHelper.send(MqConstants.Exchange.PROMOTION_EXCHANGE, MqConstants.Key.COUPON_RECEIVE, uc);
        }finally {
            lock.unlock();
        }
}

  private Coupon queryCacheByCache(Long couponId) {
        // 1.准备KEY
        String key = PromotionConstants.COUPON_CACHE_KEY_PREFIX + couponId;
        Map<Object, Object> map = redisTemplate.opsForHash().entries(key);
        if (map.isEmpty()){
            return null;
        }
        //
        return BeanUtils.mapToBean(map,Coupon.class,false, CopyOptions.create());
    }

重点:

maptobean,第三个参数是否是驼峰来着??反正选false,从redis取出来的没有驼峰

redis中hgetall 对应 java中 entries 

 maptobean,

rabbitmq的nacos配置

spring:
  rabbitmq:
    host: ${tj.mq.host:192.168.150.101}
    port: ${tj.mq.port:5672}
    virtual-host: ${tj.mq.vhost:/tjxt}
    username: ${tj.mq.username:tjxt}
    password: ${tj.mq.password:123321}
    listener:
      simple:
        retry:
          enabled: ${tj.mq.listener.retry.enable:true} # 开启消费者失败重试
          initial-interval: ${tj.mq.listener.retry.interval:1000ms} # 初始的失败等待时长为1秒
          multiplier: ${tj.mq.listener.retry.multiplier:2} # 失败的等待时长倍数,下次等待时长 = multiplier * last-interval
          max-attempts: ${tj.mq.listener.retry.max-attempts:3} # 最大重试次数
          stateless: ${tj.mq.listener.retry.stateless:true} # true无状态;false有状态。如果业务中包含事务,这里改为false

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

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

相关文章

排序(直接插入,希尔,选择,快排)

文章目录 插入排序直接插入排序希尔排序 选择排序直接选择排序堆排序 交换排序冒泡排序&#xff1a;快速排序&#xff08;hoare版本&#xff09;快速排序优化&#xff08;三数取中&#xff09;快速排序优化&#xff08;小区间优化&#xff09; 快速排序&#xff08;挖坑法&…

Unity转Unreal5从入门到精通之UMG的使用

前言 UMG (Unreal Motion Graphics UI Designer)是Unreal种的可视化 UI 工具。它就类似于Unity中的UGUI,可用于为用户创建游戏内 HUD、菜单和其他与界面相关的图形。 UMG 的核心是UI控件。它可用于创建UI界面&#xff08;按钮、复选框、滑块、进度条等&#xff09;。 快速入…

HiveSQL实战——大数据开发面试高频SQL题

查询每个区域的男女用户数 0 问题描述 每个区域内男生、女生分别有多少个 1 数据准备 use wxthive; create table t1_stu_table (id int,name string,class string,sex string ); insert overwrite table t1_stu_table values(4,张文华,二区,男),(3,李思雨,一区,女),(1…

linux -- Git基础使用

git是什么 简单说来Git是一个开源的分布式版本控制系统&#xff0c;那么什么是分布式呢&#xff0c;就是每个开发者拥有完整的本地仓库副本&#xff0c;包括所有历史记录和分支&#xff0c;可以独立工作&#xff0c;并通过合并来同步变更。 git优点 速度极快 Git在合并、分…

嵌入式 - 什么是数字晶体管

What is a digital transistor? 数字晶体管是一种集成电阻器的双极晶体管。 A digital transistor is a bipolar transistor that integrates resistors. Concerning internal resistor R1 / 关于内部电阻 R1 R1 的作用是通过将输入电压转换为电流来稳定晶体管的工作。 如果…

【秋招笔试】8.17京东秋招第二场(后端岗)-三语言题解

🍭 大家好这里是 春秋招笔试突围,一起备战大厂笔试 💻 ACM金牌团队🏅️ | 多次AK大厂笔试 | 编程一对一辅导 ✨ 本系列打算持续跟新 春秋招笔试题 👏 感谢大家的订阅➕ 和 喜欢💗 和 手里的小花花🌸 ✨ 笔试合集传送们 -> 🧷春秋招笔试合集 🍒 本专栏已收…

Nginx:高效HTTP服务器与反向代理

Nginx&#xff1a;高效HTTP服务器与反向代理 1、核心特点2、应用场景 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; Nginx&#xff0c;一个开源的HTTP服务器与反向代理工具&#xff0c;因其高性能、低资源消耗而备受推崇。以下是Nginx的几…

Python 全栈系列261 使用apscheduler

说明 任务可以分为两种&#xff1a; 1 静态(存量)任务2 动态(增量)任务 对于静态任务来说&#xff0c;一般可以事先分好大小均匀的若干block任务&#xff0c;这时候比较强调使用分布式系统进行快速处理&#xff1b;对于动态任务来说&#xff0c;主要按时间区块大小划分。对于…

[JAVA] Java中如何使用throws语句声明异常类型

如果一个方法可能会出现异常&#xff0c;但没有能力处理这种异常&#xff0c;可以在方法声明处用throws子句来声明抛出异常 — throws语句用在方法定义时声明该方法要抛出的异常类型 public void method() throws Exception1,Exception2,...ExceptionN{//可能产生异常的代码 }…

LVS配置

基础介绍 http://t.csdnimg.cn/Lv5Byhttp://t.csdnimg.cn/Lv5By 部署NAT模式集群案例 实验环境 主机名 IP vip 角色 node1 192.168.0.100 172.25.254.100 调度器&#xff08; VS &#xff09; node1 192.168.0.101 &#xff0c; GW 192.168.0.100 \ 真实服务器&#…

HarmonyOS开发案例:创建全局自定义组件复用池-BuilderNode

介绍 本示例是全局自定义组件复用实现的示例代码&#xff0c;主要讲解如何通过BuilderNode创建全局的自定义组件复用池&#xff0c;实现跨页面的组件复用。 效果图预览 使用说明 继承NodeController&#xff0c;实现可复用的NodeItem组件。使用单例模式创建NodePool组件复用…

leetcode算法题之N皇后

N皇后也是一道很经典的问题&#xff0c;问题如下&#xff1a; 题目地址 按照国际象棋的规则&#xff0c;皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。 n 皇后问题 研究的是如何将 n 个皇后放置在 nn 的棋盘上&#xff0c;并且使皇后彼此之间不能相互攻击。 给你…

2:html:基础语法2

目录 2.1图像的一些注意点 2.2表格 2.2.1基本的表格 2.2.2表头与边框 2.3列表 2.3.1无序列表 2.3.2有序列表 2.4块 2.4.1块级元素 2.4.2内联元素 2.1图像的一些注意点 在上一篇中&#xff0c;我们已经知道了怎么样去将图片运用到我们的网站中&#xff0c;但是这里还…

荣耀Magicbook x14 扩容1TB固态

版权归作者所有&#xff0c;如有转发&#xff0c;请注明文章出处&#xff1a;https://cyrus-studio.github.io/blog/ 固态硬盘规格 在官网查看加装固态硬盘的接口规格 https://www.honor.com/cn/laptops/honor-magicbook-x14-2023/ https://club.honor.com/cn/thread-2847379…

XYplorer v26.30.0200绿色版

软件介绍 XYplorers是一款多标签文件管理器&#xff0c;支持多标签页栏&#xff0c;浏览文件管理时就跟使用Chrome之类的浏览器感觉一般&#xff0c;从浏览方便性&#xff0c;和切换滑顺程度&#xff0c;要比原本Windows10的Explorer文件管理器要得多。可以大部分程度上替代系…

树莓派5环境配置笔记 新建虚拟python环境—安装第三方库—配置Thonny解释器

树莓派5虚拟环境配及第三方库的安装&#x1f680; 在完成了树莓派的系统下载和各项基础配置之后进入到了&#xff0c;传感器开发部分&#xff0c;在测试传感器开发之前我打算先安装一下自己需要的库&#xff0c;但是在我直接在系统的根目录下运行pip命令的时候总会报环境错误&a…

sudo执行带重定向命令时仍提示无权限:Permission denied问题详解以及linux里的更高效下载命令mwget安装和使用效率对比

一、sudo执行带重定向命令时仍提示无权限&#xff1a;Permission denied问题详解 小问题&#xff0c;在此记录一下&#xff0c;有时在shell下执行命令重定向到文件时提示无权限-bash: temp_20181015.log: Permission denied&#xff0c;而且加sudo执行依提示无权限&#xff0c;…

CDD数据库文件制作(四)——Data Type(0x22/0x2E)

文章目录 1、新建Data Type步骤2、Data Types类型2.1 raw value2.1.1 ASCII读取和写入:零件号…2.1.2 “多字节的十六进制” 读取和写入:密钥,种子…2.1.3 “多字节的十进制” 读取和写入:参数标定和显示2.2 text table2.3 Linear2.3.1 分辨率和偏移量非1和0的读取和写入2.…

linux:进程优先级、环境变量、地址空间

进程优先级 什么叫进程优先级&#xff1f; 进程优先级是指进程获取某些资源的先后顺序 上文中的task_struct&#xff0c;也叫进程控制块&#xff08;PCB&#xff09;&#xff0c;本质上是结构体&#xff0c;我们的优先级就被写在结构体里面&#xff08;内部字段&#xff09;…

Python | Leetcode Python题解之第345题反转字符串中的元音字母

题目&#xff1a; 题解&#xff1a; class Solution:def reverseVowels(self, s: str) -> str:def isVowel(ch: str) -> bool:return ch in "aeiouAEIOU"n len(s)s list(s)i, j 0, n - 1while i < j:while i < n and not isVowel(s[i]):i 1while j …