成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

基于代碼實(shí)操SpringBoot、Redis、LUA秒殺系統(tǒng)

開(kāi)發(fā) 前端 Redis
本文主要目的還是用代碼實(shí)現(xiàn)一下防止商品超賣(mài)的功能,所以像制定秒殺計(jì)劃,展示商品等功能就不著重寫(xiě)了。

[[410299]]

前言

那些吧redis基本的東西學(xué)的差不多了,卻沒(méi)有做過(guò)什么具體的項(xiàng)目實(shí)踐的,可以看看這篇文章做一個(gè)項(xiàng)目來(lái)鞏固知識(shí)。

相關(guān)需求&說(shuō)明

一般來(lái)說(shuō)秒殺系統(tǒng)的功能不會(huì)很多,有:

  1. 制定秒殺計(jì)劃。在某天幾點(diǎn)開(kāi)始,售賣(mài)什么商品,準(zhǔn)備賣(mài)多少個(gè),持續(xù)多久。
  2. 展示秒殺計(jì)劃列表。一般都是顯示當(dāng)天的,8點(diǎn)賣(mài)一些,10點(diǎn)賣(mài)一些這種。
  3. 商品詳情頁(yè)。
  4. 下單購(gòu)買(mǎi)。
  5. 等等

本文主要目的還是用代碼實(shí)現(xiàn)一下防止商品超賣(mài)的功能,所以像制定秒殺計(jì)劃,展示商品等功能就不著重寫(xiě)了。

還有電商的商品主要是SPU(例如iPhone 12,iPhone 11就是兩個(gè)SPU)及SKU(例如iPhone 12 64G 白色,iPhone 12 128G 黑色就是兩個(gè)SKU)的處理,展示的是SPU,購(gòu)買(mǎi)扣庫(kù)存的是SKU,本文為了方便,就直接用product來(lái)替代了。

下單購(gòu)買(mǎi)還會(huì)有一些前置條件,比如要經(jīng)過(guò)風(fēng)控系統(tǒng),確認(rèn)你是不是黃牛;營(yíng)銷(xiāo)系統(tǒng),有沒(méi)有相關(guān)的優(yōu)惠券,虛擬貨幣之類的。

下單完成還要走庫(kù)管、物流,還有積分之類的,本文就不涉及了。 本文不涉及數(shù)據(jù)庫(kù),一切都在Redis上操作,不過(guò)還是想說(shuō)一下數(shù)據(jù)庫(kù)與緩存數(shù)據(jù)一致性的問(wèn)題。

如果我們的系統(tǒng)并發(fā)不高,數(shù)據(jù)庫(kù)撐得住,則直接操作數(shù)據(jù)庫(kù)即可,為防止超賣(mài),可以采用:

悲觀鎖

  1. select * from SKU表 where sku_id=1 for update

樂(lè)觀鎖

  1. update SKU表 set stock=stock-1 where sku_id=1 and update_version=舊版本號(hào); 

果并發(fā)高一些,例如商品詳情頁(yè)一般并發(fā)最高,為了減少數(shù)據(jù)庫(kù)的壓力,都會(huì)使用Redis等緩存,為了保證數(shù)據(jù)庫(kù)與Redis的一致性,多是采用“修改后刪除”方案。 但是這個(gè)方案在更高并發(fā)情況下,如C10K、C10M等,在修改數(shù)據(jù)庫(kù)并刪除Redis內(nèi)容的一瞬間,大量查詢并發(fā)會(huì)傳導(dǎo)至數(shù)據(jù)庫(kù),產(chǎn)生異常。 這種情況,SPU詳情這種接口就堅(jiān)決不能與數(shù)據(jù)庫(kù)連接起來(lái)。 步驟應(yīng)該是:

  1. B端管理系統(tǒng)操作數(shù)據(jù)庫(kù)(這個(gè)并發(fā)不會(huì)高)。
  2. 數(shù)據(jù)入庫(kù)后,發(fā)送消息給MQ。
  3. 相關(guān)處理程序在接收到訂閱的MQ的Topic后,從數(shù)據(jù)庫(kù)取出信息,放入Redis。
  4. 相關(guān)服務(wù)接口只從Redis取數(shù)據(jù)。

代碼實(shí)現(xiàn)

在實(shí)際項(xiàng)目中,建議將ToC端的秒殺產(chǎn)品相關(guān)接口組合為一個(gè)微服務(wù),product-server。售賣(mài)接口組合為一個(gè)微服務(wù),order-server。可以參考之前的Spring Cloud系列文章進(jìn)行編碼,本文就簡(jiǎn)單使用了一個(gè)Spring Boot工程。

