《大前端進階 Node.js》系列 雙十一秒殺系統(進階必看)

接水怪發表於2020-03-18

前言

Coding 應當是一生的事業,而不僅僅是 30 歲的青春?
本文已收錄 Github https://github.com/ponkans/F2E,歡迎 Star,持續更新?

這篇 Node.js 的文章接水怪很用心,也很硬核,相信能看完的都有點東西!!!


每篇文章都希望你能收穫到東西,這篇根據搶口罩的實際場景出發,逐層對一個 Node 高併發的系統進行分析,其中 Node 服務層會講的詳細一些,希望你看完,能夠有這些收穫:

  • Node 生態已經越來越好,一些高效能的 Web 業務場景,是完全可以用 Node 來做的
  • 前端應該不止於前端,學習一些服務端的知識,不僅僅單方面的說是為了做一些全棧的系統,更多的是讓現有的前端可以去做更多的事情,去嘗試更多的可能
  • 能夠獨立去設計一些東西,可以是一個微型全棧的系統,也可以是前端工程化中某個環節的工具

場景分析

真的打心底對那些在一線的工作人員點贊,接水怪的父親目前仍在一線湖北提供生活物資的運輸,我想說,爸,你真的很棒,怪怪為您感到自豪與驕傲!

想搶個口罩怎麼就這麼難!接水怪大學之前是湖北的,想給湖北的朋友搶個口罩快遞過去,結果顯而易見,沒有搶到…

於是,接水怪痛定思痛,下定決心對口罩秒殺系統架構一探究竟,雖然業界大部分的這種場景應該都是基於 Java 實現的,但是怪怪我決定嘗試從 Node.js 的方向,配合業界一些成熟的中介軟體來分析一下整個系統的架構,以及一些常見的問題。

關於 Node.js 如何實現高併發的原理,怪怪往期的文章有寫哈。

寫之前跟好兄弟丙丙也請教了不少後端側的東西,對,就是你們熟悉的那個男人,號稱自己在網際網路苟且偷生的那個敖丙

怪怪在這裡將前後端一併分享給大家,畢竟 Node 的生態環境日益增強,前端側能做的東西也越來越多。

比如你想在公司自己做一個基於 Node 的前端釋出系統,也會涉及到 db,快取,訊息中介軟體這些東西。

接下來,讓我們請出今天的主角兒,噹噹噹當~~ 它就是低調奢華有內涵的口罩秒殺系統架構圖,後面的內容會基於這個架構圖來擺會兒龍門陣。

擺龍門陣,怪怪方言,也就是怪怪想跟大家聊會兒天的意思啦~)

秒殺型別的業務為什麼難做

秒殺,秒殺,顧名思義就是一個短時間內的高流量操作,是一個天然高併發的場景,換句話說,讀寫衝突十分嚴重。

而通常秒殺型別的業務,價格都比較誘人,這就引發了另外一個薅羊毛的問題,有不少黃牛會在這個時候趁機撈一筆,想必每年春節回家,大家都深有感觸吧,還沒來得及輸完 12306 的高智商驗證碼,票已經沒有了,哦豁~~

"哦豁",怪怪的方言,很無奈,很驚訝的意思

試想幾十萬,上百萬的流量直接打到 DB?

世界突然安靜,不得不再一次發出熟悉的感慨,哦豁~

因此,秒殺的核心就在於請求是真實請求的前提下,處理好高併發以及資料庫存扣減的問題。

接下來,就讓我們逐層來看看是怎麼做的~

業務規則層面

針對流量特別大的場景,可以分時分段開展活動,原來統一 10 點搶口罩,現在 6 點,6 點半,7 點,…每隔半個小時進行一次活動,達到流量攤勻的效果。

前端層面

APP / H5 / 小程式 / PC

針對平民老百姓

前端側會進行一個按鈕置灰的操作,當你點選完一次之後,按鈕會變灰,防止使用者重複提交搶口罩的請求。

針對程式設計師

想必你一定會說,這還不簡單?抓個包,寫個定時指令碼,一到時間,for 迴圈自動打請求不就好了?

我只能說,小夥子,你很優秀,有的場景確實是闊以滴,但是你別慌,接著往下看↓↓↓

彆著急,剛剛就是讓你皮一下,哦豁~

