免费观看又色又爽又黄的小说免费_美女福利视频国产片_亚洲欧美精品_美国一级大黄大色毛片

怎么用SpringBoot實(shí)現(xiàn)秒殺系統(tǒng)

今天小編給大家分享一下怎么用SpringBoot實(shí)現(xiàn)秒殺系統(tǒng)的相關(guān)知識(shí)點(diǎn),內(nèi)容詳細(xì),邏輯清晰,相信大部分人都還太了解這方面的知識(shí),所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來(lái)了解一下吧。

成都創(chuàng)新互聯(lián)堅(jiān)持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:成都網(wǎng)站制作、成都網(wǎng)站建設(shè)、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿足客戶于互聯(lián)網(wǎng)時(shí)代的銀川網(wǎng)站設(shè)計(jì)、移動(dòng)媒體設(shè)計(jì)的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!

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

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

  1. 制定秒殺計(jì)劃。在某天幾點(diǎn)開始,售賣什么商品,準(zhǔn)備賣多少個(gè),持續(xù)多久。

  2. 展示秒殺計(jì)劃列表。一般都是顯示當(dāng)天的,8點(diǎn)賣一些,10點(diǎn)賣一些這種。

  3. 商品詳情頁(yè)。

  4. 下單購(gòu)買。

  5. 等等

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

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

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

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

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

悲觀鎖

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

樂觀鎖

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。售賣接口組合為一個(gè)微服務(wù),order-server。

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

省略get/set

public class SecKillPlanEntity implements Serializable {
    private static final long serialVersionUID = 8866797803960607461L;

    /**
     * id
     */
    private Long id;

    /**
     * 商品id
     */
    private Long productId;

    /**
     * 商品名稱
     */
    private String productName;

    /**
     * 價(jià)格 單位:分
     */
    private Long price;

    /**
     * 劃線價(jià) 單位:分
     */
    private Long linePrice;

    /**
     * 庫(kù)存數(shù)
     */
    private Long stock;

    /**
     * 一個(gè)用戶只買一件商品標(biāo)識(shí) 0否1是
     */
    private int buyOneFlag;

    /**
     * 計(jì)劃狀態(tài) 0未提交,1已提交
     */
    private int planStatus;

    /**
     * 開始時(shí)間
     */
    private Date startTime;

    /**
     * 結(jié)束時(shí)間
     */
    private Date endTime;

    /**
     * 創(chuàng)建時(shí)間
     */
    private Date createTime;
}

說(shuō)明:

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

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

    • 一個(gè)用戶只允許購(gòu)買一件。

    • 一個(gè)用戶可以多次購(gòu)買多件。

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

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

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

@RestController
public class ProductController {

    @Resource
    private RedisTemplate<String, String> redisTemplate;

