国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

高并發下秒殺商品,必須知道的9個細節

VincentFF / 1393人閱讀

摘要:但很顯然這些請求的處理性能并不好,有沒有更好的解決方案這時可以想到布隆過濾器。系統根據商品,先從布隆過濾器中查詢該是否存在,如果存在則允許從緩存中查詢數據,如果不存在,則直接返回失敗。所以布隆過濾器絕大部分使用在緩存數據更新很少的場景中。

高并發下如何設計秒殺系統?這是一個高頻面試題。這個問題看似簡單,但是里面的水很深,它考查的是高并發場景下,從前端到后端多方面的知識。

秒殺一般出現在商城的促銷活動中,指定了一定數量(比如:10個)的商品(比如:手機),以極低的價格(比如:0.1元),讓大量用戶參與活動,但只有極少數用戶能夠購買成功。這類活動商家絕大部分是不賺錢的,說白了是找個噱頭宣傳自己。

雖說秒殺只是一個促銷活動,但對技術要求不低。下面給大家總結一下設計秒殺系統需要注意的9個細節。

一、瞬時高并發

一般在秒殺時間點(比如:12點)前幾分鐘,用戶并發量才真正突增,達到秒殺時間點時,并發量會達到頂峰。

但由于這類活動是大量用戶搶少量商品的場景,必定會出現狼多肉少的情況,所以其實絕大部分用戶秒殺會失敗,只有極少部分用戶能夠成功。

正常情況下,大部分用戶會收到商品已經搶完的提醒,收到該提醒后,他們大概率不會在那個活動頁面停留了,如此一來,用戶并發量又會急劇下降。所以這個峰值持續的時間其實是非常短的,這樣就會出現瞬時高并發的情況,下面用一張圖直觀的感受一下流量的變化:

像這種瞬時高并發的場景,傳統的系統很難應對,我們需要設計一套全新的系統。可以從以下幾個方面入手:

  • 頁面靜態化
  • CDN加速
  • 緩存
  • mq異步處理
  • 限流
  • 分布式鎖
?

二、頁面靜態化

活動頁面是用戶流量的第一入口,所以是并發量最大的地方。

如果這些流量都能直接訪問服務端,恐怕服務端會因為承受不住這么大的壓力,而直接掛掉。

活動頁面絕大多數內容是固定的,比如:商品名稱、商品描述、圖片等。為了減少不必要的服務端請求,通常情況下,會對活動頁面做靜態化處理。用戶瀏覽商品等常規操作,并不會請求到服務端。只有到了秒殺時間點,并且用戶主動點了秒殺按鈕才允許訪問服務端。

這樣能過濾大部分無效請求。

但只做頁面靜態化還不夠,因為用戶分布在全國各地,有些人在北京,有些人在成都,有些人在深圳,地域相差很遠,網速各不相同。

如何才能讓用戶最快訪問到活動頁面呢?

這就需要使用CDN,它的全稱是Content Delivery Network,即內容分發網絡。

使用戶就近獲取所需內容,降低網絡擁塞,提高用戶訪問響應速度和命中率。

?

三、秒殺按鈕

1、秒殺按鈕設計

大部分用戶怕錯過秒殺時間點,一般會提前進入活動頁面。此時看到的秒殺按鈕是置灰,不可點擊的。只有到了秒殺時間點那一時刻,秒殺按鈕才會自動點亮,變成可點擊的。

但此時很多用戶已經迫不及待了,通過不停刷新頁面,爭取在第一時間看到秒殺按鈕的點亮。

從前面得知,該活動頁面是靜態的。那么我們在靜態頁面中如何控制秒殺按鈕,只在秒殺時間點時才點亮呢?

沒錯,使用js文件控制。

為了性能考慮,一般會將css、js和圖片等靜態資源文件提前緩存到CDN上,讓用戶能夠就近訪問秒殺頁面。

看到這里,有些聰明的小伙伴,可能會問:CDN上的js文件是如何更新的?