秒殺計(jì)劃實(shí)體類

省略get/set

  1. public class SecKillPlanEntity implements Serializable { 
  2.     private static final long serialVersionUID = 8866797803960607461L; 
  3.  
  4.     /** 
  5.      * id 
  6.      */ 
  7.     private Long id; 
  8.  
  9.     /** 
  10.      * 商品id 
  11.      */ 
  12.     private Long productId; 
  13.  
  14.     /** 
  15.      * 商品名稱 
  16.      */ 
  17.     private String productName; 
  18.  
  19.     /** 
  20.      * 價(jià)格 單位:分 
  21.      */ 
  22.     private Long price; 
  23.  
  24.     /** 
  25.      * 劃線價(jià) 單位:分 
  26.      */ 
  27.     private Long linePrice; 
  28.  
  29.     /** 
  30.      * 庫(kù)存數(shù) 
  31.      */ 
  32.     private Long stock; 
  33.  
  34.     /** 
  35.      * 一個(gè)用戶只買(mǎi)一件商品標(biāo)識(shí) 0否1是 
  36.      */ 
  37.     private int buyOneFlag; 
  38.  
  39.     /** 
  40.      * 計(jì)劃狀態(tài) 0未提交,1已提交 
  41.      */ 
  42.     private int planStatus; 
  43.  
  44.     /** 
  45.      * 開(kāi)始時(shí)間 
  46.      */ 
  47.     private Date startTime; 
  48.  
  49.     /** 
  50.      * 結(jié)束時(shí)間 
  51.      */ 
  52.     private Date endTime; 
  53.  
  54.     /** 
  55.      * 創(chuàng)建時(shí)間 
  56.      */ 
  57.     private Date createTime; 

說(shuō)明:

正如前文所說(shuō),秒殺的商品應(yīng)該展示的是SPU,售賣(mài)扣庫(kù)存的是SKU,本文為了方便,只用product來(lái)替代。

用戶購(gòu)買(mǎi)秒殺商品,有兩種方式:

  • 一個(gè)用戶只允許購(gòu)買(mǎi)一件。
  • 一個(gè)用戶可以多次購(gòu)買(mǎi)多件。

所以本類使用buyOneFlag做標(biāo)識(shí)。

planStatus代表本次秒殺是否真正執(zhí)行。0不展示給C端,不進(jìn)行售賣(mài);1展示給C端,進(jìn)行售賣(mài)。

添加秒殺計(jì)劃&查詢秒殺計(jì)劃

  1. @RestController 
  2. public class ProductController { 
  3.  
  4.     @Resource 
  5.     private RedisTemplate<String, String> redisTemplate; 
  6.  
  7.     // 隨機(jī)生成秒殺計(jì)劃設(shè)置到Redis中 
  8.     @GetMapping("/addSecKillPlan"
  9.     @ResponseBody 
  10.     public DefaultResult<List<SecKillPlanEntity>> addSecKillPlan(@RequestParam("saledate") String saleDate) { 
  11.         DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); 
  12.         Random rand = new Random(); 
  13.         Gson gson = new Gson(); 
  14.         List<SecKillPlanEntity> list = Lists.newArrayList(); 
  15.  
  16.         for (int i = 0; i < 10; i++) { 
  17.             long productId = rand.nextInt(100) + 1; 
  18.             long price = rand.nextInt(100) + 1; 
  19.             long stock = rand.nextInt(100) + 1; 
  20.  
  21.             String saleStartTime = " 10:00:00"
  22.             String saleEndTime = " 12:00:00"
  23.             int buyOneFlag = 0; 
  24.             if (i > 4) { 
  25.                 saleStartTime = " 14:00:00"
  26.                 saleEndTime = " 16:00:00"
  27.                 buyOneFlag = 1; 
  28.             } 
  29.  
  30.             SecKillPlanEntity entity = new SecKillPlanEntity(); 
  31.             entity.setId(i + 1L); 
  32.             entity.setProductId(productId); 
  33.             entity.setProductName("商品" + productId); 
  34.             entity.setBuyOneFlag(buyOneFlag); 
  35.             entity.setLinePrice(999999L); 
  36.             entity.setPlanStatus(1); 
  37.             entity.setPrice(price * 100); 
  38.             entity.setStock(stock); 
  39.             entity.setEndTime(Date 
  40.                     .from(LocalDateTime.parse(saleDate + saleEndTime, dtf).atZone(ZoneId.systemDefault()).toInstant())); 
  41.             entity.setStartTime(Date.from
  42.                     LocalDateTime.parse(saleDate + saleStartTime, dtf).atZone(ZoneId.systemDefault()).toInstant())); 
  43.             entity.setCreateTime(new Date()); 
  44.  
  45.             // 商品詳情寫(xiě)入Redis 
  46.             ValueOperations<String, String> setProduct = redisTemplate.opsForValue(); 
  47.             setProduct.set("product_" + productId, gson.toJson(entity)); 
  48.             // 寫(xiě)入庫(kù)存 
  49.             if (buyOneFlag == 1) { 
  50.                 // 一個(gè)用戶只買(mǎi)一件商品 
  51.                 // 商品購(gòu)買(mǎi)用戶Set 
  52.                 redisTemplate.opsForSet().add("product_buyers_" + productId, ""); 
  53.                 // 商品庫(kù)存 
  54.                 for (int j = 0; j < stock; j++) { 
  55.                     redisTemplate.opsForList().leftPush("product_one_stock_" + productId, "1"); 
  56.                 } 
  57.             } else { 
  58.                 // 用戶可買(mǎi)多個(gè) 
  59.                 redisTemplate.opsForValue().set("product_stock_" + productId, stock + ""); 
  60.             } 
  61.             list.add(entity); 
  62.             System.out.println(gson.toJson(entity)); 
  63.         } 
  64.         redisTemplate.opsForValue().set("seckill_plan_" + saleDate, gson.toJson(list)); 
  65.  
  66.         return DefaultResult.success(list); 
  67.     } 
  68.  
  69.     @GetMapping("/findSecKillPlanByDate"
  70.     @ResponseBody 
  71.     public DefaultResult<List<SecKillPlanEntity>> findSecKillPlanByDate(@RequestParam("saledate") String saleDate) { 
  72.         Gson gson = new Gson(); 
  73.         String planJson = redisTemplate.opsForValue().get("seckill_plan_" + saleDate); 
  74.         List<SecKillPlanEntity> list = gson.fromJson(planJson, new TypeToken<List<SecKillPlanEntity>>() { 
  75.         }.getType()); 
  76.         // 設(shè)置新的庫(kù)存 
  77.         for (SecKillPlanEntity entity : list) { 
  78.             if (entity.getBuyOneFlag() == 1) { 
  79.                 long newStock = redisTemplate.opsForList().size("product_one_stock_" + entity.getProductId()); 
  80.                 entity.setStock(newStock); 
  81.             } else { 
  82.                 long newStock = Long 
  83.                         .parseLong(redisTemplate.opsForValue().get("product_stock_" + entity.getProductId())); 
  84.                 entity.setStock(newStock); 
  85.             } 
  86.         } 
  87.         return DefaultResult.success(list); 
  88.     } 

 說(shuō)明:

  • addSecKillPlan就是隨機(jī)生成10個(gè)售賣(mài)計(jì)劃,有僅售一件的,也有售多件的。并將相關(guān)數(shù)據(jù)壓入Redis。
  • seckill_plan_日期,代表某日的所有秒殺計(jì)劃,列表展示用。
  • product_商品ID,代表某商品信息,詳情頁(yè)使用。
  • product_one_stock_商品ID,代表僅售一件商品的庫(kù)存數(shù),值是List,有多少庫(kù)存,就往里面push多少個(gè)“1”。
  • product_buyers_商品ID,代表僅售一件商品的購(gòu)買(mǎi)者,已購(gòu)買(mǎi)過(guò)的用戶不允許再買(mǎi)。
  • product_stock_商品ID,代表可售多件商品的庫(kù)存數(shù),值是庫(kù)存數(shù)。

findSecKillPlanByDate,展示某日秒殺售賣(mài)計(jì)劃。庫(kù)存數(shù)從庫(kù)存相關(guān)的兩個(gè)KEY取。

LUA腳本

僅售一件buyone.lua:

  1. --商品庫(kù)存Key product_one_stock_XXX 
  2. local stockKey = KEYS[1] 
  3. --商品購(gòu)買(mǎi)用戶記錄Key product_buyers_XXX 
  4. local buyersKey = KEYS[2] 
  5. --用戶ID 
  6. local uid = KEYS[3] 
  7. --校驗(yàn)用戶是否已經(jīng)購(gòu)買(mǎi) 
  8. local result=redis.call("sadd" , buyersKey , uid ) 
  9. if(tonumber(result)==1) 
  10. then  
  11.     --沒(méi)有購(gòu)買(mǎi)過(guò),可以購(gòu)買(mǎi) 
  12.     local stock=redis.call("lpop" , stockKey ) 
  13.     --除了nil和false,其他值都是真(包括0) 
  14.     if(stock) 
  15.     then  
  16.         --有庫(kù)存 
  17.         return 1 
  18.     else 
  19.         --沒(méi)有庫(kù)存 
  20.         return -1 
  21.     end 
  22. else 
  23.     --已經(jīng)購(gòu)買(mǎi)過(guò) 
  24.     return -3 
  25. end 

 可售多件buymore.lua:

  1. --商品Key 
  2. local key = KEYS[1] 
  3. --購(gòu)買(mǎi)數(shù) 
  4. local val = ARGV[1] 
  5. --現(xiàn)有總庫(kù)存 
  6. local stock = redis.call("GET"key
  7. if (tonumber(stock)<=0)  
  8. then 
  9.     --沒(méi)有庫(kù)存 
  10.     return -1 
  11. else 
  12.     --獲取扣減后的總庫(kù)存=總庫(kù)存-購(gòu)買(mǎi)數(shù) 
  13.     local decrstock=redis.call("DECRBY"key, val) 
  14.     if(tonumber(decrstock)>=0) 
  15.     then 
  16.         --扣減購(gòu)買(mǎi)數(shù)后沒(méi)有超賣(mài),返回現(xiàn)庫(kù)存 
  17.         return decrstock 
  18.     else 
  19.         --超賣(mài)了,把扣減的再加回去 
  20.         redis.call("INCRBY"key, val) 
  21.         return -2 
  22.     end 
  23. end 

說(shuō)明:

1、僅售一件。先把購(gòu)買(mǎi)者的ID用命令“sadd”進(jìn)product_buyers_商品ID,如果返回1,代表此用戶之前沒(méi)有購(gòu)買(mǎi)過(guò),否則返回-3,已經(jīng)購(gòu)買(mǎi)過(guò)。

  • 在從product_one_stock_商品ID中l(wèi)pop出數(shù)值,如果還有庫(kù)存,必會(huì)返回1,有庫(kù)存,否則就是nil,無(wú)庫(kù)存。

2.、可售多件。之前講過(guò),不再描述。 將兩個(gè)lua文件,放在Spring Boot工程的resources目錄下。

售賣(mài)接口

  1. @RestController 
  2. public class OrderController { 
  3.  
  4.     @Resource 
  5.     private RedisTemplate<String, String> redisTemplate; 
  6.  
  7.     @GetMapping("/addOrder"
  8.     @ResponseBody 
  9.     public DefaultResult<Void> addOrder(@RequestParam("uid") long userId, @RequestParam("pid") long productId, 
  10.             @RequestParam("quantity"int quantity) { 
  11.         Gson gson = new Gson(); 
  12.         String productJson = redisTemplate.opsForValue().get("product_" + productId); 
  13.         SecKillPlanEntity entity = gson.fromJson(productJson, SecKillPlanEntity.class); 
  14.         //TODO 要校驗(yàn)售賣(mài)計(jì)劃是否已提交,是否到了售賣(mài)時(shí)間 
  15.         long code = 0; 
  16.         if (entity.getBuyOneFlag() == 1) { 
  17.             // 用戶只買(mǎi)一件 
  18.             code = this.buyOne("product_one_stock_" + productId, "product_buyers_" + productId, userId); 
  19.         } else { 
  20.             // 用戶買(mǎi)多件 
  21.             code = this.buyMore("product_stock_" + productId, quantity); 
  22.         } 
  23.         DefaultResult<Void> result = DefaultResult.success(null); 
  24.         // 錯(cuò)誤代碼的處理應(yīng)該使用ENUM,本文就節(jié)省了 
  25.         if (code < 0) { 
  26.             result.setCode(code); 
  27.             if (code == -1) { 
  28.                 result.setMsg("沒(méi)有庫(kù)存"); 
  29.             } else if (code == -2) { 
  30.                 result.setMsg("庫(kù)存不足"); 
  31.             } else if (code == -3) { 
  32.                 result.setMsg("已經(jīng)購(gòu)買(mǎi)過(guò)"); 
  33.             } 
  34.         } 
  35.         return result; 
  36.     } 
  37.  
  38.     private Long buyOne(String stockKey, String buysKey, long userId) { 
  39.         DefaultRedisScript<Long> defaultRedisScript = new DefaultRedisScript<Long>(); 
  40.         defaultRedisScript.setResultType(Long.class); 
  41.         defaultRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("buyone.lua"))); 
  42.         // "{pre}:" 
  43.         List<String> keys = Lists.newArrayList(stockKey, buysKey, userId + ""); 
  44.  
  45.         Long result = redisTemplate.execute(defaultRedisScript, keys, ""); 
  46.  
  47.         return result; 
  48.     } 
  49.  
  50.     private Long buyMore(String stockKey, int quantity) { 
  51.         DefaultRedisScript<Long> defaultRedisScript = new DefaultRedisScript<Long>(); 
  52.         defaultRedisScript.setResultType(Long.class); 
  53.         defaultRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("buymore.lua"))); 
  54.         List<String> keys = Lists.newArrayList(stockKey); 
  55.         Long result = redisTemplate.execute(defaultRedisScript, keys, quantity+""); 
  56.         return result; 
  57.     } 

 說(shuō)明: 1、主要看buyOne、buyMore兩個(gè)私有方法,里面寫(xiě)的是如何使用RedisTemplate執(zhí)行l(wèi)ua腳本。

另外我看有資料說(shuō)如果使用的是Redis集群,則會(huì)報(bào)錯(cuò),因?yàn)槲覜](méi)有Redis的集群環(huán)境,所以也沒(méi)法測(cè)試,大家有環(huán)境的可以試一試。