針對上面這種情況,後端 controller 層會進行處理,簡單來說就是對同一個使用者搶口罩的請求進行校驗,對於同一個使用者在短時間內傳送的大量請求進行次數限制,以及請求去重處理~

但怪怪我諮詢了風控的朋友,現在企業中大部分場景,後端是不做這個特殊處理,而是讓風控團隊來處理,下面所謂黑客的部分會稍微細說一下。

道高一尺,魔高一丈,接下來請出我們的終極大魔王,高階 Hacker!!

針對更加高階的 Hacker 們

比如一些高階 Hacker,控制了 30w 個 Dorking,也就是這些人手上有 30w 的 uid,如果他們同時發動手上 30w 的 Dorking 來搶口罩,咋辦?

此時,再一次陷入了尷尬~ 哦豁~~

這個問題,怪怪我諮詢了安全、以及風控的朋友,簡單跟大家分享一下

首先,安全針對秒殺,沒做啥特殊的處理。

一般來講,秒殺類工作主要在風控這邊,對於那種利用機器或者指令碼瘋狂重新整理的,QPS 異常高的,換句話說就是短時間內流量異常高滴的使用者,會直接給他彈一個滑塊(滑動驗證,應該大家都遇到過這種情況),這樣可以大大提高那些刷請求批量操作的成本,甚至能夠遏制他們的行為。

同時風控也會根據一系列的規則(通過這些規則來判定這個 uid 也就是這個使用者是否符合要求,不要問我具體規則,怪怪我還想繼續寫文章,不想進去~~),對於那些不符合平臺下單要求的 Dorking ,直接進行請求攔截,甚至有的會加入黑名單,直接取消掉這個使用者的相關許可權。

寫到這裡,我想說,風控、安全團隊,你們還是有點東西!!

Nginx 層

實際上真正的企業架構中,在 Nginx 的上面一層還會有一層 lvs ,這裡不展開講,簡單解釋就是能夠在網路第 4 層對 Nginx 服務的 ip 進行負載均衡

雖然上面我們攔截了惡意請求,減少了部分流量,但秒殺真實的使用者也超多的啊,你想想這次有多少人在搶口罩!

所以我們我們還是要搞負載均衡~~

我們先簡單解釋一下叢集跟負載均衡是個什麼玩意兒,哦豁~

叢集嘛,簡單來講就是我們不是有秒殺的 Node 服務嘛,那一臺機器是不是有可能會掛掉,流量太大,單臺機器根本扛不住,咋辦?

加機器唄,一臺會被打掛,那我們多搞幾臺,不就變強啦?俗話說,1 根牙籤一碰就斷,10 根牙籤…… 咦,好像也是一碰就斷,hhhh,哦豁~

負載均衡是嘛玩意兒嘛?剛上面不是說了叢集就是加機器嘛,那現在機器雖然變多了,來了一個請求,具體派哪個機器去處理,是不是得有一套規則嘛,並且這套規則要讓請求分發的比較合理,不然就失去了叢集的意義了撒~

Nginx 層下面是基於 Node 的 service 層,也就是業務邏輯會在這裡進行處理,實際上 Nginx 在這裡主要做了兩件事:

  • 對於前端打過來的真實的搶口罩請求,在 Nginx 這裡進行請求的分發,打到 Node 叢集的某一個機器上
  • 健康檢測,Node 叢集的機器同樣有可能掛掉,所以會利用 Nginx 進行檢測,發現掛了的機器,會幹掉重啟,保證叢集的高可用。檢測有兩種機制,被動檢測跟主動檢測,不展開說,後面會出一篇 Nginx 原理與實戰的文章,像跨域啊,專案部署啊,自己搞個代理啥的啊,都可以用 Nginx 來搞,哦豁~

Node Service 層

這一層我會說的細一些,讓我們挨個來擺下龍門陣吧~~

首先,為什麼選擇 Node 來做 service 層

想必大家都知道服務模型是經歷了 4 個階段滴,同步、複製程式、多執行緒、以及事件驅動。

不清楚的同學需要補補作業啦~

多執行緒目前業界以 Java 為首,效能啥的就不說了,各種雙十一已經證明這位兄臺是個狠人。

但是,怪怪我覺得,雖然多執行緒之間可以共享資料,記憶體可以充分利用,並且利用執行緒池可以較少建立和銷燬執行緒,節省開銷。但是,還是存在一些問題,比如:

  • 作業系統在切換執行緒的時候,同樣需要切換執行緒上下文,如果執行緒數量太多,切換執行緒會花費大量時間
  • 多執行緒之間的資料一致性問題,各種加鎖的神仙操作,也容易出問題