秒殺開始之前,js標志為false,還有另外一個隨機參數。

當秒殺開始的時候系統會生成一個新的js文件,此時標志為true,并且隨機參數生成一個新值,然后同步給CDN。由于有了這個隨機參數,CDN不會緩存數據,每次都能從CDN中獲取最新的js代碼。

此外,前端還可以加一個定時器,控制比如:10秒之內,只允許發起一次請求。如果用戶點擊了一次秒殺按鈕,則在10秒之內置灰,不允許再次點擊,等到過了時間限制,又允許重新點擊該按鈕。

?

四、讀多寫少

在秒殺的過程中,系統一般會先查一下庫存是否足夠,如果足夠才允許下單,寫數據庫。如果不夠,則直接返回該商品已經搶完。

由于大量用戶搶少量商品,只有極少部分用戶能夠搶成功,所以絕大部分用戶在秒殺時,庫存其實是不足的,系統會直接返回該商品已經搶完。

這是非常典型的:讀多寫少 的場景。

如果有數十萬的請求過來,同時通過數據庫查緩存是否足夠,此時數據庫可能會掛掉。因為數據庫的連接資源非常有限,比如:mysql,無法同時支持這么多的連接。

而應該改用緩存,比如:redis。

即便用了redis,也需要部署多個節點。

五、緩存問題

通常情況下,我們需要在redis中保存商品信息,里面包含:商品id、商品名稱、規格屬性、庫存等信息,同時數據庫中也要有相關信息,畢竟緩存并不完全可靠。

用戶在點擊秒殺按鈕,請求秒殺接口的過程中,需要傳入的商品id參數,然后服務端需要校驗該商品是否合法。

大致流程如下圖所示:

根據商品id,先從緩存中查詢商品,如果商品存在,則參與秒殺。如果不存在,則需要從數據庫中查詢商品,如果存在,則將商品信息放入緩存,然后參與秒殺。如果商品不存在,則直接提示失敗。

這個過程表面上看起來是OK的,但是如果深入分析一下會發現一些問題。

1、緩存擊穿

比如商品A第一次秒殺時,緩存中是沒有數據的,但數據庫中有。雖說上面有如果從數據庫中查到數據,則放入緩存的邏輯。

然而,在高并發下,同一時刻會有大量的請求,都在秒殺同一件商品,這些請求同時去查緩存中沒有數據,然后又同時訪問數據庫。結果悲劇了,數據庫可能扛不住壓力,直接掛掉。

如何解決這個問題呢?

這就需要加鎖,最好使用分布式鎖。

當然,針對這種情況,最好在項目啟動之前,先把緩存進行預熱。即事先把所有的商品,同步到緩存中,這樣商品基本都能直接從緩存中獲取到,就不會出現緩存擊穿的問題了。

是不是上面加鎖這一步可以不需要了?

表面上看起來,確實可以不需要。但如果緩存中設置的過期時間不對,緩存提前過期了,或者緩存被不小心刪除了,如果不加速同樣可能出現緩存擊穿。

其實這里加鎖,相當于買了一份保險。

2、緩存穿透

如果有大量的請求傳入的商品id,在緩存中和數據庫中都不存在,這些請求不就每次都會穿透過緩存,而直接訪問數據庫了。

由于前面已經加了鎖,所以即使這里的并發量很大,也不會導致數據庫直接掛掉。

但很顯然這些請求的處理性能并不好,有沒有更好的解決方案?

這時可以想到布隆過濾器

系統根據商品id,先從布隆過濾器中查詢該id是否存在,如果存在則允許從緩存中查詢數據,如果不存在,則直接返回失敗。

雖說該方案可以解決緩存穿透問題,但是又會引出另外一個問題:布隆過濾器中的數據如何更緩存中的數據保持一致?

這就要求,如果緩存中數據有更新,則要及時同步到布隆過濾器中。如果數據同步失敗了,還需要增加重試機制,而且跨數據源,能保證數據的實時一致性嗎?

