Redis--day7--黑马点评--优惠券秒杀

请添加图片描述
(以下内容全部来自上述课程)
在这里插入图片描述

优惠券秒杀

1. 全局唯一ID

每个店铺都可以发布优惠券:
在这里插入图片描述

当用户抢购时,就会生成订单并保存到tb voucher order这张表中,而订单表如果使用数据库自增ID就存在一些问题:

  • id的规律性太明显
  • 受单表数据量的限制

全局ID生成器

全局ID生成器,是一种在分布式系统下用来生成全局唯一ID的工具,一般要满足下列特性:
请添加图片描述
为了增加ID的安全性,我们可以不直接使用Redis自增的数值,而是拼接一些其他信息:
请添加图片描述
ID的组成部分:

  • 符号位:1bit,永远为0
  • 时间戳:31bit,以秒为单位,可以使用69年
  • 序列号:32bit,秒内的计数器,支持每秒产生2^32个不同ID

2. Redis实现全局唯一ID

RedisIdWorker.java:

package com.hmdp.utils;import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;@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("yyyy:MM:dd"));// 2.2.自增长long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);// 3.拼接并返回return timestamp << COUNT_BITS | count;}
}

3. 添加优惠券

每个店铺都可以发布优惠券,分为平价券和特价券。平价券可以任意购买,而特价券需要秒杀抢购:
在这里插入图片描述
表关系如下:

  • tb_voucher:优惠券的基本信息,优惠金额、使用规则等
  • tb_seckill_voucher:优惠券的库存、开始抢购时间,结束抢购时间。特价优惠券才需要填写这些信息

在VoucherController中提供了一个接口,可以添加秒杀优惠券:

package com.hmdp.controller;import com.hmdp.dto.Result;
import com.hmdp.entity.Voucher;
import com.hmdp.service.IVoucherService;
import org.springframework.web.bind.annotation.*;import javax.annotation.Resource;/*** <p>*  前端控制器* </p>** @author 虎哥* @since 2021-12-22*/
@RestController
@RequestMapping("/voucher")
public class VoucherController {@Resourceprivate IVoucherService voucherService;/*** 新增普通券* @param voucher 优惠券信息* @return 优惠券id*/@PostMappingpublic Result addVoucher(@RequestBody Voucher voucher) {voucherService.save(voucher);return Result.ok(voucher.getId());}/*** 新增秒杀券* @param voucher 优惠券信息,包含秒杀信息* @return 优惠券id*/@PostMapping("seckill")public Result addSeckillVoucher(@RequestBody Voucher voucher) {voucherService.addSeckillVoucher(voucher);return Result.ok(voucher.getId());}/*** 查询店铺的优惠券列表* @param shopId 店铺id* @return 优惠券列表*/@GetMapping("/list/{shopId}")public Result queryVoucherOfShop(@PathVariable("shopId") Long shopId) {return voucherService.queryVoucherOfShop(shopId);}
}

请添加图片描述

4. 实现秒杀

下单时需要判断两点:

  • 秒杀是否开始或结束,如果尚未开始或已经结束则无法下单
  • 库存是否充足,不足则无法下单

请添加图片描述
VoucherOrderController.java:

package com.hmdp.controller;import com.hmdp.dto.Result;
import com.hmdp.service.IVoucherOrderService;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;/*** <p>*  前端控制器* </p>** @author 虎哥* @since 2021-12-22*/
@RestController
@RequestMapping("/voucher-order")
public class VoucherOrderController {@Resourceprivate IVoucherOrderService voucherOrderService;@PostMapping("seckill/{id}")public Result seckillVoucher(@PathVariable("id") Long voucherId) {return voucherOrderService.seckillVoucher(voucherId);}
}

IVoucherOrderService.java:

package com.hmdp.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.hmdp.dto.Result;
import com.hmdp.entity.VoucherOrder;public interface IVoucherOrderService extends IService<VoucherOrder> {Result seckillVoucher(Long voucherId);
}

VoucherOrderServiceImpl.java:

package com.hmdp.service.impl;import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
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.hmdp.utils.RedisIdWorker;
import com.hmdp.utils.UserHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;
import java.time.LocalDateTime;@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {@Resourceprivate ISeckillVoucherService seckillVoucherService;@Resourceprivate RedisIdWorker redisIdWorker;@Override@Transactionalpublic 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("库存不足");}//5. 扣减库存boolean success = seckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id",voucherId).update();if (!success){//扣减失败return Result.fail("库存不足");}//6. 创建订单VoucherOrder voucherOrder = new VoucherOrder();//6.1 订单idlong orderId = redisIdWorker.nextId("order");voucherOrder.setId(orderId);//6.2 用户idLong userId = UserHolder.getUser().getId();voucherOrder.setUserId(userId);//6.3 代金券idvoucherOrder.setVoucherId(voucherId);save(voucherOrder);//7. 返回订单idreturn Result.ok(orderId);}}

5. 库存超卖问题分析

请添加图片描述
超卖问题是典型的多线程安全问题,针对这一问题的常见解决方案就是加锁:
请添加图片描述
悲观锁:添加同步锁,让线程串行执行

  • 优点:简单粗暴
  • 缺点:性能一般

乐观锁:不加锁,在更新时判断是否有其他线程在修改

  • 优点:性能好
  • 缺点:存在成功率低的问题

乐观锁

乐观锁的关键是判断之前查询得到的数据是否有被修改过,常见的方式有两种:

  • 版本号法
    请添加图片描述
  • CAS法(用库存代替版本)
    请添加图片描述

6. 乐观锁解决超卖问题

//5. 扣减库存boolean success = seckillVoucherService.update().setSql("stock = stock - 1")  //set stock = stock - 1 .eq("voucher_id",voucherId).gt("stock",0) //where id = ? and stock > 0 .update();if (!success){//扣减失败return Result.fail("库存不足");}

7. 实现一人一单

需求:修改秒杀业务,要求同一个优惠券,一个用户只能下一单
请添加图片描述
目前完整代码:VoucherOrderServiceImpl.java:

package com.hmdp.service.impl;import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
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.hmdp.utils.RedisIdWorker;
import com.hmdp.utils.UserHolder;
import lombok.val;
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;
import java.time.LocalDateTime;@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {@Resourceprivate ISeckillVoucherService seckillVoucherService;@Resourceprivate RedisIdWorker redisIdWorker;@Overridepublic 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()) {//获取代理对象(事务)IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();return proxy.createVoucherOrder(voucherId);}}@Transactionalpublic synchronized Result createVoucherOrder(Long voucherId) {//5. 一人一单Long userId = UserHolder.getUser().getId();//5.1 查询订单int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();//5.2 判断是否存在if (count > 0) {//用户已经购买过了return Result.fail("用户已经购买过一次!");}//6. 扣减库存boolean success = seckillVoucherService.update().setSql("stock = stock - 1")  //set stock = stock - 1.eq("voucher_id", voucherId).gt("stock", 0) //where id = ? and stock > 0.update();if (!success) {//扣减失败return Result.fail("库存不足");}//7. 创建订单VoucherOrder voucherOrder = new VoucherOrder();//7.1 订单idlong orderId = redisIdWorker.nextId("order");voucherOrder.setId(orderId);//7.2 用户idvoucherOrder.setUserId(userId);//7.3 代金券idvoucherOrder.setVoucherId(voucherId);save(voucherOrder);//8. 返回订单idreturn Result.ok(orderId);}
}

8. 集群下的线程并发安全问题

一人一单的并发安全问题

通过加锁可以解决在单机情况下的一人一单安全问题,但是在集群模式下就不行了。

  1. 我们将服务启动两份,端口分别为8081和8082:
    在这里插入图片描述
  2. 然后修改nqinx的conf目录下的nginx.conf文件,配置反向代理和负载均衡:
    在这里插入图片描述
    现在,用户请求会在这两个节点上负载均衡,再次测试下是否存在线程安全问题。

请添加图片描述

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

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

相关文章