那能不能讓我們人見人愛滴小闊愛,Node 小朋友,來嘗試搞一下?

Node 是基於事件驅動模型滴,相信前端同學都知道事件驅動,後端同學可能有點懵,但是作為後端同學,Node 你沒搞過,Nginx 你總用過哈,Node 跟 Nginx 都是基於事件驅動模型滴~

並且,Node 是單執行緒,採用單執行緒可以避免不必要的記憶體開銷,以及上下文切換。

跟多執行緒 Java 比起來,好像 Node 也有自己的優勢呢~

我知道,已經有不少同學迫不及待要接著問我了,尤其是後端同學心裡肯定在想,你 TM 逗我呢,你一個單執行緒還能來搞高併發,多核 CPU 怎麼利用?

畢竟隔行如隔山,後端大佬對我們 Node 有誤解,也是情有可原,就跟很多前端覺得 Java 用型別檢測是很麻煩的,不如我們 JS 靈活,是一個道理~

如果我們 Node 解決了多核 CPU 的資源利用問題,再加上 Node 非同步非阻塞的特性,帶來的效能上的提升應該是很不錯滴,並且也沒有複雜的多執行緒啊,加鎖這一類心累的問題。

關於 Node 單執行緒如何實現高併發,可以檢視往期文章哦?

Node Master-Worker 模式

我們順著架構圖,一步步來分析~

先看這裡,說好的單程式,單執行緒呢,這是個什麼鬼?

彆著急,這是 Node 程式模型中著名滴 Master-Worker 模式哦~

還不是因為單執行緒,無法利用多核 CPU 嘛,我們的小闊愛 Node 提供了 child_process 模組,或者說 cluster 模組也行,可以利用 child_process 模組直接建立子程式。

說到這裡, HTML5 提出的 Web Worker ,方式大同小異,解決了 JavaScript 主執行緒與 UI 渲染執行緒互斥,所引發的長時間執行 JavaScript 導致 UI 停頓不響應的問題。

cluster 模組:實際上就是 child_process 模組跟其它模組的組合
另外申明一點:fork 執行緒開銷是比較大的,要謹慎使用,並且我們 fork 程式是為了利用 CPU 資源,跟高併發沒啥大關係

這樣看來,多核 CPU 的資源利用問題,好像得到了解決,雖然看起來方式稍顯粗暴~

我們再次迴歸到上面的主、子程式的架構圖,接著談談主程式與子程式通訊的問題,現在一個口罩秒殺請求過來了,Node 主程式,子程式這裡是怎麼進行處理的呢?

主-子程式通訊

想必大家都知道 IPC,也就是程式間通訊,首先要申明的是實現 IPC 的方式不止一種,比如共享記憶體啊、訊號量啊等等~~

而 Node 是基於管道技術實現滴,管道是啥?

在 Node 中管道只是屬於抽象層面的一個叫法而已,具體的實現都扔給一個叫 libuv 的傢伙去搞啦~之前的文章有講到 Node 的 3 層架構,libuv 是在最下層滴哦,並且大家都知道 Node 可以跨平臺嘛,libuv 會針對不同的平臺,採用不同的方式實現程式間的通訊。

ok,我們上面大致說完了理論部分,是不是要實戰一把了?

客戶端請求代理轉發

一把大叉叉什麼鬼?說明通過代理請求轉發這種方式是不太友好滴~

那我們具體分析一下,為什麼不友好,怎麼改進?

首先我們需要明確一點,作業系統有一個叫檔案描述符的東西,這個涉及到作業系統核心的一些小知識點,並且作業系統的檔案描述符是有限的,維護起來也是需要成本滴,因此不能鋪張浪費~

那我們分析一下上面的流程,這些口罩請求打到主程式上面,主程式對這些請求進行代理,轉發到不同埠的子程式上,看起來一切都那麼美好~

並且在主程式這裡,我們還可以繼續進行一層負載均衡,讓每個子程式的任務更加合理。

but,but,接下來是重點!!

前面我們說啦,作業系統的檔案描述符不能鋪張浪費,我們來看看這個代理的方式,有沒有浪費~