顯然是不行的。

所以布隆過濾器絕大部分使用在緩存數據更新很少的場景中。

如果緩存數據更新非常頻繁,又該如何處理呢?

這時,就需要把不存在的商品id也緩存起來。

下次,再有該商品id的請求過來,則也能從緩存中查到數據,只不過該數據比較特殊,表示商品不存在。需要特別注意的是,這種特殊緩存設置的超時時間應該盡量短一點。

?

六、庫存問題

1、預扣庫存

對于庫存問題看似簡單,實則里面還是有些東西。

真正的秒殺商品的場景,不是說扣完庫存,就完事了,如果用戶在一段時間內,還沒完成支付,扣減的庫存是要加回去的。

所以,在這里引出了一個預扣庫存的概念,預扣庫存的主要流程如下:

扣減庫存中除了上面說到的預扣庫存回退庫存之外,還需要特別注意的是庫存不足和庫存超賣問題。

2、數據庫扣減庫存

使用數據庫扣減庫存,是最簡單的實現方案了,假設扣減庫存的sql如下:

update product set stock=stock-1 where id=123;

這種寫法對于扣減庫存是沒有問題的,但如何控制庫存不足的情況下,不讓用戶操作呢?

這就需要在update之前,先查一下庫存是否足夠了。

偽代碼如下:

int stock = mapper.getStockById(123);if(stock > 0) {  int count = mapper.updateStock(123);  if(count > 0) {    addOrder(123);  }}

大家有沒有發現這段代碼的問題?

沒錯,查詢操作和更新操作不是原子性的,會導致在并發的場景下,出現庫存超賣的情況。

有人可能會說,這樣好辦,加把鎖,不就搞定了,比如使用synchronized關鍵字。

確實,可以,但是性能不夠好。

還有更優雅的處理方案,即基于數據庫的樂觀鎖,這樣會少一次數據庫查詢,而且能夠天然的保證數據操作的原子性。

只需將上面的sql稍微調整一下:

update product set stock=stock-1 where id=product and stock > 0;

在sql最后加上:stock > 0,就能保證不會出現超賣的情況。

但需要頻繁訪問數據庫,我們都知道數據庫連接是非常昂貴的資源。在高并發的場景下,可能會造成系統雪崩。而且,容易出現多個請求,同時競爭行鎖的情況,造成相互等待,從而出現死鎖的問題。

3、redis扣減庫存

redis的incr方法是原子性的,可以用該方法扣減庫存。偽代碼如下:

boolean exist = redisClient.query(productId,userId);  if(exist) {    return -1;  }  int stock = redisClient.queryStock(productId);  if(stock <=0) {    return 0;  }  redisClient.incrby(productId, -1);  redisClient.add(productId,userId);return 1;

代碼流程如下:

  • 1)先判斷該用戶有沒有秒殺過該商品,如果已經秒殺過,則直接返回-1。
  • 2)查詢庫存,如果庫存小于等于0,則直接返回0,表示庫存不足。
  • 3)如果庫存充足,則扣減庫存,然后將本次秒殺記錄保存起來。然后返回1,表示成功。

估計很多小伙伴,一開始都會按這樣的思路寫代碼。但如果仔細想想會發現,這段代碼有問題。

有什么問題呢?

如果在高并發下,有多個請求同時查詢庫存,當時都大于0。由于查詢庫存和更新庫存非原則操作,則會出現庫存為負數的情況,即庫存超賣

當然有人可能會說,加個synchronized不就解決問題?

調整后代碼如下:

   boolean exist = redisClient.query(productId,userId);   if(exist) {    return -1;   }   synchronized(this) {       int stock = redisClient.queryStock(productId);       if(stock <=0) {         return 0;       }       redisClient.incrby(productId, -1);       redisClient.add(productId,userId);   }return 1;

