前言
redis在互聯(lián)網(wǎng)技術(shù)存儲方面使用如此廣泛,幾乎所有的后端技術(shù)面試官都要在Redis的使用和原理方面對小伙伴們進(jìn)行360°的刁難。
作為一個(gè)在互聯(lián)網(wǎng)公司面一次拿一次Offer的面霸,打敗了無數(shù)競爭對手,每次都只能看到無數(shù)落寞的身影失望的離開,略感愧疚(請?jiān)试S我使用一下夸張的修辭手法)。
于是在一個(gè)寂寞難耐的夜晚,我痛定思痛,決定開始寫《吊打面試官》系列,希望能幫助各位讀者以后面試勢如破竹,對面試官進(jìn)行360°的反擊,吊打問你的面試官,讓一同面試的同僚瞠目結(jié)舌,瘋狂收割大廠Offer!
絮叨
之前寫了很多Redis相關(guān)的知識點(diǎn),我又大概回頭看了下,除了比較底層的東西沒寫很深之外,我基本上的點(diǎn)都提到過了,我相信如果只是為了應(yīng)付面試應(yīng)該是夠了的,但是如果你想把它們真正的吸收納為己用,還是需要大量的知識積累,和很多實(shí)際操作的。
就我自己而言Redis在開發(fā)過程中實(shí)在用得太普遍了,熱點(diǎn)數(shù)據(jù)的存儲啊,整體性能的提升啊都會用到,但是就像我說的技術(shù)就是一把雙刃劍,使用它們隨之而來的問題也會很多的,我在老東家雙十二就遇到緩存雪崩問題讓整體服務(wù)宕機(jī)3分鐘,相必大家都知道阿里今年的雙十一數(shù)據(jù)了,那三分鐘在這種時(shí)候到底值多少錢?真的不敢想象。
Redis的普遍我就拿掘金我自己的認(rèn)知舉例,不知道對不對,但是目測是對的。
大家看到問題所在了么?是的熱門的贊的數(shù)據(jù)不是最新的,我盲猜一波上面的熱門文章是緩存。失效時(shí)間應(yīng)該是幾十分鐘的,為啥這么做呢?
熱門文章是大家共同都會看到的,也就是熱點(diǎn)數(shù)據(jù),在那做緩存,他是不需要那么高的實(shí)時(shí)性的,那下面的文章列表是最新發(fā)布的文章,有高實(shí)時(shí)性的特點(diǎn),大家訪問多的放在緩存還可以給DB減少壓力,我也不知道掘金是不是這么做的哈,反正道理是這么個(gè)道理了。
那什么場景是使用Redis比較復(fù)雜的場景,而且需要大量中間件和業(yè)務(wù)邏輯去配合的呢?
秒殺!是的就是今天的主題秒殺,我就用我自己的思路帶大家一起看一下,設(shè)計(jì)一個(gè)秒殺從前到后,從內(nèi)到外到底要技術(shù)人員做多少準(zhǔn)備。
正文
首先設(shè)計(jì)一個(gè)系統(tǒng)之前,我們需要先確認(rèn)我們的業(yè)務(wù)場景是怎么樣子的,我就帶著大家一起假設(shè)一個(gè)場景好吧。
場景
我們現(xiàn)場要賣100件下面這個(gè)嬰兒紙尿褲,然后我們根據(jù)以往這樣秒殺活動的數(shù)據(jù)經(jīng)驗(yàn)來看,目測來搶這100件紙尿褲的人足足有10萬人。(南極人打錢!)
你一聽,完了呀,這我們的服務(wù)器哪里頂?shù)米“。≌f真的直接打DB肯定掛。但是別急嘛,有暖男碼農(nóng)之屋在,我們在開始之前應(yīng)該先思考下會出現(xiàn)哪些問題?
問題
高并發(fā):
是的高并發(fā)這個(gè)是我們想都不用想的一個(gè)點(diǎn),一瞬間這么多人進(jìn)來這不是高并發(fā)什么時(shí)候是呢?
是吧,秒殺的特點(diǎn)就是這樣時(shí)間極短、 瞬間用戶量大。
正常的店鋪營銷都是用極低的價(jià)格配合上短信、App的精準(zhǔn)推送,吸引特別多的用戶來參與這場秒殺,爽了商家苦了開發(fā)呀。
秒殺大家都知道如果真的營銷到位,價(jià)格誘人,幾十萬的流量我覺得完全不是問題,那單機(jī)的Redis我感覺3-4W的QPS還是能頂?shù)米〉模窃俑吡司蜎]辦法了,那這個(gè)數(shù)據(jù)隨便搞個(gè)熱銷商品的秒殺可能都不止了。
大量的請求進(jìn)來,我們需要考慮的點(diǎn)就很多了,緩存雪崩,緩存擊穿,緩存穿透這些我之前提到的點(diǎn)都是有可能發(fā)生的,出現(xiàn)問題打掛DB那就很難受了,活動失敗用戶體驗(yàn)差,活動人氣沒了,最后背鍋的還是開發(fā)。
超賣:
但凡是個(gè)秒殺,都怕超賣,我這里舉例的只是尿不濕,要是換成100個(gè)華為MatePro30,商家的預(yù)算經(jīng)費(fèi)賣100個(gè)可以賺點(diǎn)還可以造勢,結(jié)果你寫錯(cuò)程序多賣出去200個(gè),你不發(fā)貨用戶投訴你,平臺封你店,你發(fā)貨就血虧,你怎么辦?
那最后只能殺個(gè)開發(fā)祭天解氣了,秒殺的價(jià)格本來就低了,基本上都是不怎么賺錢的,超賣了就恐怖了呀,所以超賣也是很關(guān)鍵的一個(gè)點(diǎn)。
惡意請求:
你這么低的價(jià)格,假如我搶到了,我轉(zhuǎn)手賣掉我不是血賺?就算我不賣我也不虧啊,那用戶知道,你知道,別的別有用心的人(黑客、黃牛…)肯定也知道的。
那簡單啊,我知道你什么時(shí)候搶,我搞個(gè)幾十臺機(jī)器搞點(diǎn)腳本,我也模擬出來十幾萬個(gè)人左右的請求,那我是不是意味著我基本上有80%的成功率了。
真實(shí)情況可能遠(yuǎn)遠(yuǎn)不止,因?yàn)闄C(jī)器請求的速度比人的手速往往快太多了,在貴州的敖丙我每年回家搶高鐵票都是秒光的,我也不知道有沒有黃牛的功勞,我要Diss你,黃牛。杰倫演唱會門票搶不到,我也Diss你。
Tip:科普下,小道消息了解到的,黃牛的搶票系統(tǒng),比國內(nèi)很多小公司的系統(tǒng)還吊很多,架構(gòu)設(shè)計(jì)都是頂級的,我用頂配的服務(wù)加上頂配的架構(gòu)設(shè)計(jì),你還想看演唱會?還想回家?
不過不用黃牛我回家都難,我們云貴川跟我一樣要回家過年的仔太多了555!
鏈接暴露:
前面幾個(gè)問題大家可能都很好理解,一看到這個(gè)有的小伙伴可能會比較疑惑,啥是鏈接暴露呀?
相信是個(gè)開發(fā)同學(xué)都對這個(gè)畫面一點(diǎn)都不陌生吧,懂點(diǎn)行的仔都可以打開谷歌的開發(fā)者模式,然后看看你的網(wǎng)頁代碼,有的就有URL,但是我寫VUE的時(shí)候是事件觸發(fā)然后去調(diào)用文件里面的接口看源碼看不到,但是我可以點(diǎn)擊一下查看你的請求地址啊,不過你好像可以對按鈕在秒殺前置灰。
不管怎么樣子都有危險(xiǎn),撇開外面的所有的東西你都擋住了,你賣這個(gè)東西實(shí)在便宜得過分,有誘惑力,你能保證開發(fā)不動心?開發(fā)知道地址,在秒殺的時(shí)候自己提前請求。。。(開發(fā):怎么TM又是我)
數(shù)據(jù)庫:
每秒上萬甚至十幾萬的QPS(每秒請求數(shù))直接打到數(shù)據(jù)庫,基本上都要把庫打掛掉,而且你服務(wù)不單單是做秒殺的還涉及其他的業(yè)務(wù),你沒做降級、限流、熔斷啥的,別的一起掛,小公司的話可能全站崩潰404。
反正不管你秒殺怎么掛,你別把別的搞掛了對吧,搞掛了就不是殺一個(gè)程序員能搞定的。
程序員:我TM好難啊!
問題都列出來了,那怎么設(shè)計(jì),怎么解決這些問題就是接下去要考慮的了,我們對癥下藥。
服務(wù)單一職責(zé):
設(shè)計(jì)個(gè)能抗住高并發(fā)的系統(tǒng),我覺得還是得單一職責(zé)。
什么意思呢,大家都知道現(xiàn)在設(shè)計(jì)都是微服務(wù)的設(shè)計(jì)思想,然后再用分布式的部署方式
也就是我們下單是有個(gè)訂單服務(wù),用戶登錄管理等有個(gè)用戶服務(wù)等等,那為啥我們不給秒殺也開個(gè)服務(wù),我們把秒殺的代碼業(yè)務(wù)邏輯放一起。
單獨(dú)給他建立一個(gè)數(shù)據(jù)庫,現(xiàn)在的互聯(lián)網(wǎng)架構(gòu)部署都是分庫的,一樣的就是訂單服務(wù)對應(yīng)訂單庫,秒殺我們也給他建立自己的秒殺庫。
至于表就看大家怎么設(shè)計(jì)了,該設(shè)置索引的地方還是要設(shè)置索引的,建完后記得用explain看看SQL的執(zhí)行計(jì)劃。(不了解的小伙伴也沒事,MySQL章節(jié)我會說的)
單一職責(zé)的好處就是就算秒殺沒抗住,秒殺庫崩了,服務(wù)掛了,也不會影響到其他的服務(wù)。(強(qiáng)行高可用)
秒殺鏈接加鹽:
我們上面說了鏈接要是提前暴露出去可能有人直接訪問url就提前秒殺了,那又有小伙伴要說了我做個(gè)時(shí)間的校驗(yàn)就好了呀,那我告訴你,知道鏈接的地址比起頁面人工點(diǎn)擊的還是有很大優(yōu)勢。
我知道url了,那我通過程序不斷獲取最新的北京時(shí)間,可以達(dá)到毫秒級別的,我就在00毫秒的時(shí)候請求,我敢說絕對比你人工點(diǎn)的成功率大太多了,而且我可以一毫秒發(fā)送N次請求,搞不好你賣100個(gè)產(chǎn)品我全拿了。
那這種情況怎么避免?
簡單,把URL動態(tài)化,就連寫代碼的人都不知道,你就通過MD5之類的加密算法加密隨機(jī)的字符串去做url,然后通過前端代碼獲取url后臺校驗(yàn)才能通過。
暖男我呢,又準(zhǔn)備了一個(gè)簡單的url加密給大家嘗嘗鮮,還不點(diǎn)個(gè)贊?
Redis集群:
之前不是說單機(jī)的Redis頂不住嘛,那簡單多找?guī)讉€(gè)兄弟啊,秒殺本來就是讀多寫少,那你們是不是瞬間想起來我之前跟你們提到過的,Redis集群,主從同步、讀寫分離,我們還搞點(diǎn)哨兵,開啟持久化直接無敵高可用!
Nginx:
Nginx大家想必都不陌生了吧,這玩意是高性能的web服務(wù)器,并發(fā)也隨便頂幾萬不是夢,但是我們的Tomcat只能頂幾百的并發(fā)呀,那簡單呀負(fù)載均衡嘛,一臺服務(wù)幾百,那就多搞點(diǎn),在秒殺的時(shí)候多租點(diǎn)流量機(jī)。
Tip:據(jù)我所知國內(nèi)某大廠就是在去年春節(jié)活動期間租光了亞洲所有的服務(wù)器,小公司也很喜歡在雙十一期間買流量機(jī)來頂住壓力。
這樣一對比是不是覺得你的集群能頂很多了。
惡意請求攔截也需要用到它,一般單個(gè)用戶請求次數(shù)太夸張,不像人為的請求在網(wǎng)關(guān)那一層就得攔截掉了,不然請求多了他搶不搶得到是一回事,服務(wù)器壓力上去了,可能占用網(wǎng)絡(luò)帶寬或者把服務(wù)器打崩、緩存擊穿等等。
資源靜態(tài)化:
秒殺一般都是特定的商品還有頁面模板,現(xiàn)在一般都是前后端分離的,所以頁面一般都是不會經(jīng)過后端的,但是前端也要自己的服務(wù)器啊,那就把能提前放入cdn服務(wù)器的東西都放進(jìn)去,反正把所有能提升效率的步驟都做一下,減少真正秒殺時(shí)候服務(wù)器的壓力。
按鈕控制:
大家有沒有發(fā)現(xiàn)沒到秒殺前,一般按鈕都是置灰的,只有時(shí)間到了,才能點(diǎn)擊。
這是因?yàn)榕麓蠹以跁r(shí)間快到的最后幾秒秒瘋狂請求服務(wù)器,然后還沒到秒殺的時(shí)候基本上服務(wù)器就掛了。
這個(gè)時(shí)候就需要前端的配合,定時(shí)去請求你的后端服務(wù)器,獲取最新的北京時(shí)間,到時(shí)間點(diǎn)再給按鈕可用狀態(tài)。
按鈕可以點(diǎn)擊之后也得給他置灰?guī)酌耄蝗凰粯釉陂_始之后一直點(diǎn)的。你敢說你們秒殺的時(shí)候不是這樣的?
限流:
限流這里我覺得應(yīng)該分為前端限流和后端限流。
前端限流:這個(gè)很簡單,一般秒殺不會讓你一直點(diǎn)的,一般都是點(diǎn)擊一下或者兩下然后幾秒之后才可以繼續(xù)點(diǎn)擊,這也是保護(hù)服務(wù)器的一種手段。
后端限流:秒殺的時(shí)候肯定是涉及到后續(xù)的訂單生成和支付等操作,但是都只是成功的幸運(yùn)兒才會走到那一步,那一旦100個(gè)產(chǎn)品賣光了,return了一個(gè)false,前端直接秒殺結(jié)束,然后你后端也關(guān)閉后續(xù)無效請求的介入了。
Tip:真正的限流還會有限流組件的加入例如:阿里的Sentinel、Hystrix等。我這里就不展開了,就說一下物理的限流。
庫存預(yù)熱:
秒殺的本質(zhì),就是對庫存的搶奪,每個(gè)秒殺的用戶來你都去數(shù)據(jù)庫查詢庫存校驗(yàn)庫存,然后扣減庫存,撇開性能因數(shù),你不覺得這樣好繁瑣,對業(yè)務(wù)開發(fā)人員都不友好,而且數(shù)據(jù)庫頂不住啊。
開發(fā):你tm總算為我著想一次了。
那怎么辦?
我們都知道數(shù)據(jù)庫頂不住但是他的兄弟非關(guān)系型的數(shù)據(jù)庫Redis能頂啊!
那不簡單了,我們要開始秒殺前你通過定時(shí)任務(wù)或者運(yùn)維同學(xué)提前把商品的庫存加載到Redis中去,讓整個(gè)流程都在Redis里面去做,然后等秒殺介紹了,再異步的去修改庫存就好了。
但是用了Redis就有一個(gè)問題了,我們上面說了我們采用主從,就是我們會去讀取庫存然后再判斷然后有庫存才去減庫存,正常情況沒問題,但是高并發(fā)的情況問題就很大了。
這里我就不畫圖了,我本來想畫圖的,想了半天我覺得語言可能更好表達(dá)一點(diǎn)。
多品幾遍!!!就比如現(xiàn)在庫存只剩下1個(gè)了,我們高并發(fā)嘛,4個(gè)服務(wù)器一起查詢了發(fā)現(xiàn)都是還有1個(gè),那大家都覺得是自己搶到了,就都去扣庫存,那結(jié)果就變成了-3,是的只有一個(gè)是真的搶到了,別的都是超賣的。咋辦?
Lua:
之前的文章就簡單的提到了他,我今天就多一定點(diǎn)篇幅說一下吧。
Lua 腳本功能是 Reids在 2.6 版本的最大亮點(diǎn), 通過內(nèi)嵌對 Lua 環(huán)境的支持, Redis 解決了長久以來不能高效地處理 CAS (check-and-set)命令的缺點(diǎn), 并且可以通過組合使用多個(gè)命令, 輕松實(shí)現(xiàn)以前很難實(shí)現(xiàn)或者不能高效實(shí)現(xiàn)的模式。
Lua腳本是類似Redis事務(wù),有一定的原子性,不會被其他命令插隊(duì),可以完成一些Redis事務(wù)性的操作。這點(diǎn)是關(guān)鍵。
知道原理了,我們就寫一個(gè)腳本把判斷庫存扣減庫存的操作都寫在一個(gè)腳本丟給Redis去做,那到0了后面的都Return False了是吧,一個(gè)失敗了你修改一個(gè)開關(guān),直接擋住所有的請求,然后再做后面的事情嘛。
限流&降級&熔斷&隔離:
這個(gè)為啥要做呢,不怕一萬就怕萬一,萬一你真的頂不住了,限流,頂不住就擋一部分出去但是不能說不行,降級,降級了還是被打掛了,熔斷,至少不要影響別的系統(tǒng),隔離,你本身就獨(dú)立的,但是你會調(diào)用其他的系統(tǒng)嘛,你快不行了你別拖累兄弟們啊。
削峰填谷:
一說到這個(gè)名詞,很多小伙伴就知道了,對的MQ,你買東西少了你直接100個(gè)請求改庫我覺得沒問題,但是萬一秒殺一萬個(gè),10萬個(gè)呢?服務(wù)器掛了,程序員又要背鍋的。
Tip:可能小伙伴說我們業(yè)務(wù)達(dá)不到這個(gè)量級,沒必要。但是我想說我們寫代碼,就不應(yīng)該寫出有邏輯漏洞的代碼,至少以后公司體量上去了,別人一看居然不用改代碼,一看代碼作者是碼農(nóng)之屋?有點(diǎn)東西!
你可以把它放消息隊(duì)列,然后一點(diǎn)點(diǎn)消費(fèi)去改庫存就好了嘛,不過單個(gè)商品其實(shí)一次修改就夠了,我這里說的是某個(gè)點(diǎn)多個(gè)商品一起秒殺的場景,像極了雙十一零點(diǎn)。
總結(jié)
到這里我想我已經(jīng)基本上把該考慮的點(diǎn)還有對應(yīng)的解決方案也都說了一下,不知道還有沒有沒考慮到的,但是就算沒考慮到我想我這個(gè)設(shè)計(jì),應(yīng)該也能撐住一個(gè)完整的秒殺流程。
最后我就畫個(gè)完整的流程圖給大家收個(gè)尾吧!
Tip:這個(gè)鏈路還是比較簡單的,很多細(xì)節(jié)的點(diǎn)全部畫出來就太復(fù)雜了,我上面已經(jīng)提到了所有的注意點(diǎn)了,大家都看看,真正的秒殺有比我這個(gè)簡單的,也有比我這個(gè)復(fù)雜N倍的,之前的電商老東家就做的很高級,有機(jī)會也可以跟你們探討,不過是面試嘛,我就給思路,讓你理解比較關(guān)鍵的點(diǎn)。秒殺這章我腦細(xì)胞死了很多,考慮了很多個(gè)點(diǎn),最后還是出來了,忍不住給自己點(diǎn)贊!
(這章是真的不要白嫖,每次都看了不點(diǎn)贊,你們想白嫖我么?你們好壞喲,不過我好喜歡)
總結(jié)
我們玩歸玩,鬧歸鬧,別拿面試開玩笑。
秒殺不一定是每個(gè)同學(xué)都會問到的,至少肯定沒Redis基礎(chǔ)那樣常問,但是一旦問到,大家一定要回答到點(diǎn)上。
至少你得說出可能出現(xiàn)的情況,需要注意的情況,以及對于的解決思路和方案。
最后就是需要對整個(gè)鏈路比較熟悉,注意是一個(gè)完整的鏈路,前端怎么設(shè)計(jì)的呀,網(wǎng)關(guān)的作用呀,怎么解決Redis的并發(fā)競爭啊,數(shù)據(jù)的同步方式呀,MQ的作用啊。
(提到MQ又是一整條的知識鏈路,什么異步、削峰、解耦等等,所以面試,我們還是不打沒有把握的勝仗)






