日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

实战篇--优惠券秒杀

發布時間:2024/3/13 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 实战篇--优惠券秒杀 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

優惠券秒殺

全局唯一ID

當用戶搶購時,就會生成訂單并保存到tb_voucher_order這張表中,而訂單表如果使用數據庫自增ID就存在一些問題:

  • id的規律性太明顯
  • 受單表數據量限制

全局ID生成器,是一種在分布式系統下用來生成全局唯一ID的工具,一般要滿足以下列特性:

  • 唯一性
  • 高可用
  • 高性能
  • 遞增性
  • 安全性

ID的組成部分:

  • 符號位:1bit,永遠為0
  • 時間戳:31bit,以秒為單位,可以使用69年
  • 序列號:32bit,秒內的計數器,支持每秒產生2^32個不同ID

工具類編寫代碼實現

package com.hmdp.utils;import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component;import javax.annotation.Resource; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter;/*** @author xc* @date 2023/4/26 14:59*/ @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 end = now.toEpochSecond(ZoneOffset.UTC);long timestamp = end - BEGIN_TIMESTAMP;// 2.生成序列號// 2.1獲取當前日期,精確到天String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));Long increment = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);// 3.拼接返回return timestamp << COUNT_BITS | increment;} }

實現優惠券秒殺下單

下單時需要判斷兩點:

  • 秒殺是否開始或者結束,如果尚未開始或已經結束則無法下單
  • 庫存是否充足

流程:

根據流程實現具體業務

@Resourceprivate ISeckillVoucherService iSeckillVoucherService;@Resourceprivate IVoucherService iVoucherService;@Resourceprivate RedisIdWorker redisIdWorker;@Override@Transactionalpublic Result seckillVoucher(Long voucherId) {if (voucherId == null || voucherId < 0) {return Result.fail("請求id錯誤");}Voucher voucher = iVoucherService.getById(voucherId);if (voucher == null) {return Result.fail("當前優惠券不存在");}SeckillVoucher seckillVoucher = iSeckillVoucherService.getById(voucherId);if (seckillVoucher == null) {return Result.fail("秒殺優惠券不存在");}LocalDateTime beginTime = seckillVoucher.getBeginTime();if (LocalDateTime.now().isBefore(beginTime)) {return Result.fail("秒殺未開始");}LocalDateTime endTime = seckillVoucher.getEndTime();if (LocalDateTime.now().isAfter(endTime)) {return Result.fail("秒殺已結束");}int leftStock = seckillVoucher.getStock();if (leftStock <= 0) {return Result.fail("優惠券已被搶空");}boolean update = iSeckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).update();if (!update) {return Result.fail("服務器內部錯誤");}VoucherOrder voucherOrder = new VoucherOrder();long orderId = redisIdWorker.nextId("order");voucherOrder.setId(orderId);voucherOrder.setUserId(UserHolder.getUser().getId());voucherOrder.setVoucherId(voucherId);voucherOrder.setPayType(voucher.getType());boolean save = this.save(voucherOrder);if (!save) {return Result.fail("服務器內部錯誤");}return Result.ok(orderId);}

超賣問題

樂觀鎖

  • 版本號法

使用CAS方法:

int leftStock = seckillVoucher.getStock();if (leftStock <= 0) {return Result.fail("優惠券已被搶空");}boolean update = iSeckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId)// 更新的時候判斷當前剩余庫存量是否跟開始查詢的時候相等.eq("stock",leftStock).update();

弊端:

成功率很低

庫存改為大于0

int leftStock = seckillVoucher.getStock();if (leftStock <= 0) {return Result.fail("優惠券已被搶空");}boolean update = iSeckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId)// 更新的時候判斷當前剩余庫存量是否大于0.gt("stock",0).update();

一人一單

在搶購前判斷數據庫是否存在已經的訂單

// 查詢秒殺優惠券,該用戶是否已經搶到int count = query().eq("user_id", userId).eq("voucher_id",voucherId).count();if (count > 0) {return Result.fail("您已經搶過了");}boolean update = iSeckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).eq("stock",leftStock).update();if (!update) {return Result.fail("服務器內部錯誤");}

