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
英文地址,請注明出處:http://en.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…