首先,需要明確一點。程式每收到一個連線,就會用到一個檔案操作符,所以呢?來,怪怪給你整個當字開頭的排比句!

當客戶端連線到主程式的時候,用掉一個操作符~

當主程式連線到子程式,又用掉一個~

所以嘛,從數量上來看,代理方案浪費掉了整整一倍,這好像不太科學,囊個搞內?

怪怪那兒的方言,“囊個搞”,就是怎麼辦的意思咯~~

魔高一尺,道高一丈~

控制程式碼傳遞-去除代理

Node 在 v0.5.9 引入了程式間傳送控制程式碼的機制,簡單解釋一下,控制程式碼實際上就是一種可以用來標識資源的引用。

通過控制程式碼傳遞,我們就可以去掉代理這種方案。使主程式收到客戶端的請求之後,將這個請求直接傳送給工作程式,而不是重新與子程式之間建立新的連線來轉發資料。

但實際上這一塊還涉及到很多知識點,比如控制程式碼的傳送與還原啊,埠的共同監聽啊等系列問題。

這一塊的具體實現,可以參考 《深入淺出 Node.js 》9.2.3 控制程式碼傳遞

最終,通過控制程式碼傳遞就可以得到我們想要的效果,即不經過代理,子程式直接響應了客戶端的請求。

怪怪我也去研究了一下 Egg.js 在多程式模型和程式間通訊這塊是怎麼做滴,大體架構思路跟上面差不多,不同的點在於, Egg.js 搞了一個叫 Agent 的東西。

對於一些後臺執行的重複邏輯,Egg.js 將它們放到了一個單獨的程式上去執行,這個程式就叫 Agent Worker,簡稱 Agent,專門用來處理一些公共事務,具體細節可以參考 Egg.js 官網。

子程式服務高可用問題

畢竟是秒殺服務,fork 的子程式是可能掛滴,所以我們需要一種機制來保證子程式的高可用。

我知道,你肯定會說,掛了,重啟一個繼續提供服務不就好了?

對,你說的沒錯,我們就先來搞定重啟這一趴~

假如現在某個子程式發生異常啦,哦豁~

那麼,此時這個子程式會立即停止接受新的請求連線,直到所有連線斷開,當這個子程式退出的時候主程式會監聽到一個 exit() 事件,然後主程式重啟一個子程式來進行補償。

也就是這樣一個流程。

小夥子,你很不錯,但是極端情況我們是不是也要考慮一下子?

假如有一天,出現極端情況,你所有的女朋友一夜之間都要跟你分手,是不是開始慌了?(壞笑~~)

如果所有的子程式同時異常了,按照我們上面的處理方式,所有的子程式都停止接收新的請求,整個服務豈不是就會出現瞬時癱瘓的現象了?大部分的新請求就會白白丟掉,想想如果是雙十一,損失得有多大,哦豁~

既然如此,我們就來改進一下。

有的小夥伴可能會說,我知道怎麼搞了!!!

既然不能一直等待著,那就直接強行 kill 幹掉這個程式,然後立馬重啟一個!!

小夥子,你有點激動,不過可以理解,畢竟能想到這裡,你還是有點東西~~ 不過可以更加全面一點

強行 kill 掉,會導致已連線的使用者直接中斷,那使用者也就自然離開啦~ 哦豁~

所以,總結起來我們要解決的就是,如何在不丟失現有使用者請求的基礎上,重啟新的子程式,從而保證業務跟系統的雙重高可用!!!

忽然靈光一現,我們可以這樣搞撒~

上面分析到主程式要等到子程式退出的時候,才會去重啟新的子程式,這就有點意思啦!! 同志,不要等到分手了再去反思自己哪裡做的不對!!! ,只要女朋友一生氣,即使響應,立馬認錯,沒毛病~~~

因此,我們可以在子程式剛出異常的時候,就給主程式發一個自殺訊號,主程式收到訊號後,立即建立新的子程式,這樣上面的問題完美解決!!

從此跟女朋友過上了幸福美好滴生活~

Node 叢集

搞定了上述秒殺服務 Node 主程式,子程式高可用的問題之後,我們就可以搭建 Node 叢集了,進一步提高整個系統的高可用,Node 叢集的負載均衡,文章一開始的 Nginx 層已經講過啦~

至於叢集做多大,用多少臺機器,這些需要根據具體的業務場景來決策。並且一般大型的活動上線之前,企業內部會進行幾輪壓測,簡單來講,就是模擬使用者高併發的流量請求,然後看一下小夥子你搞的這個系統抗不扛得住,靠不靠譜~