    // 隨機(jī)生成秒殺計(jì)劃設(shè)置到Redis中
    @GetMapping("/addSecKillPlan")
    @ResponseBody
    public DefaultResult<List<SecKillPlanEntity>> addSecKillPlan(@RequestParam("saledate") String saleDate) {
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        Random rand = new Random();
        Gson gson = new Gson();
        List<SecKillPlanEntity> list = Lists.newArrayList();

        for (int i = 0; i < 10; i++) {
            long productId = rand.nextInt(100) + 1;
            long price = rand.nextInt(100) + 1;
            long stock = rand.nextInt(100) + 1;

            String saleStartTime = " 10:00:00";
            String saleEndTime = " 12:00:00";
            int buyOneFlag = 0;
            if (i > 4) {
                saleStartTime = " 14:00:00";
                saleEndTime = " 16:00:00";
                buyOneFlag = 1;
            }

            SecKillPlanEntity entity = new SecKillPlanEntity();
            entity.setId(i + 1L);
            entity.setProductId(productId);
            entity.setProductName("商品" + productId);
            entity.setBuyOneFlag(buyOneFlag);
            entity.setLinePrice(999999L);
            entity.setPlanStatus(1);
            entity.setPrice(price * 100);
            entity.setStock(stock);
            entity.setEndTime(Date
                    .from(LocalDateTime.parse(saleDate + saleEndTime, dtf).atZone(ZoneId.systemDefault()).toInstant()));
            entity.setStartTime(Date.from(
                    LocalDateTime.parse(saleDate + saleStartTime, dtf).atZone(ZoneId.systemDefault()).toInstant()));
            entity.setCreateTime(new Date());

            // 商品詳情寫入Redis
            ValueOperations<String, String> setProduct = redisTemplate.opsForValue();
            setProduct.set("product_" + productId, gson.toJson(entity));
            // 寫入庫(kù)存
            if (buyOneFlag == 1) {
                // 一個(gè)用戶只買一件商品
                // 商品購(gòu)買用戶Set
                redisTemplate.opsForSet().add("product_buyers_" + productId, "");
                // 商品庫(kù)存
                for (int j = 0; j < stock; j++) {
                    redisTemplate.opsForList().leftPush("product_one_stock_" + productId, "1");
                }
            } else {
                // 用戶可買多個(gè)
                redisTemplate.opsForValue().set("product_stock_" + productId, stock + "");
            }
            list.add(entity);
            System.out.println(gson.toJson(entity));
        }
        redisTemplate.opsForValue().set("seckill_plan_" + saleDate, gson.toJson(list));

        return DefaultResult.success(list);
    }

    @GetMapping("/findSecKillPlanByDate")
    @ResponseBody
    public DefaultResult<List<SecKillPlanEntity>> findSecKillPlanByDate(@RequestParam("saledate") String saleDate) {
        Gson gson = new Gson();
        String planJson = redisTemplate.opsForValue().get("seckill_plan_" + saleDate);
        List<SecKillPlanEntity> list = gson.fromJson(planJson, new TypeToken<List<SecKillPlanEntity>>() {
        }.getType());
        // 設(shè)置新的庫(kù)存
        for (SecKillPlanEntity entity : list) {
            if (entity.getBuyOneFlag() == 1) {
                long newStock = redisTemplate.opsForList().size("product_one_stock_" + entity.getProductId());
                entity.setStock(newStock);
            } else {
                long newStock = Long
                        .parseLong(redisTemplate.opsForValue().get("product_stock_" + entity.getProductId()));
                entity.setStock(newStock);
            }
        }
        return DefaultResult.success(list);
    }
}

說(shuō)明:

  • addSecKillPlan就是隨機(jī)生成10個(gè)售賣計(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)買者,已購(gòu)買過的用戶不允許再買。

  • product_stock_商品ID,代表可售多件商品的庫(kù)存數(shù),值是庫(kù)存數(shù)。

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

LUA腳本

僅售一件buyone.lua:

--商品庫(kù)存Key product_one_stock_XXX
local stockKey = KEYS[1]
--商品購(gòu)買用戶記錄Key product_buyers_XXX
local buyersKey = KEYS[2]
--用戶ID
local uid = KEYS[3]
--校驗(yàn)用戶是否已經(jīng)購(gòu)買
local result=redis.call("sadd" , buyersKey , uid )
if(tonumber(result)==1)
then 
    --沒有購(gòu)買過,可以購(gòu)買
    local stock=redis.call("lpop" , stockKey )
    --除了nil和false,其他值都是真(包括0)
    if(stock)
    then 
        --有庫(kù)存
        return 1
    else
        --沒有庫(kù)存
        return -1
    end
else
    --已經(jīng)購(gòu)買過
    return -3
end

可售多件buymore.lua:

--商品Key
local key = KEYS[1]
--購(gòu)買數(shù)
local val = ARGV[1]
--現(xiàn)有總庫(kù)存
local stock = redis.call("GET", key)
if (tonumber(stock)<=0) 
then
    --沒有庫(kù)存
    return -1