Vue 与 React 深度对比:设计哲学、技术差异与应用场景

一、核心设计理念对比 特性 Vue React 设计目标 渐进式框架,降低学习曲线 构建大型应用,保持灵活性 设计哲学 “约定优于配置” “配置优于约定” 核心思想 响应式数据绑定 函数式编程 + 虚拟DOM 模板语言 HTML-based 模板 JSX(JavaScript XML) 状态管理 内置响应式系统 依…

软件开发 - foreground 与 background

foreground 与 background 1、foreground词性含义n.前景&#xff1b;最突出的位置.v使突出&#xff1b;强调# 例词in the 【foreground】&#xff08;在最显眼的位置&#xff09;【foreground】 task&#xff08;前台任务&#xff09;【foreground】 color&#xff08;前景色&a…

深度学习——03 神经网络(2)-损失函数

2 损失函数 2.1 概述作用&#xff1a;衡量模型预测结果&#xff08;y^\hat{y}y^​&#xff09;和真实标签&#xff08;yyy&#xff09;的差异&#xff0c;差异越大&#xff0c;说明模型参数“质量越差”&#xff08;需要调整&#xff09;&#xff1b;本质&#xff1a;深度学习训…

【大模型微调系列-04】 神经网络基础与小项目实战

【大模型微调系列-04】 神经网络基础与小项目实战&#x1f4a1; 本章目标&#xff1a;通过构建一个能识别手写数字的AI模型&#xff0c;让你真正理解神经网络是如何"学习"的。2-3小时后&#xff0c;你将拥有第一个自己训练的AI模型&#xff01;4.1 理论讲解&#xff…

JavaWeb前端(HTML,CSS具体案例)

前言 一直在学习B站黑马程序员苍穹外卖。现在已经学的差不多了&#xff0c;但是我学习一直是针对后端开发的&#xff0c;前端也没太注重去学&#xff08;他大部分都给课程资料嘻嘻&#x1f92a;&#xff09;&#xff0c;但我还是比较感兴趣&#xff0c;准备先把之前学JavaWeb&…

核心数据结构:DataFrame

3.3.1 创建与访问什么是 DataFrame&#xff1f;DataFrame 是 Pandas 中的核心数据结构之一&#xff0c;多行多列表格数据&#xff0c;类似于 Excel 表格 或 SQL 查询结果。它是一个 二维表格结构&#xff0c;具有行索引&#xff08;index&#xff09;和列标签&#xff08;colu…

深入探索Go语言标准库 net 包中的 IP 处理

深入探索Go语言标准库 net 包中的 IP 处理 文章目录深入探索Go语言标准库 net 包中的 IP 处理引言核心知识type IP常用函数常用方法代码示例常见问题1. DNS 查询失败怎么办&#xff1f;2. 如何区分 IPv4 和 IPv6 地址&#xff1f;使用场景1. 服务器端编程2. 网络监控和调试3. 防…

2.4 双向链表

目录 引入 结构定义 结构操作 初始化 插入 删除 打印 查找 随机位置插入 随机位置删除 销毁 总结 数据结构专栏https://blog.csdn.net/xyl6716/category_13002640.html 精益求精 追求卓越 【代码仓库】&#xff1a;Code Is Here 【合作】 &#xff1a;apollomona…

开发指南132-DOM的宽度、高度属性

宽度、高度类似。这里以高度为例来说明DOM中有关高度的概念&#xff1a;1、height取法&#xff1a;element.style.height说明&#xff1a;元素内容区域的高度&#xff0c;不含padding、border、margin该属性可写2、clientHeight取法&#xff1a;element..clientHeight&#xff…

魔改chromium源码——解除 iframe 的同源策略

在进行以下操作之前,请确保已完成之前文章中提到的 源码拉取及编译 部分。 如果已顺利完成相关配置,即可继续执行后续操作。 同源策略限制了不同源(协议、域名、端口)的网页脚本访问彼此的资源。iframe 的跨域限制由 Blink 渲染引擎和 Chromium 的安全层共同实现。 咱们直…