synchronized確實能解決庫存為負數問題,但是這樣會導致接口性能急劇下降,每次查詢都需要競爭同一把鎖,顯然不太合理。

為了解決上面的問題,代碼優化如下:

boolean exist = redisClient.query(productId,userId);if(exist) {  return -1;}if(redisClient.incrby(productId, -1)<0) {  return 0;}redisClient.add(productId,userId);return 1;

該代碼主要流程如下:

    1. 先判斷該用戶有沒有秒殺過該商品,如果已經秒殺過,則直接返回-1。
    1. 扣減庫存,判斷返回值是否小于0,如果小于0,則直接返回0,表示庫存不足。
    1. 如果扣減庫存后,返回值大于或等于0,則將本次秒殺記錄保存起來。然后返回1,表示成功。

該方案咋一看,好像沒問題。

但如果在高并發場景中,有多個請求同時扣減庫存,大多數請求的incrby操作之后,結果都會小于0。

雖說,庫存出現負數,不會出現超賣的問題。但由于這里是預減庫存,如果負數值負的太多的話,后面萬一要回退庫存時,就會導致庫存不準。

那么,有沒有更好的方案呢?

4、 lua腳本扣減庫存

我們都知道lua腳本,是能夠保證原子性的,它跟redis一起配合使用,能夠完美解決上面的問題。

lua腳本有段非常經典的代碼:

該代碼的主要流程如下:

  • 1)先判斷商品id是否存在,如果不存在則直接返回。
  • 2)獲取該商品id的庫存,判斷庫存如果是-1,則直接返回,表示不限制庫存。
  • 3)如果庫存大于0,則扣減庫存。
  • 4)如果庫存等于0,是直接返回,表示庫存不足。
?

七、分布式鎖

之前我提到過,在秒殺的時候,需要先從緩存中查商品是否存在,如果不存在,則會從數據庫中查商品。如果數據庫中,則將該商品放入緩存中,然后返回。如果數據庫中沒有,則直接返回失敗。

大家試想一下,如果在高并發下,有大量的請求都去查一個緩存中不存在的商品,這些請求都會直接打到數據庫。數據庫由于承受不住壓力,而直接掛掉。

那么如何解決這個問題呢?

這就需要用redis分布式鎖了。

有關redis分布式鎖之前有寫過,具體可以參考文章

Redisson分布式鎖-原理篇(4)

?

八、mq異步處理

我們都知道在真實的秒殺場景中,有三個核心流程:

而這三個核心流程中,真正并發量大的是秒殺功能,下單和支付功能實際并發量很小。所以,我們在設計秒殺系統時,有必要把下單和支付功能從秒殺的主流程中拆分出來,特別是下單功能要做成mq異步處理的。而支付功能,比如支付寶支付,是業務場景本身保證的異步。

于是,秒殺后下單的流程變成如下:

如果使用mq,需要關注以下幾個問題:

1、消息丟失問題

秒殺成功了,往mq發送下單消息的時候,有可能會失敗。原因有很多,比如:網絡問題、broker掛了、mq服務端磁盤問題等。這些情況,都可能會造成消息丟失。

那么,如何防止消息丟失呢?

答:加一張消息發送表。

在生產者發送mq消息之前,先把該條消息寫入消息發送表,初始狀態是待處理,然后再發送mq消息。消費者消費消息時,處理完業務邏輯之后,再回調生產者的一個接口,修改消息狀態為已處理。

如果生產者把消息寫入消息發送表之后,再發送mq消息到mq服務端的過程中失敗了,造成了消息丟失。

這時候,要如何處理呢?

答:使用job,增加重試機制。

用job每隔一段時間去查詢消息發送表中狀態為待處理的數據,然后重新發送mq消息。

2、重復消費問題

本來消費者消費消息時,在ack應答的時候,如果網絡超時,本身就可能會消費重復的消息。但由于消息發送者增加了重試機制,會導致消費者重復消息的概率增大。

那么,如何解決重復消息問題呢?