但真正的壓測,遠比我說的要複雜,比如壓測中某個環節出了問題,扛不住啦,是直接重啟機器,還是擴容,又或者是降低處理等等,這都是需要綜合考慮滴~

當然,最終的目的也是在保證服務高可用的前提下能給企業節約成本,畢竟加機器擴容是需要成本滴~

同樣的需求,別人要 5 臺機器,你只要 4 臺就能搞定,小夥子,那你就真的有點東西啦,升職加薪指日可待!!~

資料一致性

搞定了上面 Node 服務的各個環節之後,還有一個很重要的問題要解決,我丟!!! 咋還有問題~~

首先,明確一個知識點,Node 多個程式之間是不允許共享資料滴~

這就是最後一個非常重要的問題,資料共享

實際業務中,就拿我們今天的口罩系統來說,庫存量就是一個典型資料共享的場景,會有多個程式需要知道現在還有多少庫存,不解決好這個問題,就很有可能發生口罩超賣的問題。

超賣,簡單來講就是,怪怪家一共有 200 只口罩等待出售,然後怪怪我在某平臺上發起了一個秒殺搶口罩的活動,由於平臺的資料沒處理好,怪怪我在後臺驚喜的收到了 500 個訂單,超出了 300 個訂單,這就是超賣現象啦~

那這個問題,我們應該如何避免呢,怪怪我有點心累,買個口罩怎麼這麼複雜!

既然 Node 本身不支援資料共享,ok,那我們就用三方的嘛,比如今天的第二個主角~ 噹噹噹當~~ 它就是一位名叫 Redis 的神祕女子,身穿紅色外套,看著還有點內斂~

至於 Redis 這裡如何保持資料一致,放到下面 Redis 的部分一起說。

Redis 層

我們使用 Redis 解決上述遺留的 Node 程式間資料不能共享的問題。

解決問題之前,先簡單介紹一下 Redis ,畢竟,都不認識,怎麼開始戀愛?

Redis 還是有很多知識點滴,具體可檢視 Redis 官網

相信大家在大學的時候都接觸過 SQL Server ,相信你也曾經因為 SQL Server 那詭異的環境曾頭痛~

但大部分學校,應該都是沒有 Redis 這門課滴,如果有,怪怪我只能說,朋友,你的學校有點東西!!

步入正題,Redis 是一個在記憶體中進行資料結構儲存的系統,可以用作資料庫、快取和訊息中介軟體,是 key-value 的形式來進行資料的讀寫。簡單理解就是,我們可以利用 Redis 加快取減輕 db 的壓力,提升使用者讀寫的速度。

大家要明確的是,秒殺的本質就是對庫存的搶奪,如果每個從上層 Node 服務打過來的請求,都直接去 db 進行處理,

拋開最大的效能問題先不說,你不覺得這樣好繁瑣,對開發人員以及後期的維護都很不友好嘛?

那咋辦

上面已經說啦,直接搞到 MySQL 扛不住,你可以找她閨蜜 Redis 嘛,寫一個定時指令碼,在秒殺還未開始之前,就把當前口罩的庫存數量寫入 Redis,讓 Node 服務的請求從 Redis 這裡拿資料進行處理,然後非同步的往 kafka 訊息佇列裡面寫入搶到口罩使用者的訂單資訊(這一塊具體的放到後面 kafka 訊息佇列部分分析)。

那這裡就關聯上了我們前面提到的問題,資料一致性問題,如何保證 Node 服務從你 Redis 拿到的庫存量是沒有問題滴?

如果直接按正常的邏輯去寫,搶到口罩,Redis 中庫存 count -1,這種方式看起來是沒有問題滴,但是我們來思考這樣一個場景。

比如倉庫裡面還有最後 1 個口罩,現在光頭強發了一個請求過來,讀了一下 Redis 的資料,count 為 1,然後點選下單,count - 1 。但如果此時 count 正在執行 -1 的操作的時候(此時 count 依然是 1 ),熊大哥哥一看這形式不對,也開始搶口罩,一個查庫存量的請求再一次打了過來,發現 Redis 中庫存量仍然為 1 ,然後點選下單。

按上述的流程,就會發生文章一開頭提到的超賣現象,1 只口罩,你給我下了兩個單?!!!