在鸿蒙中实现深色/浅色模式切换:从原理到可运行 Demo

摘要 现在几乎所有主流应用都支持“深色模式”和“浅色模式”切换&#xff0c;这已经成了用户习惯。鸿蒙&#xff08;HarmonyOS&#xff09;同样提供了两种模式&#xff08;dark / light&#xff09;&#xff0c;并且支持应用根据系统主题切换&#xff0c;或者应用内手动切换。…

Redux搭档Next.js的简明使用教程

Redux 是一个用于 JavaScript 应用的状态管理库&#xff0c;主要解决组件间共享状态和复杂状态逻辑的问题。当应用规模较大、组件层级较深或多个组件需要共享/修改同一状态时&#xff0c;Redux 可以提供可预测、可追踪的状态管理方式&#xff0c;避免状态在组件间混乱传递。Red…

SCAI采用公平发射机制成功登陆LetsBonk,60%代币供应量已锁仓

去中心化科学&#xff08;DeSci&#xff09;平台SCAI宣布&#xff0c;其代币已于今日以Fair Launch形式在LetsBonk.fun平台成功发射。为保障资金安全与透明&#xff0c;开发团队已将代币总量的60%进行锁仓&#xff0c;进一步提升社区信任与项目合规性。SCAI是一个专注于高质量科…

【Kubernetes系列】Kubernetes中的resources

博客目录1. limits&#xff08;资源上限&#xff09;2. requests&#xff08;资源请求&#xff09;关键区别其他注意事项示例场景在 Kubernetes (k8s) 中&#xff0c;resources 用于定义容器的资源请求&#xff08;requests&#xff09;和限制&#xff08;limits&#xff09;&a…

hadoop 前端yarn 8088端口查看任务执行情况

图中资源相关参数含义及简单分析思路&#xff1a; 基础资源抢占参数 Total Resource Preempted: <memory:62112, vCores:6> 含义&#xff1a;应用总共被抢占的资源量&#xff0c; memory:62112 表示累计被收回的内存&#xff08;单位通常是MB &#xff0c;结合Hadoop生态…

基于SpringBoot的个性化教育学习平台的设计与实现(源码+lw+部署文档+讲解等)

课题介绍在教育数字化转型与学习者需求差异化的背景下&#xff0c;传统学习平台 “统一内容、统一进度” 的模式已显局限。当前&#xff0c;平台多提供标准化课程资源&#xff0c;无法根据学习者年龄、基础、目标&#xff08;如升学、技能提升&#xff09;定制学习路径&#xf…

UE5多人MOBA+GAS 48、制作闪现技能

文章目录添加标签添加GA_Blink添加标签 CRUNCH_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Ability_Blink_Teleport)CRUNCH_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Ability_Blink_Cooldown)UE_DEFINE_GAMEPLAY_TAG_COMMENT(Ability_Blink_Teleport, "Ability.Blink.Teleport"…

Swift 实战:实现一个简化版的 Twitter(LeetCode 355)

文章目录摘要描述示例解决答案设计思路题解代码分析测试示例和结果时间复杂度空间复杂度总结摘要 在社交媒体平台里&#xff0c;推送机制是核心功能之一。比如你关注了某人&#xff0c;就希望在自己的时间线上能看到他们的最新消息&#xff0c;同时自己的消息也要能出现在别人…

在浏览器端使用 xml2js 遇到的报错及解决方法

在浏览器端使用 xml2js 遇到的报错及解决方法 一、引言 在前端开发过程中&#xff0c;我们常常需要处理 XML 数据。xml2js 是一个非常流行的用于将 XML 转换为 JavaScript 对象的库。然而&#xff0c;当我们在浏览器端使用它时&#xff0c;可能会遇到一些问题。本文将介绍在浏览…

eChart饼环pie中间显示总数_2个以上0值不挤掉

<!DOCTYPE html> <html> <head><meta charset"utf-8"><title>环饼图显示总数</title><script src"https://cdn.jsdelivr.net/npm/echarts5.4.3/dist/echarts.min.js"></script><style>#main { widt…