else
    --獲取扣減后的總庫(kù)存=總庫(kù)存-購(gòu)買數(shù)
    local decrstock=redis.call("DECRBY", key, val)
    if(tonumber(decrstock)>=0)
    then
        --扣減購(gòu)買數(shù)后沒有超賣,返回現(xiàn)庫(kù)存
        return decrstock
    else
        --超賣了,把扣減的再加回去
        redis.call("INCRBY", key, val)
        return -2
    end
end

說(shuō)明:

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

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

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

售賣接口

@RestController
public class OrderController {

    @Resource
    private RedisTemplate<String, String> redisTemplate;

    @GetMapping("/addOrder")
    @ResponseBody
    public DefaultResult<Void> addOrder(@RequestParam("uid") long userId, @RequestParam("pid") long productId,
            @RequestParam("quantity") int quantity) {
        Gson gson = new Gson();
        String productJson = redisTemplate.opsForValue().get("product_" + productId);
        SecKillPlanEntity entity = gson.fromJson(productJson, SecKillPlanEntity.class);
        //TODO 要校驗(yàn)售賣計(jì)劃是否已提交,是否到了售賣時(shí)間
        long code = 0;
        if (entity.getBuyOneFlag() == 1) {
            // 用戶只買一件
            code = this.buyOne("product_one_stock_" + productId, "product_buyers_" + productId, userId);
        } else {
            // 用戶買多件
            code = this.buyMore("product_stock_" + productId, quantity);
        }
        DefaultResult<Void> result = DefaultResult.success(null);
        // 錯(cuò)誤代碼的處理應(yīng)該使用ENUM,本文就節(jié)省了
        if (code < 0) {
            result.setCode(code);
            if (code == -1) {
                result.setMsg("沒有庫(kù)存");
            } else if (code == -2) {
                result.setMsg("庫(kù)存不足");
            } else if (code == -3) {
                result.setMsg("已經(jīng)購(gòu)買過");
            }
        }
        return result;
    }

    private Long buyOne(String stockKey, String buysKey, long userId) {
        DefaultRedisScript<Long> defaultRedisScript = new DefaultRedisScript<Long>();
        defaultRedisScript.setResultType(Long.class);
        defaultRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("buyone.lua")));
        // "{pre}:"
        List<String> keys = Lists.newArrayList(stockKey, buysKey, userId + "");

        Long result = redisTemplate.execute(defaultRedisScript, keys, "");

        return result;
    }

    private Long buyMore(String stockKey, int quantity) {
        DefaultRedisScript<Long> defaultRedisScript = new DefaultRedisScript<Long>();
        defaultRedisScript.setResultType(Long.class);
        defaultRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("buymore.lua")));
        List<String> keys = Lists.newArrayList(stockKey);
        Long result = redisTemplate.execute(defaultRedisScript, keys, quantity+"");
        return result;
    }
}

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

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

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

  1. A用戶購(gòu)買僅售一件商品1,成功。

  2. A用戶再購(gòu)買僅售一件商品1,失敗。

  3. N用戶購(gòu)買僅售一件商品1,庫(kù)存不足。

  4. A用戶購(gòu)買可售多件商品2,成功。

  5. A用戶購(gòu)買可售多件商品2,庫(kù)存不足。

以上就是“怎么用SpringBoot實(shí)現(xiàn)秒殺系統(tǒng)”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會(huì)為大家更新不同的知識(shí),如果還想學(xué)習(xí)更多的知識(shí),請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。

網(wǎng)站欄目:怎么用SpringBoot實(shí)現(xiàn)秒殺系統(tǒng)
文章出自:http://m.newbst.com/article10/pjcsdo.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供App設(shè)計(jì)網(wǎng)站設(shè)計(jì)小程序開發(fā)Google商城網(wǎng)站企業(yè)網(wǎng)站制作

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

綿陽(yáng)服務(wù)器托管