2、addOrder有一些代碼為了節(jié)省時(shí)間,就寫(xiě)得很low了,比如一些校驗(yàn)沒(méi)有加,錯(cuò)誤碼應(yīng)該使用ENUM等。 測(cè)試用例:

  1. A用戶購(gòu)買(mǎi)僅售一件商品1,成功。
  2. A用戶再購(gòu)買(mǎi)僅售一件商品1,失敗。
  3. N用戶購(gòu)買(mǎi)僅售一件商品1,庫(kù)存不足。
  4. A用戶購(gòu)買(mǎi)可售多件商品2,成功。
  5. A用戶購(gòu)買(mǎi)可售多件商品2,庫(kù)存不足。

 

責(zé)任編輯:姜華 來(lái)源: 今日頭條
相關(guān)推薦

2024-04-28 10:52:25

CentOS系統(tǒng)RHEL系統(tǒng)

2020-08-04 07:47:59

代碼模板模式

2019-07-23 13:32:13

Java開(kāi)發(fā)代碼

2009-12-23 17:22:18

Linux系統(tǒng)rsyn

2020-04-01 17:31:03

Redis系統(tǒng)秒殺

2010-04-29 12:23:58

Oracle 獲取系統(tǒng)

2010-01-06 10:38:16

Linux安裝JDK