答:加一張消息處理表。

消費者讀到消息之后,先判斷一下消息處理表,是否存在該消息,如果存在,表示是重復消費,則直接返回。如果不存在,則進行下單操作,接著將該消息寫入消息處理表中,再返回。

有個比較關鍵的點是:下單和寫消息處理表,要放在同一個事務中,保證原子操作

3、垃圾消息問題

這套方案表面上看起來沒有問題,但如果出現了消息消費失敗的情況。比如:由于某些原因,消息消費者下單一直失敗,一直不能回調狀態變更接口,這樣job會不停的重試發消息。最后,會產生大量的垃圾消息。

那么,如何解決這個問題呢?

每次在job重試時,需要先判斷一下消息發送表中該消息的發送次數是否達到最大限制,如果達到了,則直接返回。如果沒有達到,則將次數加1,然后發送消息。

這樣如果出現異常,只會產生少量的垃圾消息,不會影響到正常的業務。

4、延遲消費問題

通常情況下,如果用戶秒殺成功了,下單之后,在15分鐘之內還未完成支付的話,該訂單會被自動取消,回退庫存。

那么,在15分鐘內未完成支付,訂單被自動取消的功能,要如何實現呢?

我們首先想到的可能是job,因為它比較簡單。

但job有個問題,需要每隔一段時間處理一次,實時性不太好。

還有更好的方案?

答:使用延遲隊列。

我們都知道rocketmq,自帶了延遲隊列的功能。

下單時消息生產者會先生成訂單,此時狀態為待支付,然后會向延遲隊列中發一條消息。達到了延遲時間,消息消費者讀取消息之后,會查詢該訂單的狀態是否為待支付。如果是待支付狀態,則會更新訂單狀態為取消狀態。如果不是待支付狀態,說明該訂單已經支付過了,則直接返回。

還有個關鍵點,用戶完成支付之后,會修改訂單狀態為已支付。

九、如何限流

通過秒殺活動,如果我們運氣爆棚,可能會用非常低的價格買到不錯的商品(這種概率堪比買福利彩票中大獎)。

但有些高手,并不會像我們一樣老老實實,通過秒殺頁面點擊秒殺按鈕,搶購商品。他們可能在自己的服務器上,模擬正常用戶登錄系統,跳過秒殺頁面,直接調用秒殺接口。

如果是我們手動操作,一般情況下,一秒鐘只能點擊一次秒殺按鈕。

但是如果是服務器,一秒鐘可以請求成上千接口。

這種差距實在太明顯了,如果不做任何限制,絕大部分商品可能是被機器搶到,而非正常的用戶,有點不太公平。

所以,我們有必要識別這些非法請求,做一些限制。那么,我們該如何現在這些非法請求呢?

目前有兩種常用的限流方式:

  • 基于nginx限流
  • 基于redis限流

1、對同一用戶限流

為了防止某個用戶,請求接口次數過于頻繁,可以只針對該用戶做限制。

限制同一個用戶id,比如每分鐘只能請求5次接口。

2、對同一ip限流

有時候只對某個用戶限流是不夠的,有些高手可以模擬多個用戶請求,這種nginx就沒法識別了。

這時需要加同一ip限流功能。

限制同一個ip,比如每分鐘只能請求5次接口。

但這種限流方式可能會有誤殺的情況,比如同一個公司或網吧的出口ip是相同的,如果里面有多個正常用戶同時發起請求,有些用戶可能會被限制住。

3、對接口限流

別以為限制了用戶和ip就萬事大吉,有些高手甚至可以使用代理,每次都請求都換一個ip。

這時可以限制請求的接口總次數。

在高并發場景下,這種限制對于系統的穩定性是非常有必要的。但可能由于有些非法請求次數太多,達到了該接口的請求上限,而影響其他的正常用戶訪問該接口。看起來有點得不償失。

4、加驗證碼