這又咋辦

我們可以加事務嘛!!

事務可以一次執行多個命令,並且有兩個很重要的特性~

  • 事務中的所有命令都會序列化、按順序地執行。事務在執行的過程中,不會被其他客戶端傳送來的命令請求所打斷
  • 事務是一個原子操作:事務中的命令要麼全部被執行,要麼全部都不執行。

這裡直接引用 Redis 的官方示例來解決超賣問題,官方示例講的是執行 +1 的操作,我們這裡只需要修改成 -1 就 ok。具體可參考官方文件。

這樣一來,Node 資料共享,庫存減叩的問題就搞定了,Redis 小姐姐,你有點東西!!~

那如果單臺 Redis 扛不住怎麼辦,我們可以對 Redis 進行叢集嘛,主從同步這一系列的操作,然後再搞點哨兵持久化也搞上,那你這個秒殺直接無敵!!!

後續有時間,會寫一個基於 Node.js 的 Redis 實戰與原理剖析?,之前怪怪寫過一個視覺化頁面搭建系統,其中各個模組在後臺進行渲染的時候,為了提高渲染速度,就用到了 Redis 進行快取記憶體。

快取這一趴告一段落,下面接著說說訊息佇列~

kafka 訊息佇列層

如果將訂單資料一次性全部寫入 db,效能不好,所以會先往訊息佇列裡面存一下,當然訊息佇列不僅僅是起到了這個作用,像什麼應用的解耦,也可以基於 kafka 來做一層封裝處理。

先簡單說一下訊息佇列,不光秒殺,其它場景的使用大同小異。

kafka 訊息佇列就是基於生產者-消設計模式滴,按具體場景與規則,針對上層請求讓生產者往佇列的末尾新增資料,讓多個消費者從佇列裡面依次讀取資料然後自行處理。

結合到我們的秒殺場景就是,對訂單進行分組儲存管理,然後讓多個消費者來進行消費,也就是把訂單資訊寫入 db。

這裡附上一張經典的訊息佇列的圖,有興趣的小夥伴可以深入去了解 kafka,還是有點東西。

MySQL 資料庫層

資料庫這塊不多說,資料儲存的地方,最終的訂單資訊會寫到 MySQL 中進行持久化儲存。

簡單模擬這個系統的話,表結構就搞到最簡化就行。