2010-04-12 09:36:29

Oacle merge

2010-05-04 14:10:53

Oracle表

2020-09-01 07:47:32

Redis秒殺微信

2010-04-09 10:13:13

Oracle數(shù)據(jù)字典

2010-04-15 14:18:30

Oracle創(chuàng)建

2010-05-10 17:00:53

Oracle死鎖進(jìn)程

2021-09-22 15:36:31

勒索軟件攻擊數(shù)據(jù)泄露

2010-04-13 14:00:00

Oracle inse

2009-12-01 18:03:56

Linux版本

2010-04-27 10:25:28

Oracle Subs

2010-05-18 12:24:16

MySQL binlo

2010-05-18 18:19:40

MySQL修改表結(jié)構(gòu)

2010-05-20 15:53:15

配置MySQL
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 国产成人免费视频网站高清观看视频 | 精品国产欧美一区二区 | 久草在线中文888 | 日韩欧美一区二区三区免费观看 | 91精品国产一区二区三区香蕉 | 免费a国产 | 九九精品在线 | 天堂在线91 | 中文在线a在线 | 最新av片| 天天影视综合 | 91成人在线 | 日韩成人性视频 | 色狠狠一区 | 美国一级黄色片 | 免费国产视频在线观看 | 亚洲精品一区av在线播放 | 在线看黄免费 | 免费看一区二区三区 | 伊人99 | 一区二区三区精品视频 | 日韩在线电影 | 国产欧美精品一区二区色综合 | 国产精品精品视频一区二区三区 | 国产精品久久久久久久久 | 久久综合九九 | 国产高清精品在线 | 日本一区二区三区在线观看 | 免费在线观看成年人视频 | 亚洲精品成人av久久 | 精品欧美一区二区精品久久久 | 日韩中文一区二区三区 | 日韩精品免费视频 | 国产一区二区三区视频免费观看 | 日韩不卡在线 | 日韩一级一区 | 亚洲欧美日本在线 | 97av视频| 成人精品一区二区户外勾搭野战 | 久久成人久久 | 亚洲国产成人精品久久 |