問題:

在多線程上會出現都走到代碼第6行,然后再一起執行更新操作,就會出現一人多單情況

解決:

對操作進行加鎖

@Overridepublic Result seckillVoucher(Long voucherId) {if (voucherId == null || voucherId < 0) {return Result.fail("請求id錯誤");}Voucher voucher = iVoucherService.getById(voucherId);if (voucher == null) {return Result.fail("當前優惠券不存在");}SeckillVoucher seckillVoucher = iSeckillVoucherService.getById(voucherId);if (seckillVoucher == null) {return Result.fail("秒殺優惠券不存在");}LocalDateTime beginTime = seckillVoucher.getBeginTime();if (LocalDateTime.now().isBefore(beginTime)) {return Result.fail("秒殺未開始");}LocalDateTime endTime = seckillVoucher.getEndTime();if (LocalDateTime.now().isAfter(endTime)) {return Result.fail("秒殺已結束");}int leftStock = seckillVoucher.getStock();if (leftStock <= 0) {return Result.fail("優惠券已被搶空");}return getResult(voucherId, voucher, leftStock);}@Transactionalpublic Result getResult(Long voucherId, Voucher voucher, int leftStock) {Long userId = UserHolder.getUser().getId();// 查詢秒殺優惠券,該用戶是否已經搶到long orderId;// 對相同用戶并發請求加鎖 new// String類型也可能出現不同對象 new // intern()的作用是返回字符串常量池中對象的地址 new synchronized (userId.toString().intern()) { int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();if (count > 0) {return Result.fail("您已經搶過了");}boolean update = iSeckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).eq("stock", leftStock).update();if (!update) {return Result.fail("服務器內部錯誤");}VoucherOrder voucherOrder = new VoucherOrder();orderId = redisIdWorker.nextId("order");voucherOrder.setId(orderId);voucherOrder.setUserId(userId);voucherOrder.setVoucherId(voucherId);voucherOrder.setPayType(voucher.getType());boolean save = this.save(voucherOrder);if (!save) {return Result.fail("服務器內部錯誤");}}return Result.ok(orderId);} }

此時還會出現一個問題:

  • 當一個線程釋放鎖后,但是事務還沒提交,那么還是會出現一人多單的情況,所以需要對整個方法調用進行加鎖
@Overridepublic Result seckillVoucher(Long voucherId) {if (voucherId == null || voucherId < 0) {return Result.fail("請求id錯誤");}Voucher voucher = iVoucherService.getById(voucherId);if (voucher == null) {return Result.fail("當前優惠券不存在");}SeckillVoucher seckillVoucher = iSeckillVoucherService.getById(voucherId);if (seckillVoucher == null) {return Result.fail("秒殺優惠券不存在");}LocalDateTime beginTime = seckillVoucher.getBeginTime();if (LocalDateTime.now().isBefore(beginTime)) {return Result.fail("秒殺未開始");}LocalDateTime endTime = seckillVoucher.getEndTime();if (LocalDateTime.now().isAfter(endTime)) {return Result.fail("秒殺已結束");}int leftStock = seckillVoucher.getStock();if (leftStock <= 0) {return Result.fail("優惠券已被搶空");}Long userId = UserHolder.getUser().getId();// new synchronized (userId.toString().intern()) {return getResult(voucherId, voucher, leftStock);}}@Transactionalpublic Result getResult(Long voucherId, Voucher voucher, int leftStock) {Long userId = UserHolder.getUser().getId();// 查詢秒殺優惠券,該用戶是否已經搶到long orderId;// 對相同用戶并發請求加鎖// String類型也可能出現不同對象// intern()的作用是返回字符串常量池中對象的地址int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();if (count > 0) {return Result.fail("您已經搶過了");}boolean update = iSeckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).eq("stock", leftStock).update();if (!update) {return Result.fail("服務器內部錯誤");}VoucherOrder voucherOrder = new VoucherOrder();orderId = redisIdWorker.nextId("order");voucherOrder.setId(orderId);voucherOrder.setUserId(userId);voucherOrder.setVoucherId(voucherId);voucherOrder.setPayType(voucher.getType());boolean save = this.save(voucherOrder);if (!save) {return Result.fail("服務器內部錯誤");}return Result.ok(orderId);}

對于spring事務管理熟悉的話,在seckillVoucher方法中調用有事務的getResult這個方法,會出現事務失效。因為相當于this.getResult,用的不是代理類。

解決方法:

@Overridepublic Result seckillVoucher(Long voucherId) {if (voucherId == null || voucherId < 0) {return Result.fail("請求id錯誤");}Voucher voucher = iVoucherService.getById(voucherId);if (voucher == null) {return Result.fail("當前優惠券不存在");}SeckillVoucher seckillVoucher = iSeckillVoucherService.getById(voucherId);if (seckillVoucher == null) {return Result.fail("秒殺優惠券不存在");}LocalDateTime beginTime = seckillVoucher.getBeginTime();if (LocalDateTime.now().isBefore(beginTime)) {return Result.fail("秒殺未開始");}LocalDateTime endTime = seckillVoucher.getEndTime();if (LocalDateTime.now().isAfter(endTime)) {return Result.fail("秒殺已結束");}int leftStock = seckillVoucher.getStock();if (leftStock <= 0) {return Result.fail("優惠券已被搶空");}Long userId = UserHolder.getUser().getId();synchronized (userId.toString().intern()) {// 獲取當前對象的代理對象 newIVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();return proxy.getResult(voucherId, voucher, leftStock);}}/*** xc* @param voucherId* @param voucher* @param leftStock* @return*/@Override@Transactionalpublic Result getResult(Long voucherId, Voucher voucher, int leftStock) {Long userId = UserHolder.getUser().getId();// 查詢秒殺優惠券,該用戶是否已經搶到long orderId;// 對相同用戶并發請求加鎖// String類型也可能出現不同對象// intern()的作用是返回字符串常量池中對象的地址int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();if (count > 0) {return Result.fail("您已經搶過了");}boolean update = iSeckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).eq("stock", leftStock).update();if (!update) {return Result.fail("服務器內部錯誤");}VoucherOrder voucherOrder = new VoucherOrder();orderId = redisIdWorker.nextId("order");voucherOrder.setId(orderId);voucherOrder.setUserId(userId);voucherOrder.setVoucherId(voucherId);voucherOrder.setPayType(voucher.getType());boolean save = this.save(voucherOrder);if (!save) {return Result.fail("服務器內部錯誤");}return Result.ok(orderId);}

需要導入aspectj的依賴

<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId></dependency>

然后在主啟動類上暴露代理對象

@MapperScan("com.hmdp.mapper") @SpringBootApplication @EnableTransactionManagement @EnableAspectJAutoProxy(exposeProxy = true) public class HmDianPingApplication {public static void main(String[] args) {SpringApplication.run(HmDianPingApplication.class, args);}}

分布式鎖

通過加鎖可以解決在單機情況下的一人一單安全問題,但是在集群模式下就不行了

模擬集群效果:

怎么添加idea的Serivces

在Service中添加SpringBoot項目,通過不同的端口啟動

# 在VM options中添加此段代碼,以指定端口啟動 -Dserver.port=8082

前端nginx通過負載均衡訪問后端接口

在集群模式下:

有多個JVM實例的存在,所以又會出現超賣問題

使用分布式鎖解決

流程:

基于Redis實現分布式鎖初級版本:

需求:定義一個類,實現下面接口,利用Redis實現分布式鎖功能。

public interface Ilock {/*** 嘗試獲取鎖* @param timeoutSec 過期時間* @return 獲取鎖是否成功*/boolean tryLock(long timeoutSec);void unlock(); }

簡單鎖實現類

package com.hmdp.utils; import org.springframework.data.redis.core.StringRedisTemplate; import java.util.concurrent.TimeUnit;/*** @author: xc* @date: 2023/4/27 20:46*/public class SimpleRedisLock implements ILock {/*** 鎖的統一前綴*/private static final String KEY_PREFIX = "lock:";/*** 鎖的名稱*/private String name;// 因為不是spring管理的bean所以需要構造方法private StringRedisTemplate stringRedisTemplate;public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {this.name = name;this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean tryLock(long timeoutSec) {// 當前線程idLong threadId = Thread.currentThread().getId();// setIfAbsent:如果不存在就設置Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadId+"", timeoutSec, TimeUnit.SECONDS);return Boolean.TRUE.equals(success);}@Overridepublic void unlock() {// 刪除keystringRedisTemplate.delete(KEY_PREFIX + name);} }

實現初級redis分布式鎖版本

Long userId = UserHolder.getUser().getId();// 因為只對同一個用戶加鎖,所以用 order:+userId 作為鎖的keySimpleRedisLock lock = new SimpleRedisLock("order:"+userId, stringRedisTemplate);// 獲取鎖boolean tryLock = lock.tryLock(LOCK_TIMEOUT);if (!tryLock) {// 獲取鎖失敗return Result.fail("不允許重復下單");}try {IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();return proxy.getResult(voucherId, voucher, leftStock);} finally {// 拿到鎖的釋放鎖lock.unlock();}

會出現的問題:

會釋放別人的鎖

解決方案:釋放鎖的時候先看一下是不是自己的鎖

流程:

改進Redis的分布式鎖:

  • 在獲取鎖時存入線程表示(可以用UUID表示)

  • 在釋放鎖時獲取線程ID,判斷是否與當前線程標示一致

    • 如果一致則釋放鎖
    • 如果不一致則不釋放鎖

修改獲取鎖和釋放鎖的邏輯

package com.hmdp.utils; import org.springframework.data.redis.core.StringRedisTemplate; import java.util.concurrent.TimeUnit; import java.util.UUID;/*** @author: xc* @date: 2023/4/27 20:46*/public class SimpleRedisLock implements ILock {/*** 鎖的統一前綴*/private static final String KEY_PREFIX = "lock:";/*** 隨機生成線程uuid的前綴*/private static final String ID_PREFIX = UUID.randomUUID() +"-";/*** 鎖的名稱*/private String name;private StringRedisTemplate stringRedisTemplate;public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {this.name = name;this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean tryLock(long timeoutSec) {long threadId = Thread.currentThread().getId();// 標識位 ID_PREFIX+threadIdBoolean success = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, ID_PREFIX+threadId, timeoutSec, TimeUnit.SECONDS);return Boolean.TRUE.equals(success);}@Overridepublic void unlock() {long threadId = Thread.currentThread().getId();// 判斷標識是否一致if ((ID_PREFIX+threadId).equals(stringRedisTemplate.opsForValue().get(KEY_PREFIX + name))) {stringRedisTemplate.delete(KEY_PREFIX + name);}} }

出現問題:

  • 線程1如果判斷完是自己的鎖后,出現gc阻塞線程知道鎖過期,此時線程2過來獲取到鎖執行自己的業務,然后線程1又阻塞完畢回到刪除鎖,就會將線程2的鎖刪除。然而又有線程3來過來獲取鎖沒獲取到,就會出現線程2和線程3同時執行代碼。

解決辦法: 保證判斷鎖和釋放鎖的原子性

使用Redis的Lua腳本:

關于redis的基本語法

執行Lua腳本

再次改進Redis的分布式鎖

總結

基于Redis的分布式鎖實現思路:

  • 利用set nx ex獲取鎖,并設置過期時間,保存線程標示
  • 釋放鎖時先判斷線程標示是否與自己一致,一致則刪除鎖

特性:

  • 利用set nx滿足互斥性
  • 利用set ex保證故障時鎖依然能釋放,避免死鎖,提高安全性
  • 利用Redis集群保證高可用和高并發特性

基于Redis的分布式鎖的優化:

基于setnx實現的分布式鎖存在下面的問題:

Redisson入門

引入依賴

<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.13.6</version></dependency>

配置Redisson客戶端

/*** @author xc* @date 2023/4/28 9:16*/ @Configuration public class RedissonConfig {public RedissonClient redissonClient() {// 配置Config config = new Config();config.setTransportMode(TransportMode.EPOLL);config.useSingleServer().setAddress("redis://127.0.0.1:6379");return Redisson.create(config);}}

使用Redisson的分布式鎖

Redisson可重入鎖的原理

流程圖:

獲取鎖Lua腳本:

釋放鎖Lua腳本:

Redisson底層源碼講解(P66、P67)

Redisson分布式鎖原理:

  • 可重入:利用hash結構記錄線程id和重入次數
  • 可重試:利用信號量和PubSub功能實現等待、喚醒、獲取鎖失敗的重試機制
  • 超時續約:利用watchDog,每個一段時間(releaseTime/3),重置超時時間

解決主從一致(P68)

Redis優化秒殺

改進秒殺業務,提高并發性能

需求:

  • 新增秒殺優惠券的同時,將優惠券信息保存到Redis中
// 引入redis@Resourceprivate StringRedisTemplate stringRedisTemplate;// 在保存秒殺優惠券的時候,也將優惠券的id和庫存保存到redis中stringRedisTemplate.opsForValue().set(SECKILL_STOCK_KEY+voucher.getId(),voucher.getStock().toString());
  • 基于Lua腳本,判斷秒殺庫存、一人一單、決定用戶是否搶購成功

lua腳本

--- --- Generated by Luanalysis --- Created by xc. --- DateTime: 2023/4/28 11:48 --- local voucherId = ARGV[1] local userId = ARGV[2]local stockKey = 'seckill:stock:' .. voucherId local orderKey = 'seckill:order:' .. voucherIdif(tonumber(redis.call('get',stockKey)) <= 0) thenreturn 1 endif(redis.call('sismember',orderKey,userId) == 1) thenreturn 2 end redis.call('incrby',stockKey,-1) redis.call('sadd',orderKey,userId) return 0

java代碼

private static final DefaultRedisScript<Long> SECKILL_SCIPT;static {SECKILL_SCIPT = new DefaultRedisScript<>();ClassPathResource pathResource = new ClassPathResource("seckill.lua");SECKILL_SCIPT.setLocation(pathResource);SECKILL_SCIPT.setResultType(Long.class);}@Overridepublic Result seckillVoucher(Long voucherId) {// 1.執行lua腳本Long userId = UserHolder.getUser().getId();long execute = stringRedisTemplate.execute(SECKILL_SCIPT,Collections.EMPTY_LIST,voucherId.toString(), userId.toString());// 2.判斷結果是否為0if (execute != 0) {// 2.1 不為0,代表沒有購買資格// 為1時庫存不足,2時重復下單return Result.fail(execute == 1 ? "庫存不足" : "重復下單");}// 2.2 為0 ,有購買資格,把下單信息保存到阻塞隊列long orderId = redisIdWorker.nextId("order"); // new ArrayBlockingQueue<>()// 3.返回訂單idreturn Result.ok(orderId);}
  • 如果搶購成功,將優惠券id和用戶id封裝后存入阻塞隊列
// 阻塞隊列private BlockingQueue<VoucherOrder> orderTasks = new ArrayBlockingQueue<>(1024 * 1024);@Overridepublic Result seckillVoucher(Long voucherId) {// 1.執行lua腳本Long userId = UserHolder.getUser().getId();long execute = stringRedisTemplate.execute(SECKILL_SCIPT,Collections.EMPTY_LIST,voucherId.toString(), userId.toString());// 2.判斷結果是否為0if (execute != 0) {// 2.1 不為0,代表沒有購買資格// 為1時庫存不足,2時重復下單return Result.fail(execute == 1 ? "庫存不足" : "重復下單");}VoucherOrder voucherOrder = new VoucherOrder();// 2.2 為0 ,有購買資格,把下單信息保存到阻塞隊列long orderId = redisIdWorker.nextId("order");voucherOrder.setId(orderId);voucherOrder.setUserId(userId);voucherOrder.setVoucherId(voucherId);// 加入到阻塞隊列 orderTasks.add(voucherOrder);proxy = (IVoucherOrderService) AopContext.currentProxy();// 3.返回訂單idreturn Result.ok(orderId);}
  • 開啟線程任務,不斷從阻塞隊列中回去信息,實現異步下單功能
private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();/*** 在類初始化完后會執行該方法*/@PostConstructprivate void init(){SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());}private class VoucherOrderHandler implements Runnable{@Overridepublic void run() {while (true){// 獲取隊列中的隊列信息try {VoucherOrder voucherOrder = orderTasks.take();handleVoucherOrder(voucherOrder);} catch (Exception e) {log.error("處理訂單異常",e);}}}private void handleVoucherOrder(VoucherOrder voucherOrder) {// 因為是全新開啟一個線程,所以需要在訂單中拿到用戶idLong userId = voucherOrder.getUserId();// 因為只對同一個用戶加鎖,所以用 order:+userId 作為鎖的keyRLock lock = redissonClient.getLock("lock:order:" + userId);boolean tryLock = lock.tryLock();if (!tryLock) {// 獲取鎖失敗log.error("不允許重復下單");return;}try {proxy.getResult(voucherOrder);} finally {// 拿到鎖的釋放鎖lock.unlock();}}}

總結

秒殺業務的優化思路是什么?

  • 先利用Redis完成庫存余量、一人一單判斷,完成搶單業務
  • 再將下單業務放入阻塞隊列,利用獨立線程異步下單

基于阻塞隊列的異步秒殺存在哪些問題?

  • 內存限制問題
  • 數據安全問題

Redis消息隊列實現異步秒殺

消息隊列,字面意思就是存放消息隊列。最簡單的消息隊列模型包括3個角色

  • 消息隊列:存儲和管理消息,也被稱為消息代理
  • 生產者:發送消息到消息隊列
  • 消費者:從消息隊列獲取消息并處理消息

基于List結構模擬消息隊列

基于List的消息隊列由哪些優缺點?

優點:

  • 利用Redis存儲,不受限于JVM內存上限
  • 基于Redis的持久化機制,數據安全性有保證
  • 可以滿足消息有序性

缺點:

  • 無法避免消息丟失
  • 只支持單消費者

基于PubSub的消息隊列

基于PubSub的消息隊列由哪些優缺點?

優點:

  • 采用發布訂閱模型,支持多生產、多消費

缺點:

  • 不支持數據持久化
  • 無法避免消息丟失
  • 消息堆積有上限,超出時數據丟失

基于Stream的消息隊列

基于STREAM的消息隊列由哪些優缺點?

優點:

  • 消息可回溯
  • 一個消息可以被多個消費者讀取
  • 可以阻塞讀取

缺點:

  • 有消息漏讀的風險

基于Stream的消息隊列-消費者組

創建消費者組:

XGROUP CREATE key groupName ID [MKSTREAM]
  • key:隊列名稱
  • groupName:消費者組名稱
  • ID:起始ID標示,$代表隊列中最后一個消息,0則代表隊列中第一個消息
  • MKSTREAM:隊列不存在時自動創建隊列

其它常見命令:

# 刪除指定的消費者組 XGROUP DESTORY key groupName# 給指定的消費者組添加消費者 XGROUP CREATECONSUMER key groupname consumername# 刪除消費者組中的指定消費者 XGROUP DELCONSUMER key groupname consumername

Stream類型消息隊列的XREADGROUP命令特點:

  • 消息可回溯
  • 可以多消費者爭搶消息,加快消費速度
  • 可以阻塞讀取
  • 沒有消息漏讀風險
  • 有消息確認機制,保證消息至少被消費一次

總結

以上是生活随笔為你收集整理的实战篇--优惠券秒杀的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。