CREATE TABLE IF NOT EXISTS `seckill`(
   `id` INT UNSIGNED AUTO_INCREMENT,
   `date` TIMESTAMP,
   `stock` INT UNSIGNED,
   PRIMARY KEY ( `id` )
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
複製程式碼

深入學習 MySQL,還是有些東西滴,查詢優化啊,索引怎麼用啊,底層資料結構啊,怎麼去設計表啊等等一些列問題都可以去探究~

相信前段時間搞的沸沸揚揚滴刪庫跑路事件,大家也都聽說了~~

順便提一嘴,很多小夥伴說,我想搞一下前端工程化,我想搞幾個減少開發流程的系統,我想…

就舉個很簡單的例子,企業都有線下、預發、線上這一套環境,你想開發一款 host 切換工具來切各個不同的環境,兩種方式:

  • 直接基於 Electron 寫一個本地 host 檔案(etc/hosts)讀寫的工具,看似也沒什麼毛病
  • 但既然是整個公司用,那思維就不要太侷限啦,如果僅僅開發一個本地 host 檔案讀寫的工具,那沒什麼意思,大家直接用開源的不就好了,還節省成本,不用開發。
  • 做成可配置滴,公共的 host 配置檔案存到 db,並且支援本地自定義配置,這就很棒啊,全公司統一。假如哪天預發的機器 ip 變了,那麼只用去 host 工具管理後臺更新一下,全公司所有的小夥伴就可以一鍵更新啊,是不是很爽??

怪怪之前是維護我們公司的這個 host 小工具滴,後面計劃自己寫一套開源出來。

什麼,這麼個破玩意兒你還要開源?

舉上面的例子,就是想說,Node 生態越來越好,我們前端能做的事情也越來越多,我們有義務去接觸更多的東西,尋找更多前端的突破口~

狼叔那一篇在 cnode 社群置頂的文章,就寫的非常贊!!~~~

踩過的坑-經驗分享

能夠看到這裡,說明你有點東西,整個系統的各個層都講了一下,我估計現在讓你自己來搞一個秒殺系統,也問題不大了~

怪怪我基於文章開頭的架構圖,自己搞了一個低配版的 Node 秒殺系統,在這裡把踩過的一些小坑跟大家分享一下,畢竟踩過的坑,不希望大家踩第二次!

首先,如果大家自己在本地搞這個系統,可能乍一看,又有 Node,又有 Nginx,又有 Redis ,又有 kafka,而且管理 kafka 還要用 zookeeper,感覺這也太麻煩了,可能看到這個環境搭建都望而卻步了~。

淡定,淡定!!

我們可以用 docker 來搞嘛,這樣就很輕量並且很方便管理,沒用過的小夥伴強烈建議去學習一下~
使用 docker 來搞,上面說的幾個環境,一個配置檔案就搞定,簡單示意一下:

services:
  zk:
    image: wurstmeister/zookeeper
    ports:
      - "2181:2181"
  kfk:
    image: wurstmeister/kafka
    ports:
      - "9092:9092"
    environment:
      KAFKA_ADVERTISED_HOST_NAME: 172.17.36.250
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
    volumes:
      - ./docker.sock:/var/run/docker.sock
  redis:
    image: redis
    ports:
      - "6379:6379"
  mysql:
    image: mysql
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: 'password'
複製程式碼
遇到過坑的小總結
  • 用 docker 搭建環境,避免自己去裝那些不必要的環境,比如 MySQL。

  • 使用 Docker Compose 定義和執行多容器的 Docker 應用程式,便於容器的集中配置管理。

  • 刪除 Docker 容器,容器裡面的資料還是會在你物理機上面,除非你手動去清理。

  • kafka-node 這個 npm 包,最新的版本用法相比老版本有一些更新,比如老版本建立一個 kafkaClient 的寫法是 new kafka.Client(); 但新版本現在已經是 new kafka.KafkaClient(); 這種寫法了

  • kafka 的 docker 映象,官方提供的 docker-compose 示例未指定埠對映關係,需要自行處理一下對映關係

  • Egg.js 首次在連線 MySQL 的 docker 容器的時候,會出現 Client does not support authentication protocol requested by server; 的異常,修改一下密碼就 ok。

    ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_new_password';
    FLUSH PRIVILEGES;
    複製程式碼

被後端朋友靈魂 3 問的小分享

如果是一家很小的公司,由於資金問題機器不夠用來叢集怎麼辦

萬一機器不夠,只能放棄部分請求,原則上還是要已保護系統為核心,不然所有的請求都失敗,就更不理想


kafka 佇列這邊如果發生異常處理失敗怎麼反饋給使用者

那就給使用者及時反饋說下單失敗,讓使用者重試,都已經搶到口罩了,重新下個單應該問題不大的,再者,這是少數情況~


搶到口罩之後未及時支付或取消這筆訂單,如何對剩餘庫存做及時的處理呢

首先,搶到口罩之後,支付介面會有一個支付的時間提醒,例如,超過 20 分鐘未支出,這筆訂單將被取消。關聯到資料庫裡就會有一個未支出的狀態。如果超過時間,庫存將會重新恢復。

前端側也會給到使用者對應的提示,比如 20 分鐘之後再試試看,說不定又有口罩了喲~

總結

本文已收錄 Github https://github.com/ponkans/F2E,歡迎 Star,持續更新?

怪怪上面寫的也不全,真正的企業實戰,整個鏈路會複雜很多,環環相扣。比如 Node 服務的監控報警啊,各種異常處理啊等等~~

後續會對這篇秒殺中的知識點進行拆分深入探究,比如程式,執行緒這塊從最基本概念到最底層核心的一些知識都會來跟大家分享。

近期會針對 Node.js 寫一個系列,同系列傳送門:


喜歡的小夥伴加個關注,點個贊哦,感恩??

聯絡我 / 公眾號

微信搜尋【接水怪】或掃描下面二維碼回覆”加群“,我會拉你進技術交流群。講真的,在這個群,哪怕您不說話,光看聊天記錄也是一種成長。(阿里技術專家、敖丙作者、Java3y、蘑菇街資深前端、螞蟻金服安全專家、各路大牛都在)。

接水怪也會定期原創,定期跟小夥伴進行經驗交流或幫忙看簡歷。加關注,不迷路,有機會一起跑個步? ↓↓↓

相關文章