相對于上面三種方式,加驗證碼的方式可能更精準一些,同樣能限制用戶的訪問頻次,但好處是不會存在誤殺的情況。

通常情況下,用戶在請求之前,需要先輸入驗證碼。用戶發起請求之后,服務端會去校驗該驗證碼是否正確。只有正確才允許進行下一步操作,否則直接返回,并且提示驗證碼錯誤。

此外,驗證碼一般是一次性的,同一個驗證碼只允許使用一次,不允許重復使用。

普通驗證碼,由于生成的數字或者圖案比較簡單,可能會被破解。優點是生成速度比較快,缺點是有安全隱患。

還有一個驗證碼叫做:移動滑塊,它生成速度比較慢,但比較安全,是目前各大互聯網公司的首選。

5、提高業務門檻

上面說的加驗證碼雖然可以限制非法用戶請求,但是有些影響用戶體驗。用戶點擊秒殺按鈕前,還要先輸入驗證碼,流程顯得有點繁瑣,秒殺功能的流程不是應該越簡單越好嗎?

其實,有時候達到某個目的,不一定非要通過技術手段,通過業務手段也一樣。

12306剛開始的時候,全國人民都在同一時刻搶火車票,由于并發量太大,系統經常掛。后來,重構優化之后,將購買周期放長了,可以提前20天購買火車票,并且可以在9點、10、11點、12點等整點購買火車票。調整業務之后(當然技術也有很多調整),將之前集中的請求,分散開了,一下子降低了用戶并發量。

回到這里,我們通過提高業務門檻,比如只有會員才能參與秒殺活動,普通注冊用戶沒有權限。或者,只有等級到達3級以上的普通用戶,才有資格參加該活動。

這樣簡單的提高一點門檻,即使是黃牛黨也束手無策,他們總不可能為了參加一次秒殺活動,還另外花錢充值會員吧?

?

參考

1、公眾號 蘇三說技術 的一篇文章 非常感謝。

?

?
?

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/124134.html

相關文章

  • 秒殺系統優化方案之緩存、隊列、鎖設計思路

    摘要:一為什么難秒殺系統難做的原因庫存只有一份,所有人會在集中的時間讀和寫這些數據。又例如搶票,亦與秒殺類似,瞬時流量更甚。 一、為什么難 ????秒殺系統難做的原因:庫存只有一份,所有人會在集中的時間讀和寫這些數據。例如小米手機每周二的秒殺,可能手機只有1萬部,但瞬時進入的流量可能是幾百幾千萬。又例如12306搶票,亦與秒殺類似,瞬時流量更甚。 主要需要解決的問題有兩個: 高并發對數據庫...

    dinfer 評論0 收藏0
  • 淺談秒殺系統架構設計

    摘要:動態生成隨機下單頁面的為了避免用戶直接訪問下單需要將動態化,用隨機數作為參數,只能秒殺開始的時候才生成。該文件不被緩存的做法隨機數。淺談秒殺系統架構設計如何只允許,第一個提交的單進入訂單系統。未超過秒殺商品總數,提交到子訂單系統。 秒殺是電子商務網站常見的一種營銷手段。 原則 不要整個系統宕機。 即使系統故障,也不要將錯誤數據展示出來。 盡量保持公平公正。 實現效果 秒殺開始前,...

    maochunguang 評論0 收藏0
  • 秒殺系統架構如何設計之我見

    摘要:即使秒殺系統崩潰了,也不會對網站造成影響。動態生成隨機下單頁面的為了避免用戶直接訪問下單需要將動態化,用隨機數作為參數,只能秒殺開始的時候才生成。架構設計如何控制秒殺商品頁面搶購按鈕的可用禁用。該文件不被緩存的做法隨機數。 秒殺背景 電商中為了吸引顧客、聚集人氣,經常會策劃一些秒殺活動。活動中售賣的商品,要么價格遠低于市場價格,要么比較稀缺(如一些新發布的商品)。這些商品電商一般都會限...

    zhunjiee 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<