摘要:但很顯然這些請(qǐng)求的處理性能并不好,有沒(méi)有更好的解決方案這時(shí)可以想到布隆過(guò)濾器。系統(tǒng)根據(jù)商品,先從布隆過(guò)濾器中查詢?cè)撌欠翊嬖?,如果存在則允許從緩存中查詢數(shù)據(jù),如果不存在,則直接返回失敗。所以布隆過(guò)濾器絕大部分使用在緩存數(shù)據(jù)更新很少的場(chǎng)景中。
高并發(fā)下如何設(shè)計(jì)秒殺系統(tǒng)?這是一個(gè)高頻面試題。這個(gè)問(wèn)題看似簡(jiǎn)單,但是里面的水很深,它考查的是高并發(fā)場(chǎng)景下,從前端到后端多方面的知識(shí)。
秒殺一般出現(xiàn)在商城的促銷活動(dòng)
中,指定了一定數(shù)量(比如:10個(gè))的商品(比如:手機(jī)),以極低的價(jià)格(比如:0.1元),讓大量用戶參與活動(dòng),但只有極少數(shù)用戶能夠購(gòu)買成功。這類活動(dòng)商家絕大部分是不賺錢的,說(shuō)白了是找個(gè)噱頭宣傳自己。
雖說(shuō)秒殺只是一個(gè)促銷活動(dòng),但對(duì)技術(shù)要求不低。下面給大家總結(jié)一下設(shè)計(jì)秒殺系統(tǒng)需要注意的9個(gè)細(xì)節(jié)。
一般在秒殺時(shí)間點(diǎn)(比如:12點(diǎn))前幾分鐘,用戶并發(fā)量才真正突增,達(dá)到秒殺時(shí)間點(diǎn)時(shí),并發(fā)量會(huì)達(dá)到頂峰。
但由于這類活動(dòng)是大量用戶搶少量商品的場(chǎng)景,必定會(huì)出現(xiàn)狼多肉少
的情況,所以其實(shí)絕大部分用戶秒殺會(huì)失敗,只有極少部分用戶能夠成功。
正常情況下,大部分用戶會(huì)收到商品已經(jīng)搶完的提醒,收到該提醒后,他們大概率不會(huì)在那個(gè)活動(dòng)頁(yè)面停留了,如此一來(lái),用戶并發(fā)量又會(huì)急劇下降。所以這個(gè)峰值持續(xù)的時(shí)間其實(shí)是非常短的,這樣就會(huì)出現(xiàn)瞬時(shí)高并發(fā)的情況,下面用一張圖直觀的感受一下流量的變化:
像這種瞬時(shí)高并發(fā)的場(chǎng)景,傳統(tǒng)的系統(tǒng)很難應(yīng)對(duì),我們需要設(shè)計(jì)一套全新的系統(tǒng)??梢詮囊韵聨讉€(gè)方面入手:
活動(dòng)頁(yè)面是用戶流量的第一入口,所以是并發(fā)量最大的地方。
如果這些流量都能直接訪問(wèn)服務(wù)端,恐怕服務(wù)端會(huì)因?yàn)槌惺懿蛔∵@么大的壓力,而直接掛掉。
活動(dòng)頁(yè)面絕大多數(shù)內(nèi)容是固定的,比如:商品名稱、商品描述、圖片等。為了減少不必要的服務(wù)端請(qǐng)求,通常情況下,會(huì)對(duì)活動(dòng)頁(yè)面做靜態(tài)化
處理。用戶瀏覽商品等常規(guī)操作,并不會(huì)請(qǐng)求到服務(wù)端。只有到了秒殺時(shí)間點(diǎn),并且用戶主動(dòng)點(diǎn)了秒殺按鈕才允許訪問(wèn)服務(wù)端。
這樣能過(guò)濾大部分無(wú)效請(qǐng)求。
但只做頁(yè)面靜態(tài)化還不夠,因?yàn)橛脩舴植荚谌珖?guó)各地,有些人在北京,有些人在成都,有些人在深圳,地域相差很遠(yuǎn),網(wǎng)速各不相同。
如何才能讓用戶最快訪問(wèn)到活動(dòng)頁(yè)面呢?
這就需要使用CDN,它的全稱是Content Delivery Network,即內(nèi)容分發(fā)網(wǎng)絡(luò)。
使用戶就近獲取所需內(nèi)容,降低網(wǎng)絡(luò)擁塞,提高用戶訪問(wèn)響應(yīng)速度和命中率。
?大部分用戶怕錯(cuò)過(guò)秒殺時(shí)間點(diǎn),一般會(huì)提前進(jìn)入活動(dòng)頁(yè)面。此時(shí)看到的秒殺按鈕
是置灰,不可點(diǎn)擊的。只有到了秒殺時(shí)間點(diǎn)那一時(shí)刻,秒殺按鈕才會(huì)自動(dòng)點(diǎn)亮,變成可點(diǎn)擊的。
但此時(shí)很多用戶已經(jīng)迫不及待了,通過(guò)不停刷新頁(yè)面,爭(zhēng)取在第一時(shí)間看到秒殺按鈕的點(diǎn)亮。
從前面得知,該活動(dòng)頁(yè)面是靜態(tài)的。那么我們?cè)陟o態(tài)頁(yè)面中如何控制秒殺按鈕,只在秒殺時(shí)間點(diǎn)時(shí)才點(diǎn)亮呢?
沒(méi)錯(cuò),使用js文件控制。
為了性能考慮,一般會(huì)將css、js和圖片等靜態(tài)資源文件提前緩存到CDN上,讓用戶能夠就近訪問(wèn)秒殺頁(yè)面。
看到這里,有些聰明的小伙伴,可能會(huì)問(wèn):CDN上的js文件是如何更新的?
秒殺開始之前,js標(biāo)志為false,還有另外一個(gè)隨機(jī)參數(shù)。
當(dāng)秒殺開始的時(shí)候系統(tǒng)會(huì)生成一個(gè)新的js文件,此時(shí)標(biāo)志為true,并且隨機(jī)參數(shù)生成一個(gè)新值,然后同步給CDN。由于有了這個(gè)隨機(jī)參數(shù),CDN不會(huì)緩存數(shù)據(jù),每次都能從CDN中獲取最新的js代碼。
此外,前端還可以加一個(gè)定時(shí)器,控制比如:10秒之內(nèi),只允許發(fā)起一次請(qǐng)求。如果用戶點(diǎn)擊了一次秒殺按鈕,則在10秒之內(nèi)置灰,不允許再次點(diǎn)擊,等到過(guò)了時(shí)間限制,又允許重新點(diǎn)擊該按鈕。
?在秒殺的過(guò)程中,系統(tǒng)一般會(huì)先查一下庫(kù)存是否足夠,如果足夠才允許下單,寫數(shù)據(jù)庫(kù)。如果不夠,則直接返回該商品已經(jīng)搶完。
由于大量用戶搶少量商品,只有極少部分用戶能夠搶成功,所以絕大部分用戶在秒殺時(shí),庫(kù)存其實(shí)是不足的,系統(tǒng)會(huì)直接返回該商品已經(jīng)搶完。
這是非常典型的:讀多寫少
的場(chǎng)景。
如果有數(shù)十萬(wàn)的請(qǐng)求過(guò)來(lái),同時(shí)通過(guò)數(shù)據(jù)庫(kù)查緩存是否足夠,此時(shí)數(shù)據(jù)庫(kù)可能會(huì)掛掉。因?yàn)閿?shù)據(jù)庫(kù)的連接資源非常有限,比如:mysql,無(wú)法同時(shí)支持這么多的連接。
而應(yīng)該改用緩存,比如:redis。
即便用了redis,也需要部署多個(gè)節(jié)點(diǎn)。
通常情況下,我們需要在redis中保存商品信息,里面包含:商品id、商品名稱、規(guī)格屬性、庫(kù)存等信息,同時(shí)數(shù)據(jù)庫(kù)中也要有相關(guān)信息,畢竟緩存并不完全可靠。
用戶在點(diǎn)擊秒殺按鈕,請(qǐng)求秒殺接口的過(guò)程中,需要傳入的商品id參數(shù),然后服務(wù)端需要校驗(yàn)該商品是否合法。
大致流程如下圖所示:
根據(jù)商品id,先從緩存中查詢商品,如果商品存在,則參與秒殺。如果不存在,則需要從數(shù)據(jù)庫(kù)中查詢商品,如果存在,則將商品信息放入緩存,然后參與秒殺。如果商品不存在,則直接提示失敗。
這個(gè)過(guò)程表面上看起來(lái)是OK的,但是如果深入分析一下會(huì)發(fā)現(xiàn)一些問(wèn)題。
比如商品A第一次秒殺時(shí),緩存中是沒(méi)有數(shù)據(jù)的,但數(shù)據(jù)庫(kù)中有。雖說(shuō)上面有如果從數(shù)據(jù)庫(kù)中查到數(shù)據(jù),則放入緩存的邏輯。
然而,在高并發(fā)下,同一時(shí)刻會(huì)有大量的請(qǐng)求,都在秒殺同一件商品,這些請(qǐng)求同時(shí)去查緩存中沒(méi)有數(shù)據(jù),然后又同時(shí)訪問(wèn)數(shù)據(jù)庫(kù)。結(jié)果悲劇了,數(shù)據(jù)庫(kù)可能扛不住壓力,直接掛掉。
如何解決這個(gè)問(wèn)題呢?
這就需要加鎖,最好使用分布式鎖。
當(dāng)然,針對(duì)這種情況,最好在項(xiàng)目啟動(dòng)之前,先把緩存進(jìn)行預(yù)熱
。即事先把所有的商品,同步到緩存中,這樣商品基本都能直接從緩存中獲取到,就不會(huì)出現(xiàn)緩存擊穿的問(wèn)題了。
是不是上面加鎖這一步可以不需要了?
表面上看起來(lái),確實(shí)可以不需要。但如果緩存中設(shè)置的過(guò)期時(shí)間不對(duì),緩存提前過(guò)期了,或者緩存被不小心刪除了,如果不加速同樣可能出現(xiàn)緩存擊穿。
其實(shí)這里加鎖,相當(dāng)于買了一份保險(xiǎn)。
如果有大量的請(qǐng)求傳入的商品id,在緩存中和數(shù)據(jù)庫(kù)中都不存在,這些請(qǐng)求不就每次都會(huì)穿透過(guò)緩存,而直接訪問(wèn)數(shù)據(jù)庫(kù)了。
由于前面已經(jīng)加了鎖,所以即使這里的并發(fā)量很大,也不會(huì)導(dǎo)致數(shù)據(jù)庫(kù)直接掛掉。
但很顯然這些請(qǐng)求的處理性能并不好,有沒(méi)有更好的解決方案?
這時(shí)可以想到布隆過(guò)濾器
。
系統(tǒng)根據(jù)商品id,先從布隆過(guò)濾器中查詢?cè)搃d是否存在,如果存在則允許從緩存中查詢數(shù)據(jù),如果不存在,則直接返回失敗。
雖說(shuō)該方案可以解決緩存穿透問(wèn)題,但是又會(huì)引出另外一個(gè)問(wèn)題:布隆過(guò)濾器中的數(shù)據(jù)如何更緩存中的數(shù)據(jù)保持一致?
這就要求,如果緩存中數(shù)據(jù)有更新,則要及時(shí)同步到布隆過(guò)濾器中。如果數(shù)據(jù)同步失敗了,還需要增加重試機(jī)制,而且跨數(shù)據(jù)源,能保證數(shù)據(jù)的實(shí)時(shí)一致性嗎?
顯然是不行的。
所以布隆過(guò)濾器絕大部分使用在緩存數(shù)據(jù)更新很少的場(chǎng)景中。
如果緩存數(shù)據(jù)更新非常頻繁,又該如何處理呢?
這時(shí),就需要把不存在的商品id也緩存起來(lái)。
下次,再有該商品id的請(qǐng)求過(guò)來(lái),則也能從緩存中查到數(shù)據(jù),只不過(guò)該數(shù)據(jù)比較特殊,表示商品不存在。需要特別注意的是,這種特殊緩存設(shè)置的超時(shí)時(shí)間應(yīng)該盡量短一點(diǎn)。
?對(duì)于庫(kù)存問(wèn)題看似簡(jiǎn)單,實(shí)則里面還是有些東西。
真正的秒殺商品的場(chǎng)景,不是說(shuō)扣完庫(kù)存,就完事了,如果用戶在一段時(shí)間內(nèi),還沒(méi)完成支付,扣減的庫(kù)存是要加回去的。
所以,在這里引出了一個(gè)預(yù)扣庫(kù)存
的概念,預(yù)扣庫(kù)存的主要流程如下:
扣減庫(kù)存中除了上面說(shuō)到的預(yù)扣庫(kù)存
和回退庫(kù)存
之外,還需要特別注意的是庫(kù)存不足和庫(kù)存超賣問(wèn)題。
使用數(shù)據(jù)庫(kù)扣減庫(kù)存,是最簡(jiǎn)單的實(shí)現(xiàn)方案了,假設(shè)扣減庫(kù)存的sql如下:
update product set stock=stock-1 where id=123;
這種寫法對(duì)于扣減庫(kù)存是沒(méi)有問(wèn)題的,但如何控制庫(kù)存不足的情況下,不讓用戶操作呢?
這就需要在update之前,先查一下庫(kù)存是否足夠了。
偽代碼如下:
int stock = mapper.getStockById(123);if(stock > 0) { int count = mapper.updateStock(123); if(count > 0) { addOrder(123); }}
大家有沒(méi)有發(fā)現(xiàn)這段代碼的問(wèn)題?
沒(méi)錯(cuò),查詢操作和更新操作不是原子性的,會(huì)導(dǎo)致在并發(fā)的場(chǎng)景下,出現(xiàn)庫(kù)存超賣的情況。
有人可能會(huì)說(shuō),這樣好辦,加把鎖,不就搞定了,比如使用synchronized關(guān)鍵字。
確實(shí),可以,但是性能不夠好。
還有更優(yōu)雅的處理方案,即基于數(shù)據(jù)庫(kù)的樂(lè)觀鎖,這樣會(huì)少一次數(shù)據(jù)庫(kù)查詢,而且能夠天然的保證數(shù)據(jù)操作的原子性。
只需將上面的sql稍微調(diào)整一下:
update product set stock=stock-1 where id=product and stock > 0;
在sql最后加上:stock > 0,就能保證不會(huì)出現(xiàn)超賣的情況。
但需要頻繁訪問(wèn)數(shù)據(jù)庫(kù),我們都知道數(shù)據(jù)庫(kù)連接是非常昂貴的資源。在高并發(fā)的場(chǎng)景下,可能會(huì)造成系統(tǒng)雪崩。而且,容易出現(xiàn)多個(gè)請(qǐng)求,同時(shí)競(jìng)爭(zhēng)行鎖的情況,造成相互等待,從而出現(xiàn)死鎖的問(wèn)題。
redis的incr
方法是原子性的,可以用該方法扣減庫(kù)存。偽代碼如下:
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;
代碼流程如下:
估計(jì)很多小伙伴,一開始都會(huì)按這樣的思路寫代碼。但如果仔細(xì)想想會(huì)發(fā)現(xiàn),這段代碼有問(wèn)題。
有什么問(wèn)題呢?
如果在高并發(fā)下,有多個(gè)請(qǐng)求同時(shí)查詢庫(kù)存,當(dāng)時(shí)都大于0。由于查詢庫(kù)存和更新庫(kù)存非原則操作,則會(huì)出現(xiàn)庫(kù)存為負(fù)數(shù)的情況,即庫(kù)存超賣
。
當(dāng)然有人可能會(huì)說(shuō),加個(gè)synchronized不就解決問(wèn)題?
調(diào)整后代碼如下:
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
確實(shí)能解決庫(kù)存為負(fù)數(shù)問(wèn)題,但是這樣會(huì)導(dǎo)致接口性能急劇下降,每次查詢都需要競(jìng)爭(zhēng)同一把鎖,顯然不太合理。
為了解決上面的問(wèn)題,代碼優(yōu)化如下:
boolean exist = redisClient.query(productId,userId);if(exist) { return -1;}if(redisClient.incrby(productId, -1)<0) { return 0;}redisClient.add(productId,userId);return 1;
該代碼主要流程如下:
該方案咋一看,好像沒(méi)問(wèn)題。
但如果在高并發(fā)場(chǎng)景中,有多個(gè)請(qǐng)求同時(shí)扣減庫(kù)存,大多數(shù)請(qǐng)求的incrby操作之后,結(jié)果都會(huì)小于0。
雖說(shuō),庫(kù)存出現(xiàn)負(fù)數(shù),不會(huì)出現(xiàn)超賣的問(wèn)題。但由于這里是預(yù)減庫(kù)存,如果負(fù)數(shù)值負(fù)的太多的話,后面萬(wàn)一要回退庫(kù)存時(shí),就會(huì)導(dǎo)致庫(kù)存不準(zhǔn)。
那么,有沒(méi)有更好的方案呢?
我們都知道lua腳本,是能夠保證原子性的,它跟redis一起配合使用,能夠完美解決上面的問(wèn)題。
lua腳本有段非常經(jīng)典的代碼:
該代碼的主要流程如下:
之前我提到過(guò),在秒殺的時(shí)候,需要先從緩存中查商品是否存在,如果不存在,則會(huì)從數(shù)據(jù)庫(kù)中查商品。如果數(shù)據(jù)庫(kù)中,則將該商品放入緩存中,然后返回。如果數(shù)據(jù)庫(kù)中沒(méi)有,則直接返回失敗。
大家試想一下,如果在高并發(fā)下,有大量的請(qǐng)求都去查一個(gè)緩存中不存在的商品,這些請(qǐng)求都會(huì)直接打到數(shù)據(jù)庫(kù)。數(shù)據(jù)庫(kù)由于承受不住壓力,而直接掛掉。
那么如何解決這個(gè)問(wèn)題呢?
這就需要用redis分布式鎖了。
有關(guān)redis分布式鎖之前有寫過(guò),具體可以參考文章
Redisson分布式鎖-原理篇(4)
?我們都知道在真實(shí)的秒殺場(chǎng)景中,有三個(gè)核心流程:
而這三個(gè)核心流程中,真正并發(fā)量大的是秒殺功能,下單和支付功能實(shí)際并發(fā)量很小。所以,我們?cè)谠O(shè)計(jì)秒殺系統(tǒng)時(shí),有必要把下單和支付功能從秒殺的主流程中拆分出來(lái),特別是下單功能要做成mq異步處理的。而支付功能,比如支付寶支付,是業(yè)務(wù)場(chǎng)景本身保證的異步。
于是,秒殺后下單的流程變成如下:
如果使用mq,需要關(guān)注以下幾個(gè)問(wèn)題:
秒殺成功了,往mq發(fā)送下單消息的時(shí)候,有可能會(huì)失敗。原因有很多,比如:網(wǎng)絡(luò)問(wèn)題、broker掛了、mq服務(wù)端磁盤問(wèn)題等。這些情況,都可能會(huì)造成消息丟失。
那么,如何防止消息丟失呢?
答:加一張消息發(fā)送表。
在生產(chǎn)者發(fā)送mq消息之前,先把該條消息寫入消息發(fā)送表,初始狀態(tài)是待處理,然后再發(fā)送mq消息。消費(fèi)者消費(fèi)消息時(shí),處理完業(yè)務(wù)邏輯之后,再回調(diào)生產(chǎn)者的一個(gè)接口,修改消息狀態(tài)為已處理。
如果生產(chǎn)者把消息寫入消息發(fā)送表之后,再發(fā)送mq消息到mq服務(wù)端的過(guò)程中失敗了,造成了消息丟失。
這時(shí)候,要如何處理呢?
答:使用job,增加重試機(jī)制。
用job每隔一段時(shí)間去查詢消息發(fā)送表中狀態(tài)為待處理的數(shù)據(jù),然后重新發(fā)送mq消息。
本來(lái)消費(fèi)者消費(fèi)消息時(shí),在ack應(yīng)答的時(shí)候,如果網(wǎng)絡(luò)超時(shí),本身就可能會(huì)消費(fèi)重復(fù)的消息。但由于消息發(fā)送者增加了重試機(jī)制,會(huì)導(dǎo)致消費(fèi)者重復(fù)消息的概率增大。
那么,如何解決重復(fù)消息問(wèn)題呢?
答:加一張消息處理表。
消費(fèi)者讀到消息之后,先判斷一下消息處理表,是否存在該消息,如果存在,表示是重復(fù)消費(fèi),則直接返回。如果不存在,則進(jìn)行下單操作,接著將該消息寫入消息處理表中,再返回。
有個(gè)比較關(guān)鍵的點(diǎn)是:下單和寫消息處理表,要放在同一個(gè)事務(wù)中,保證原子操作。
這套方案表面上看起來(lái)沒(méi)有問(wèn)題,但如果出現(xiàn)了消息消費(fèi)失敗的情況。比如:由于某些原因,消息消費(fèi)者下單一直失敗,一直不能回調(diào)狀態(tài)變更接口,這樣job會(huì)不停的重試發(fā)消息。最后,會(huì)產(chǎn)生大量的垃圾消息。
那么,如何解決這個(gè)問(wèn)題呢?
每次在job重試時(shí),需要先判斷一下消息發(fā)送表中該消息的發(fā)送次數(shù)是否達(dá)到最大限制,如果達(dá)到了,則直接返回。如果沒(méi)有達(dá)到,則將次數(shù)加1,然后發(fā)送消息。
這樣如果出現(xiàn)異常,只會(huì)產(chǎn)生少量的垃圾消息,不會(huì)影響到正常的業(yè)務(wù)。
通常情況下,如果用戶秒殺成功了,下單之后,在15分鐘之內(nèi)還未完成支付的話,該訂單會(huì)被自動(dòng)取消,回退庫(kù)存。
那么,在15分鐘內(nèi)未完成支付,訂單被自動(dòng)取消的功能,要如何實(shí)現(xiàn)呢?
我們首先想到的可能是job,因?yàn)樗容^簡(jiǎn)單。
但job有個(gè)問(wèn)題,需要每隔一段時(shí)間處理一次,實(shí)時(shí)性不太好。
還有更好的方案?
答:使用延遲隊(duì)列。
我們都知道rocketmq,自帶了延遲隊(duì)列的功能。
下單時(shí)消息生產(chǎn)者會(huì)先生成訂單,此時(shí)狀態(tài)為待支付,然后會(huì)向延遲隊(duì)列中發(fā)一條消息。達(dá)到了延遲時(shí)間,消息消費(fèi)者讀取消息之后,會(huì)查詢?cè)撚唵蔚臓顟B(tài)是否為待支付。如果是待支付狀態(tài),則會(huì)更新訂單狀態(tài)為取消狀態(tài)。如果不是待支付狀態(tài),說(shuō)明該訂單已經(jīng)支付過(guò)了,則直接返回。
還有個(gè)關(guān)鍵點(diǎn),用戶完成支付之后,會(huì)修改訂單狀態(tài)為已支付。
通過(guò)秒殺活動(dòng),如果我們運(yùn)氣爆棚,可能會(huì)用非常低的價(jià)格買到不錯(cuò)的商品(這種概率堪比買福利彩票中大獎(jiǎng))。
但有些高手,并不會(huì)像我們一樣老老實(shí)實(shí),通過(guò)秒殺頁(yè)面點(diǎn)擊秒殺按鈕,搶購(gòu)商品。他們可能在自己的服務(wù)器上,模擬正常用戶登錄系統(tǒng),跳過(guò)秒殺頁(yè)面,直接調(diào)用秒殺接口。
如果是我們手動(dòng)操作,一般情況下,一秒鐘只能點(diǎn)擊一次秒殺按鈕。
但是如果是服務(wù)器,一秒鐘可以請(qǐng)求成上千接口。
這種差距實(shí)在太明顯了,如果不做任何限制,絕大部分商品可能是被機(jī)器搶到,而非正常的用戶,有點(diǎn)不太公平。
所以,我們有必要識(shí)別這些非法請(qǐng)求,做一些限制。那么,我們?cè)撊绾维F(xiàn)在這些非法請(qǐng)求呢?
目前有兩種常用的限流方式:
為了防止某個(gè)用戶,請(qǐng)求接口次數(shù)過(guò)于頻繁,可以只針對(duì)該用戶做限制。
限制同一個(gè)用戶id,比如每分鐘只能請(qǐng)求5次接口。
有時(shí)候只對(duì)某個(gè)用戶限流是不夠的,有些高手可以模擬多個(gè)用戶請(qǐng)求,這種nginx就沒(méi)法識(shí)別了。
這時(shí)需要加同一ip限流功能。
限制同一個(gè)ip,比如每分鐘只能請(qǐng)求5次接口。
但這種限流方式可能會(huì)有誤殺的情況,比如同一個(gè)公司或網(wǎng)吧的出口ip是相同的,如果里面有多個(gè)正常用戶同時(shí)發(fā)起請(qǐng)求,有些用戶可能會(huì)被限制住。
別以為限制了用戶和ip就萬(wàn)事大吉,有些高手甚至可以使用代理,每次都請(qǐng)求都換一個(gè)ip。
這時(shí)可以限制請(qǐng)求的接口總次數(shù)。
在高并發(fā)場(chǎng)景下,這種限制對(duì)于系統(tǒng)的穩(wěn)定性是非常有必要的。但可能由于有些非法請(qǐng)求次數(shù)太多,達(dá)到了該接口的請(qǐng)求上限,而影響其他的正常用戶訪問(wèn)該接口??雌饋?lái)有點(diǎn)得不償失。
相對(duì)于上面三種方式,加驗(yàn)證碼的方式可能更精準(zhǔn)一些,同樣能限制用戶的訪問(wèn)頻次,但好處是不會(huì)存在誤殺的情況。
通常情況下,用戶在請(qǐng)求之前,需要先輸入驗(yàn)證碼。用戶發(fā)起請(qǐng)求之后,服務(wù)端會(huì)去校驗(yàn)該驗(yàn)證碼是否正確。只有正確才允許進(jìn)行下一步操作,否則直接返回,并且提示驗(yàn)證碼錯(cuò)誤。
此外,驗(yàn)證碼一般是一次性的,同一個(gè)驗(yàn)證碼只允許使用一次,不允許重復(fù)使用。
普通驗(yàn)證碼,由于生成的數(shù)字或者圖案比較簡(jiǎn)單,可能會(huì)被破解。優(yōu)點(diǎn)是生成速度比較快,缺點(diǎn)是有安全隱患。
還有一個(gè)驗(yàn)證碼叫做:移動(dòng)滑塊
,它生成速度比較慢,但比較安全,是目前各大互聯(lián)網(wǎng)公司的首選。
上面說(shuō)的加驗(yàn)證碼雖然可以限制非法用戶請(qǐng)求,但是有些影響用戶體驗(yàn)。用戶點(diǎn)擊秒殺按鈕前,還要先輸入驗(yàn)證碼,流程顯得有點(diǎn)繁瑣,秒殺功能的流程不是應(yīng)該越簡(jiǎn)單越好嗎?
其實(shí),有時(shí)候達(dá)到某個(gè)目的,不一定非要通過(guò)技術(shù)手段,通過(guò)業(yè)務(wù)手段也一樣。
12306剛開始的時(shí)候,全國(guó)人民都在同一時(shí)刻搶火車票,由于并發(fā)量太大,系統(tǒng)經(jīng)常掛。后來(lái),重構(gòu)優(yōu)化之后,將購(gòu)買周期放長(zhǎng)了,可以提前20天購(gòu)買火車票,并且可以在9點(diǎn)、10、11點(diǎn)、12點(diǎn)等整點(diǎn)購(gòu)買火車票。調(diào)整業(yè)務(wù)之后(當(dāng)然技術(shù)也有很多調(diào)整),將之前集中的請(qǐng)求,分散開了,一下子降低了用戶并發(fā)量。
回到這里,我們通過(guò)提高業(yè)務(wù)門檻,比如只有會(huì)員才能參與秒殺活動(dòng),普通注冊(cè)用戶沒(méi)有權(quán)限?;蛘?,只有等級(jí)到達(dá)3級(jí)以上的普通用戶,才有資格參加該活動(dòng)。
這樣簡(jiǎn)單的提高一點(diǎn)門檻,即使是黃牛黨也束手無(wú)策,他們總不可能為了參加一次秒殺活動(dòng),還另外花錢充值會(huì)員吧?
?1、公眾號(hào) 蘇三說(shuō)技術(shù) 的一篇文章 非常感謝。
? ?文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/124134.html
摘要:一為什么難秒殺系統(tǒng)難做的原因庫(kù)存只有一份,所有人會(huì)在集中的時(shí)間讀和寫這些數(shù)據(jù)。又例如搶票,亦與秒殺類似,瞬時(shí)流量更甚。 一、為什么難 ????秒殺系統(tǒng)難做的原因:庫(kù)存只有一份,所有人會(huì)在集中的時(shí)間讀和寫這些數(shù)據(jù)。例如小米手機(jī)每周二的秒殺,可能手機(jī)只有1萬(wàn)部,但瞬時(shí)進(jìn)入的流量可能是幾百幾千萬(wàn)。又例如12306搶票,亦與秒殺類似,瞬時(shí)流量更甚。 主要需要解決的問(wèn)題有兩個(gè): 高并發(fā)對(duì)數(shù)據(jù)庫(kù)...
摘要:動(dòng)態(tài)生成隨機(jī)下單頁(yè)面的為了避免用戶直接訪問(wèn)下單需要將動(dòng)態(tài)化,用隨機(jī)數(shù)作為參數(shù),只能秒殺開始的時(shí)候才生成。該文件不被緩存的做法隨機(jī)數(shù)。淺談秒殺系統(tǒng)架構(gòu)設(shè)計(jì)如何只允許,第一個(gè)提交的單進(jìn)入訂單系統(tǒng)。未超過(guò)秒殺商品總數(shù),提交到子訂單系統(tǒng)。 秒殺是電子商務(wù)網(wǎng)站常見的一種營(yíng)銷手段。 原則 不要整個(gè)系統(tǒng)宕機(jī)。 即使系統(tǒng)故障,也不要將錯(cuò)誤數(shù)據(jù)展示出來(lái)。 盡量保持公平公正。 實(shí)現(xiàn)效果 秒殺開始前,...
摘要:即使秒殺系統(tǒng)崩潰了,也不會(huì)對(duì)網(wǎng)站造成影響。動(dòng)態(tài)生成隨機(jī)下單頁(yè)面的為了避免用戶直接訪問(wèn)下單需要將動(dòng)態(tài)化,用隨機(jī)數(shù)作為參數(shù),只能秒殺開始的時(shí)候才生成。架構(gòu)設(shè)計(jì)如何控制秒殺商品頁(yè)面搶購(gòu)按鈕的可用禁用。該文件不被緩存的做法隨機(jī)數(shù)。 秒殺背景 電商中為了吸引顧客、聚集人氣,經(jīng)常會(huì)策劃一些秒殺活動(dòng)?;顒?dòng)中售賣的商品,要么價(jià)格遠(yuǎn)低于市場(chǎng)價(jià)格,要么比較稀缺(如一些新發(fā)布的商品)。這些商品電商一般都會(huì)限...
閱讀 777·2023-04-25 15:13
閱讀 1399·2021-11-22 12:03
閱讀 826·2021-11-19 09:40
閱讀 1910·2021-11-17 09:38
閱讀 1714·2021-11-08 13:18
閱讀 655·2021-09-02 15:15
閱讀 1767·2019-08-30 15:54
閱讀 2636·2019-08-30 11:12