1. 再見,秒殺

        共 11774字,需瀏覽 24分鐘

         ·

        2022-06-30 12:50

        前言

        最近心血來潮,想起前段時間公司舉辦的線下秒殺活動不理想,想研究一下秒殺系統(tǒng)的優(yōu)化。當時活動現(xiàn)場有 200+ 會員,由于我們先前沒有經(jīng)驗,各種原因?qū)е掠脩粼诿霘⒌臅r候 APP 頁面白屏、卡死。業(yè)務(wù)部門想把手機甩我們開發(fā)臉上......當時我剛畢業(yè)也剛?cè)肼毑痪?,不敢發(fā)表意見。現(xiàn)在逐漸膨脹,是時候重新設(shè)計一套秒殺系統(tǒng)了......

        問題分析

        有經(jīng)驗的同學看到 200+ 會員都出現(xiàn)白屏、卡死,可能會覺得公司技術(shù)太 low 。其實不然,公司系統(tǒng)架構(gòu)還是很好的,大佬搭建了一套 SpringCloud 組件,都是比較新的版本。這次秒殺活動失利的確是之前沒有這樣的經(jīng)驗,很多代碼考慮沒到位,訪問數(shù)據(jù)庫的次數(shù)太多。雖然會員數(shù)是 200 ,但是會員從進入秒殺頁面,點擊秒殺商品,再到秒殺下單,這中間夸了幾個微服務(wù),對于數(shù)據(jù)庫的訪問遠遠不止 200 。

        對代碼分析之后,我們公司秒殺其實是和普通訂單是同一個流程,只是加了一個秒殺 ID 字段。下單流程在一個事務(wù)里面各種校驗、跨服務(wù)調(diào)用、加鎖扣減庫存、插入訂單商品信息、物流配送信息......等。這樣跨服務(wù)和多次訪問數(shù)據(jù)庫很明顯無法滿足秒殺業(yè)務(wù)瞬間流量巨大的特性。就像下面的圖

        上面就是我們下單的粗略流程,其實具體比這個要復(fù)雜,在每個微服務(wù)里面又調(diào)用了其他服務(wù)訪問數(shù)據(jù)庫。。。因為一個用戶至少就是一個線程,當用戶量過大線程數(shù)就很多,服務(wù)資源是有限的。當資源不夠用的時候,后來的用戶請求就會等待服務(wù)器釋放資源處理,用戶就會覺得卡。用戶一旦覺得卡,就很可能會回退頁面刷新,或者再次點擊提交訂單,然后請求又過來,又訪問數(shù)據(jù)庫,就會更卡。一次請求每多訪問一次數(shù)據(jù)庫,就需要更多的時間來處理,所以請求太多最終服務(wù)器處理不過來,前端得不到響應(yīng),用戶屏幕會卡在那白屏。

        還有一個關(guān)鍵原因是秒殺商品的查詢,也是走的數(shù)據(jù)庫,而且由于公司業(yè)務(wù)特殊性,不同地區(qū)的會員看到的商品不同等其他業(yè)務(wù),導(dǎo)致秒殺商品的查詢也比較慢,系統(tǒng)吞吐量低,最后可能導(dǎo)致用戶白屏,流程和上面下單的差不多。說到底就是要提高系統(tǒng)吞吐量,讓服務(wù)器盡快釋放資源。

        針對上述問題:在應(yīng)對秒殺系統(tǒng)這樣一瞬間的巨大流量,現(xiàn)在系統(tǒng)架構(gòu)存在的核心問題:

        • 沒有遵循服務(wù)單一原則,把秒殺功能做在訂單服務(wù)里面,萬一秒殺系統(tǒng)壓力過大還會影響到正常的訂單業(yè)務(wù)

        • 和普通訂單一樣巨大流量蜂擁而至加鎖扣減庫存,導(dǎo)致很多無效請求占用資源

        • 秒殺訂單鏈接沒有加密,給專業(yè)團隊可趁之機

        • 大量操作直接操作數(shù)據(jù)庫,頻繁磁盤 IO,系統(tǒng)吞吐量很低,甚至有可能數(shù)據(jù)庫掛掉

        以上是幾大核心問題,解決了這幾個問題,基本上就可以實現(xiàn)較好的秒殺系統(tǒng)了。下面全面分析、解決秒殺系統(tǒng)問題:

        服務(wù)單一職責

        我們都知道秒殺的特性,瞬間流量巨大。如果將秒殺功能做在訂單服務(wù)里面,萬一秒殺占用的資源過多,或者秒殺功能直接把服務(wù)搞掛,正常訂單業(yè)務(wù)也會受影響。所以秒殺要單獨部署微服務(wù)

        巨大流量處理

        秒殺一瞬間的巨大流量不僅僅有廣大用戶正常請求,還有用戶不必要的頻繁點擊、惡意用戶、惡意攻擊等。如果不做好處理很有可能請求還沒到庫存扣減那里,微服務(wù)集群就頂不住了。對于巨大的流量,采取適當?shù)南蘖鞔胧┦呛苡斜匾摹3S孟蘖鞣绞接邢旅鎺追N:

        前端限流

        我們要在巨大流量的基礎(chǔ)上去盡可能減少一些不必要的流量,活動未開始的時候前端按鈕就置灰,不讓點擊,活動開始之后限制用戶點擊按鈕的頻率。這樣可以去除正常用戶的大量不必要請求。

        Nginx 限流

        前端限流只能防止正常用戶,但是有些惡意用戶有點開發(fā)知識,通過網(wǎng)頁獲取 URL 來模擬請求,這里可以通過 Nginx 對同一 IP 做出每秒訪問次數(shù)限制。

        limit_req_zone $binary_remote_addr zone=one:10m rate=20r/s; #限制同一IP 允許訪問20次/S

        網(wǎng)關(guān)限流

        根據(jù)現(xiàn)有的網(wǎng)關(guān)集群、秒殺服務(wù)集群,估算集群能夠支撐的最大請求,然后限制流量。網(wǎng)關(guān)限流以 GateWay 為例,可以對 IP、用戶、接口限流,這里可以選擇對秒殺接口限流。常用的網(wǎng)關(guān)限流算法有,漏桶算法 和 令牌桶算法,我們選擇使用令牌桶算法限流,因為這個算法可允許突發(fā)的瞬間流量處理,GateWay 內(nèi)置了一個過濾器工廠配置 RequestRateLimiterGatewayFilterFactory 使用它即可,它需要依賴 Redis
        <dependency>    <groupId>org.springframework.boot</groupId>    <artifatId>spring-boot-starter-data-redis-reactive</artifactId></dependency>

        配置:

        server:  port: 40000spring:  cloud:    gateway:      routes:        - id: sec_kill_route          uri: lb://hosjoy-b2b-seckill          predicates:          - Path=/seckill/**          filters:          - name: RequestRateLimiter            args:              key-resolver: '#{@apiKeyResolver}'  #從Spring容器中獲取限流的Bean              redis-rate-limiter.replenishRate: 100 #令牌填充速率(每秒處理的請求)              redis-rate-limiter.burstCapacity: 3000 #令牌總?cè)萘浚?秒內(nèi)能允許的最大請求數(shù))  application:    name: hosjoy-b2b-gateway  redis:    host: localhost    port: 6379    database: 0

        代碼:

        @Beanpublic KeyResolver apiKeyResolver() {    return exchange -> Mono.just(exchange.getRequest().getPath().value());}

        如果公司有使用阿里的 Sentinel 組件,這里也可以在網(wǎng)關(guān)層使用 Sentinel 做限流,功能強大,方便監(jiān)控、熔斷、降級,使用非常方便!

        集群實例擴充

        沒有什么流量是服務(wù)實例個數(shù)解決不了的,如果有,那么就繼續(xù)加服務(wù)實例......這樣通過增加服務(wù)實例來對應(yīng)大流量也可以變相的達到限流效果 。

        應(yīng)對惡意請求

        相信大家都有所耳聞,有些 “專業(yè)團隊” ,專門通過代別人搶茅臺等商品牟利。對于這種團隊,他們不僅有多 IP ,甚至還有可能有多賬戶!就是通過各種渠道低價購買正常用戶的賬號來逃避風控系統(tǒng)。對于這樣的 “專業(yè)團隊”,單純的限流不能完全解決這個問題,你想一下,有可能發(fā)生這種情況,這些惡意的請求被處理了,搶到了商品,但是廣大用戶沒有搶到,這個問題就很嚴重。對于這種情況,我們可以采取兩種方案:

        秒殺鏈接加密

        在秒殺控制器的傳參中,我們一般會接受場次 ID、商品 ID,可以再多加一個和商品匹配的密碼參數(shù),只有密碼正確才能繼續(xù)流程,否則記錄密碼錯誤次數(shù)自增。這個密碼是在秒殺場次和商品上架的時候隨機生成,就連開發(fā)這個功能的人都不知道!
        @PostMapping("/sec-kill")    public void secKill(@RequestParam("secId") Long secId,@RequestParam("productId") Long productId,@RequestParam("password") String password){        SecProductResponse secProduct = (SecProductResponse) redisTemplate.opsForValue().get("secId:productId");//場次商品信息        if(secProduct.getPassword().equals(password)) { //校驗秒殺商品密碼            //...        } else {            stringRedisTemplate.opsForValue().increment("black:secId:productId:userId");        }}

        黑名單過濾

        在網(wǎng)關(guān)層校驗這個用戶是不是黑名單,如果是,直接結(jié)束。
        String s = stringRedisTemplate.opsForValue().get("secId:black:userId");if(s != null && Integer.parseInt(s) >= maxCount){    return;}

        防止用戶重復(fù)購買

        一般來說秒殺活動對于同一個用戶的購買是有限制的,如果已經(jīng)購買過,那么這個用戶就不應(yīng)該繼續(xù)購買。雖然前端已經(jīng)做了限制,但是為了防止專業(yè)人士,這里在后端也要進行限制。我們可以使用 Redis 來實現(xiàn)

        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent("secId:productId:userId", 1, time, TimeUnit.SECONDS);if(flag){    //...}

        超賣控制、庫存扣減

        秒殺商品發(fā)生超賣是個很可怕的事情,因為秒殺商品本身就很優(yōu)惠。為了吸引流量以極低的秒殺價格售賣,甚至虧本。如果一旦超賣,公司或者商家可能虧的血本無歸。為了控制超賣,我們可以在扣除庫存的時候加鎖。但是這里必須要加分布式鎖,使用本地鎖不能控制住。使用分布式鎖避免并發(fā)造成庫存扣減超賣。但是如此一來系統(tǒng)吞吐量會有所下降。我們原來就是跨服務(wù)扣減庫存,加分布式鎖,還訪問的數(shù)據(jù)庫,拿大腿都能想到這個對于秒殺的請求量肯定不合適。現(xiàn)在我們在秒殺接口里面雖然可以優(yōu)化到不跨服務(wù)訪問數(shù)據(jù)庫了,所以使用分布式鎖也能解決這個問題。但是既然是鎖,就有資源消耗。有沒有不使用分布式鎖的方案呢?所以我們可以換個角度考慮這個問題,秒殺商品庫存一般都是有一定數(shù)量限制的,并且秒殺庫存遠小于商品可售賣庫存。我們可以把這個秒殺庫存的數(shù)量提前保存在 Redis 里面,然后用 Redis 來預(yù)先扣減庫存,庫存一旦扣減完,就返回秒殺結(jié)束,已搶完。如此一來,我們在這里有多少庫存就會放進來多少請求,剩余的無效請求全部返回。不但防止了超賣,還做了流量限制,相對于原來的蜂擁而至排隊扣減庫存模式,這樣吞吐量極高。我們可以采用 Redis 的分布式信號量實現(xiàn),這里可以使用 Redisson 來做具體代碼實現(xiàn)

        RSemaphore semaphore = redissonClient.getSemaphore("secId:productId");boolean success=false;try {    success = semaphore.tryAcquire(1,50,TimeUnit.MILLISECONDS);} catch (InterruptedException e) {    log.error(e);    return;}if(success){    //生成訂單號    //發(fā)送消息到MQ}

        其實分布式信號量也可以算是一種分布式鎖,但是它的性能極高,獲取一次信號量幾乎是 0 - 1 ms,基本不會影響系統(tǒng)吞吐量。

        流量削峰

        經(jīng)過上面重重關(guān)卡,最后調(diào)用訂單服務(wù)的請求數(shù)和秒殺商品的庫存數(shù)量一樣。假設(shè) 100 萬人搶 400 茅臺,那么就有 400 請求要調(diào)用訂單服務(wù),400 并發(fā)下單的話,由于還有一系列業(yè)務(wù)處理,并發(fā)訪問數(shù)據(jù)庫,其實又回到了最初的模式。在秒殺接口里面訪問數(shù)據(jù)庫,這樣吞吐量是很低的,還有可能打掛數(shù)據(jù)庫。我們應(yīng)該讓秒殺接口的操作全部走 Redis 。這里我們可以使用消息隊列來做 為什么使用消息隊列?使用 MQ 來削峰,平緩消費創(chuàng)建訂單,將峰值流量散開。由于消息隊列在強大并發(fā)下可能會造成消息丟失等問題,具體可參考 RabbitMQ 可靠性、重復(fù)消費、順序性、消息積壓解決方案

        數(shù)據(jù)庫分表分庫

        一般來說以上就能實現(xiàn)較好的秒殺系統(tǒng)效果了,如果公司數(shù)據(jù)量很大,業(yè)務(wù)很復(fù)雜。甚至 MQ 異步消費訪問數(shù)據(jù)庫也不能解決的話,那么就用讀寫分離,讀庫和寫庫分開,有效降低數(shù)據(jù)庫壓力。還可以去對數(shù)據(jù)庫分表、分庫來提升單表并發(fā)能力和磁盤 IO 讀寫性能。

        解決以上問題,秒殺流程基本就 OK 了,其實上面的偽代碼都很簡單,真實實現(xiàn)的話,代碼也不復(fù)雜,只是要合理的設(shè)計方案,該屏蔽過濾的請求就屏蔽過濾,不該訪問數(shù)據(jù)庫的不訪問即可。下面具體看下這幾個環(huán)節(jié)的流程圖

        商品上架/庫存回退

        商品上架其實很簡單,我們只需要把需要的信息存入 Redis 即可。不過不同公司有不同的業(yè)務(wù),比如我公司的業(yè)務(wù) B → b → c 的模式,秒殺商品、活動是有區(qū)域的,就是說一場活動可能會發(fā)生,經(jīng)營區(qū)域在 A、B、C 三個市的會員店可以參與,其他區(qū)域的會員店不可以參與。所以針對這種情況,我們需要把秒殺場次信息在所有可允許的區(qū)域都要存儲一份,就像下面這樣

        //場次信息redisTemplate.opsForValue().set("province:cityId:secId","data");//商品信息redisTemplate.opsForValue().set("secId:productId","data");//庫存信息redisTemplate.opsForValue().set("stock:secId:productId:password","data");

        那么你可能會說,這得存多少 Redis 的 Key 啊......的確,如果場次多一點,選擇的區(qū)域多一點,是要存不少 key 。計算一下,據(jù) 2016 年統(tǒng)計,中國總共好像是 293 個市,按 300 算。假設(shè)最近三天有 30 場秒殺活動, 每場活動有 10 個商品 。那么總共需要的 key 數(shù)量的計算方法為 城市場次 + 場次商品 + 場次商品庫存 = 300 * 30 + 30 * 10 + 30 * 10 = 9600

        再按照三天內(nèi)掃描前后三天再乘個三好了,也就 30000 不到的 key。你覺得這個數(shù)量多嗎?我們來看看官方對于 Redis 存儲 key 數(shù)量給的描述:

        Redis can handle up to 2^32 keys, and was tested in practice to handle at least 250 million >keys per instance. Every hash, list, set, and sorted set, can hold 2^32 elements. In other words your limit is likely the available memory in your system.

        來源于 Redis 官網(wǎng)

        官方說 Redis 理論上能存儲 2^32 個 key ,實際測試中一個實例至少存儲 2.5 億的 key 。最后一句:你的限制其實是你系統(tǒng)的可用內(nèi)存而已......而且這還只是一個 Redis 實例的數(shù)據(jù)。所以說不要太小看 Redis ,人家官網(wǎng)聲稱性能極高,讀的速度是110000次/s,寫的速度是81000次/s 。而且,如果一個互聯(lián)網(wǎng)公司在當今緩存界對于 Redis 這么牛逼的緩存中間件的使用量很少,那么一般來說,業(yè)務(wù)用戶量是有限的。不過有一點需要注意,一旦業(yè)務(wù)大量使用 Redis 作為緩存中間件,必須至少要防止三件事 Redis 實戰(zhàn)應(yīng)用篇 — 緩存雪崩、緩存擊穿、緩存穿透和數(shù)據(jù)一致性

        因為秒殺活動有一種業(yè)務(wù)場景是沒賣完,雖然這有些尷尬......但是不得不考慮,這里需要在場次結(jié)束之后,把沒有賣完的庫存從 Redis 回退到庫存表里面。

        如上圖,配置定時任務(wù)定期掃描近三天要秒殺的場次,然后上架,注意不要重復(fù)上架。上架主要是將上圖的信息保存到 Redis,然后對于每個場次結(jié)束的商品發(fā)送延遲消息,在消費者里面判斷如果信號量不為 0,就說明秒殺活動沒有賣完,需要把庫存回退,然后刪除 Redis 中的信號量。

        秒殺商品查詢

        由于秒殺活動查詢頻繁、巨大流量,千萬不能去數(shù)據(jù)庫查詢商品信息。所有查詢操作走 Redis ,注意在活動開始之前不要返回商品密碼字段。

        這里有一點需要注意的地方,因為頁面上活動開始之前購買按鈕是置灰的,所以在秒殺開始的前一秒,需要去請求一次服務(wù)器獲取商品密碼。假設(shè)有十萬人準備搶購,那就有十萬次請求發(fā)到服務(wù)器。其實十萬次請求到是沒什么問題,因為你既然有十萬人準備搶購,就得有十萬請求要到服務(wù)器,如果你在這里覺得十萬次請求到服務(wù)器不太好,那么你的秒殺接口不是一樣要放十萬請求到服務(wù)器嗎?所以關(guān)鍵的問題不是請求數(shù)量,而是請求的錯峰。就是說你前端不能讓十萬客戶端在真正相同毫秒級別的時間把請求發(fā)過來,比如 2021-05-01 00:00:00 有一場秒殺活動,那么前端在 2021-04-30 23:59:58 或者 59 的時候就可以發(fā)請求了,但是這里要精確到毫秒去發(fā),1 s = 1000 ms ,前端可以在這 1000-2000 毫秒內(nèi)錯開十萬的請求量,這樣十萬的請求量不在同一個毫秒級別的時間,服務(wù)器壓力會小一些,而且服務(wù)器是走 Redis 查詢的,響應(yīng)時間應(yīng)該 10 - 20 ms 就可以。拿到商品密碼之后判斷當前時間是否到達秒殺開始時間,如果到了就恢復(fù)按鈕狀態(tài),如果沒到就等時間到了再恢復(fù)按鈕就行了。

        秒殺流程

        下面就是具體的秒殺流程詳細圖,按順序描述每一節(jié)點要考慮的問題以及解決方案


        秒殺流程的偽代碼:

        @Autowired    private RedisTemplate<String,Object> redisTemplate;    /**     * 秒殺流程     * */    @PostMapping("/sec-kill")    public void secKill(@RequestParam("secId") Long secId,@RequestParam("productId") Long productId,@RequestParam("password") String password){        SecResponse sec = (SecResponse) redisTemplate.opsForValue().get("secId");//場次信息        LocalDateTime now = LocalDateTime.now();        if(now.isAfter(sec.getStartTime()) && now.isBefore(sec.getEndTime())){ //校驗已開始            SecProductResponse secProduct = (SecProductResponse) redisTemplate.opsForValue().get("secId:productId");//場次商品信息            if(secProduct.getPassword().equals(password)){ //校驗秒殺商品密碼                Duration duration = Duration.between(sec.getStartTime(), sec.getEndTime());                int random = (int)(Math.random() * 100);                long period = duration.getSeconds() + random;                Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent("secId:productId:userId", "1", period, TimeUnit.SECONDS);                if(flag != null && flag){ //校驗已購買                    RSemaphore semaphore = redissonClient.getSemaphore("secId:productId:password" );                    try {                        //嘗試在50ms內(nèi)獲取信號量                        boolean acquire = semaphore.tryAcquire(num,50, TimeUnit.MILLISECONDS);                        if(acquire){ //搶到庫存                            String orderNo = generateOrderNo();//生成訂單號                            rabbitTemplate.convertAndSend("hosjoy-b2b-secKill","routingKey","data");//發(fā)送消息                        } else{                            //如果沒搶到,刪除已購買的標識(其實不刪也沒什么問題)                            stringRedisTemplate.delete("secId:productId:userId");                        }                    } catch (InterruptedException e) {}                }            } else {                stringRedisTemplate.opsForValue().increment("black:secId:productId:userId");            }        }    }


        以上就是大致的秒殺流程代碼,也是我覺得比較好的秒殺流程,設(shè)計完之后請同事大佬指點(咳咳,其實我是想裝個 X,噓!)了一下。我與他的想法或者說設(shè)計思路有主要兩點不同


        • 實現(xiàn) Redis 庫存的數(shù)據(jù)結(jié)構(gòu)

        • 什么時候算秒殺成功

        他使用的是 Redis 的 List 數(shù)據(jù)結(jié)構(gòu)來存放庫存,比如有 100 個庫存就 leftPush 100 個商品 id。然后通過 pop 的方式去扣減庫存。我對比了一下分布式信號量 Semaphore 和 List 結(jié)構(gòu),兩者都可以實現(xiàn),用起來也都很方便,還有個 incr 和 decr 自增自減其實也可以,但是這都是默認針對秒殺商品只能秒殺一件的 。如果說業(yè)務(wù)允許秒殺可以購買多件商品,那么 List 和 decr 就必須要加分布式鎖來控制了,如此一來會讓系統(tǒng)的吞吐量就相對被降低了。因為 List 一次只能彈出一個元素,decr 雖然可以傳參數(shù)扣減,但是可以減到負數(shù)的。假設(shè) A 用戶秒殺 5 件,庫存現(xiàn)在只有 4 件,B 用戶秒殺 2 件,理論上 A 是秒殺失敗,但是 B 應(yīng)該秒殺成功,如果不加分布式鎖,A 把庫存減到 -1 ,發(fā)現(xiàn)不對,要把庫存加回去,此時 B 秒殺 2 件,發(fā)現(xiàn)庫存已經(jīng)是 -1 了,也秒殺失敗,這就有問題了。所以......分布式信號量牛逼?。?/p>

        還有個區(qū)別是他有個用戶購買之后排隊的概念來校驗重復(fù)購買,我這直接 setIfAbsent 來校驗,這個區(qū)別其實無關(guān)緊要,重要的是什么時候算秒殺成功。

        我的設(shè)計思路

        以我的設(shè)計方案,只要用戶嘗試獲取信號量成功,就算秒殺成功,但是這里其實可以不用立即返回告訴用戶,最好讓用戶手機繼續(xù)轉(zhuǎn)圈 1-2 秒之后告訴他秒殺成功,因為 MQ 發(fā)送消息到消費成功有一定時間,如果立即告訴用戶秒殺成功,而訂單還在生成中,可能會給用戶帶來不好的用戶體驗。等 1-2 秒之后 MQ 消息消費完成訂單也生成成功,此時正好用戶收到秒殺成功,訂單也生成成功就很 NICE!

        那么你可能會有疑問,如果消費者生成訂單報錯了怎么辦?不得不說,這是個必須考慮的問題,畢竟 MQ 的消費說不準。這里當然我也考慮到了這種情況,如果消費失敗首先采取重試,如果重試 3 次仍然失敗,那說明這里產(chǎn)生了代碼問題導(dǎo)致訂單生成失敗,記錄下來報錯消息,然后人工查詢錯誤,恢復(fù)用戶訂單即可。畢竟這是個小概率的事情,也不會有一堆訂單消費失敗吧?更何況人家本來就是在秒殺服務(wù)搶到了庫存,既然搶到了我就算他秒殺成功了,訂單由于其他原因生成失敗,我給他手動生成訂單,保證最終一致性即可,不然怎么跟用戶交代?

        同事的設(shè)計思路

        而同事他說不應(yīng)該這么設(shè)計,應(yīng)該設(shè)計為用戶搶到信號量只是有一個秒殺機會,具體秒殺成功與否要看訂單服務(wù)消費的結(jié)果。如果訂單服務(wù)消費失敗,就回滾秒殺庫存到 Redis ,讓其他用戶來搶,因為可能會存在業(yè)務(wù)校驗不通過,用戶沒有購買資格。不得不說,他考慮問題一向很周到,我從跟他后面做項目開始到現(xiàn)在也成長很多,他真的是實力很強的大佬!

        不過我的設(shè)計初衷是沒有考慮到有業(yè)務(wù)校驗用戶沒有資格購買商品,為什么會有用戶沒有資格購買商品……這特么什么業(yè)務(wù)場景,既然沒資格買為什么要讓他看到秒殺活動?。但是仔細一想,這樣根據(jù)訂單生成結(jié)果判定秒殺結(jié)果其實是有點問題的。

        存在的問題

        • 假設(shè) 100 萬人搶 400 茅臺,本來全部搶完之后你提醒沒搶到的用戶秒殺商品已搶完了。但是訂單服務(wù)那邊消費到第 399 和 400 個消息的時候失敗了,回滾了訂單,回滾了庫存到 Redis 。如果是因為業(yè)務(wù)校驗未通過,那我認為是否應(yīng)該不讓用戶看見這個活動,或者想辦法在搶到秒殺機會之前就提示用戶沒有參加資格會比較好

        • 此時消費到第 399 和 400 消息大約過了 3-5 秒,你把它回滾了。正常用戶剛開始看沒搶到,可能都走了,這還有可能發(fā)生少賣。

        • 如果不是因為業(yè)務(wù)校驗的問題,而是代碼問題導(dǎo)致的報錯,這時回滾了訂單,感覺這個用戶有點慘啊,明明是系統(tǒng)問題,卻讓用戶背鍋......

        • 如果該用戶由于代碼問題被回滾了訂單,然后去秒殺商品頁面又看到了庫存剩余再次秒殺,然后再次失敗,再次秒殺,再次失敗......如此循環(huán)下去,我覺得他的內(nèi)心是崩潰的......,不過這個概率很小

        看到這里大伙可能會覺得,我靠這個博主太不要臉了,就挑別人的刺,不考慮自己的問題


        emmm 我怎么會是這種人呢......

        我的方案存在的問題

        • 需要有人去關(guān)注秒殺活動,雖然出錯的概率比較小,但是一旦訂單服務(wù)報錯,你得有人去盡快生成/恢復(fù)訂單,耗費人力。如果恢復(fù)了訂單,用戶最后不支付的話,那這個人力資源相當于白費了呀。。。

        • 未支付就在設(shè)計邏輯上算用戶秒殺成功,這樣可能領(lǐng)導(dǎo)聽起來不太能接受,如果先讓用戶支付,支付完成才算秒殺成功,然后去生成訂單,這樣領(lǐng)導(dǎo)應(yīng)該會很贊同......這個看起來沒問題,實際實現(xiàn)細節(jié)上有沒有問題還沒有研究過,畢竟天貓、淘寶也是先生成訂單才去支付的,等第二版更新。

        個人覺得每個人的方案都可能存在一定的局限、問題,畢竟沒有完美的方案,只能最后根據(jù)實際業(yè)務(wù)情況或者公司所有同事一起討論去選用一種更為符合的設(shè)計方案,或者在此基礎(chǔ)上再做優(yōu)化。

        瀏覽 102
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
          
          

            1. 成人片黄色 | 欧美成人一区二区三区片免费 | 男人和女人搞鸡 | 日本午夜福利 | 国产动漫 久久久精品四季影院 |