為什么 NodeJS 是構(gòu)建微服務(wù)的最佳選擇?
PS:全套前端課程私信我免費(fèi)領(lǐng)取
什么是微服務(wù)
微服務(wù)是一種應(yīng)用架構(gòu),它將每個(gè)應(yīng)用功能都放在自己的服務(wù)中,與其他服務(wù)隔離。這些服務(wù)是松散耦合的,可獨(dú)立部署。
這種架構(gòu)的出現(xiàn)是為了解決舊的 Web 應(yīng)用開(kāi)發(fā)的單體方法。在單體軟件中,所有的東西都是作為一個(gè)單元構(gòu)建的,所有的業(yè)務(wù)邏輯都被歸入一個(gè)廣泛的應(yīng)用。
這種方法使更新代碼庫(kù)的過(guò)程變得復(fù)雜化,因?yàn)樗绊懙秸麄€(gè)系統(tǒng),即使是最小的代碼改動(dòng)也需要構(gòu)建和部署整個(gè)軟件的新版本。此外,哪怕你只想擴(kuò)展應(yīng)用的某個(gè)特定功能,卻需要擴(kuò)展整個(gè)應(yīng)用來(lái)實(shí)現(xiàn)它。
微服務(wù)解決了單體系統(tǒng)所面臨的這些挑戰(zhàn),它將應(yīng)用從一個(gè)整體分割成幾個(gè)小部分。

什么時(shí)候應(yīng)該使用微服務(wù)?
從本質(zhì)上講,微服務(wù)架構(gòu)解決了龐大、復(fù)雜應(yīng)用的快速開(kāi)發(fā)問(wèn)題。
對(duì)于“哪個(gè)更好?”這一問(wèn)題,目前還沒(méi)有通用的答案。答案取決于各種情況,因?yàn)槊恳环N情況都有其好處和缺點(diǎn)。
下面是一些微服務(wù)架構(gòu)的優(yōu)點(diǎn)和缺點(diǎn),你可能對(duì)此已經(jīng)有所了解:
優(yōu)點(diǎn)
語(yǔ)言不可知性:微服務(wù)并不限于特定的編程語(yǔ)言,每個(gè)微服務(wù)都可以用不同的語(yǔ)言來(lái)編寫(xiě),以支持選定的通信協(xié)議。
可擴(kuò)展性:由于微服務(wù)和它的職責(zé)可以由開(kāi)發(fā)者共同承擔(dān),所以如果有一個(gè)大的團(tuán)隊(duì)參與到這個(gè)項(xiàng)目中,應(yīng)用就會(huì)變得更加易于維護(hù)。
無(wú)限迭代:由于開(kāi)發(fā)者不會(huì)被其他組件所束縛,所以在微服務(wù)上迭代會(huì)變得更加簡(jiǎn)單。
單元測(cè)試:由于微服務(wù)是獨(dú)立的應(yīng)用,它的重點(diǎn)是特定的功能,因此,開(kāi)發(fā)者可以很輕松地編寫(xiě)測(cè)試腳本,以驗(yàn)證該特定功能。
缺點(diǎn)
要作為一個(gè)整體來(lái)管理是很困難的:凱撒大帝有一句名言“分而治之”(divide et impera,拉丁語(yǔ)),即使在這里也可以大規(guī)模應(yīng)用,但是要謹(jǐn)慎,因?yàn)檫^(guò)多的活動(dòng)部分會(huì)變得難以管理。
難以追蹤:如果架構(gòu)變得過(guò)于復(fù)雜,微服務(wù)之間的通信渠道會(huì)非常多,出現(xiàn)錯(cuò)誤后會(huì)很難追溯并確定故障點(diǎn)。
需要大量的專業(yè)知識(shí):構(gòu)建和部署微服務(wù)要求非常高的計(jì)劃和協(xié)調(diào)方面的軟技能。
具有挑戰(zhàn)性的測(cè)試:測(cè)試是一把雙刃劍,因?yàn)槲⒎?wù)作為一個(gè)整體更難測(cè)試。集成和端到端的測(cè)試同樣會(huì)有挑戰(zhàn)。
審計(jì)日志:可能更難獲得和調(diào)查。
在架構(gòu)方面,SaaS 微服務(wù)非常適合,因?yàn)槲⒎?wù)是 SaaS 應(yīng)用的一個(gè)不錯(cuò)的選擇。由于這類應(yīng)用想要用戶付錢(qián)買(mǎi)單,那么它就需要提供高可用的服務(wù),因此將軟件分成小塊可以加快恢復(fù)速度。同時(shí),SaaS 應(yīng)用的發(fā)展主要是由其社區(qū)推動(dòng),所以,它也會(huì)受到很多變化的影響,而通過(guò)微服務(wù)和解耦,開(kāi)發(fā)者可以獲得了靈活性,這是單體架構(gòu)無(wú)法提供的。
單體應(yīng)用程序可能難以水平擴(kuò)展,因?yàn)槟惚仨殢?fù)制整個(gè)應(yīng)用程序,如果它依賴于單個(gè)數(shù)據(jù)庫(kù),這個(gè)過(guò)程將變得更加困難。另一邊,微服務(wù)卻可以根據(jù)單個(gè)服務(wù)進(jìn)行擴(kuò)展、復(fù)制或負(fù)載平衡。比如,如果你需要發(fā)送更多的電子郵件,你只需要擴(kuò)展負(fù)責(zé)電子郵件功能的微服務(wù)。今天你有 10 個(gè)用戶,明天你有 1000 個(gè);SaaS 應(yīng)用可以在短時(shí)間內(nèi)維持大規(guī)模的增長(zhǎng),這就是為什么他們的架構(gòu)必須要以最經(jīng)濟(jì)的方式進(jìn)行輕松擴(kuò)展的原因。
這樣還可以減少資源的消耗,因此可以減少賬單。所以,可以肯定地說(shuō),微服務(wù)是 SaaS 企業(yè)架構(gòu)的下一個(gè)階段。
弄清你是否需要微服務(wù)的最好方法是問(wèn)自己:我有關(guān)于單體應(yīng)用的問(wèn)題嗎?如果有的話,或許你應(yīng)該考慮轉(zhuǎn)向微服務(wù)。如果沒(méi)有,那就堅(jiān)持下去——沒(méi)有必要把時(shí)間花在一個(gè)根本不存在的問(wèn)題上。
微服務(wù)通信是如何工作的?
由于服務(wù)之間彼此獨(dú)立,所以與微服務(wù)的通信需要好好選擇。通信協(xié)議的使用不當(dāng)會(huì)造成應(yīng)用的性能下降,大家必須根據(jù)自己應(yīng)用的具體需求來(lái)選擇通信協(xié)議。
有兩種通信方式可以選擇:同步通信和異步通信,這是請(qǐng)求 - 響應(yīng)和基于事件的模式的基礎(chǔ)。

在第一種情況下,即同步方式,客戶端發(fā)送請(qǐng)求并等待響應(yīng)。這種方法有一個(gè)缺陷,那就是它是一個(gè)阻塞模式。但是,如果你有一個(gè)讀操作非常多的應(yīng)用時(shí),那就不一定了,因?yàn)槟愕膽?yīng)用更傾向從外部讀取和接受信息。在這種情況下,使用同步方式可能是一個(gè)很好的選擇,特別是當(dāng)它涉及實(shí)時(shí)數(shù)據(jù)時(shí)。
我們的另一個(gè)選擇是異步通信,這是一個(gè)非阻塞模式。如果你想要一種有彈性的微服務(wù),那么,與同步通信相比,異步通信是一種更好的選擇。在這種情況下,客戶端會(huì)發(fā)送一個(gè)請(qǐng)求,收到請(qǐng)求的確認(rèn),并將其遺忘。這種方法最適用于大量寫(xiě)操作、無(wú)法承受數(shù)據(jù)記錄丟失的應(yīng)用。
下面是一些涉及微服務(wù)通信的解決方案,你可以從中選擇:
基于 HTTP 的 REST
基于 HTTP/2 的 REST
WebSocket
TCP 套接字
UDP 數(shù)據(jù)包
好好考慮最適合自身需求的通信協(xié)議,因?yàn)檫@將使應(yīng)用響應(yīng)更快、效率更高。
為什么 NodeJS 用于微服務(wù)?
在構(gòu)建微服務(wù)時(shí),有很多頂級(jí)編程語(yǔ)言可供選擇。NodeJS 就是其中之一。那么,為什么 NodeJS 是最佳選擇呢?
單線程 & 異步:NodeJS 使用事件循環(huán)來(lái)執(zhí)行代碼,允許異步代碼被執(zhí)行,從而使服務(wù)器能夠使用非阻塞機(jī)制來(lái)響應(yīng)。
事件驅(qū)動(dòng):NodeJS 使用事件驅(qū)動(dòng)架構(gòu),該架構(gòu)建立在軟件開(kāi)發(fā)的常見(jiàn)模式上,被稱為發(fā)布 - 訂閱或觀察者模式,能夠構(gòu)建強(qiáng)大的應(yīng)用,尤其是實(shí)時(shí)應(yīng)用。
快速和高度的可擴(kuò)展性:運(yùn)行環(huán)境建立在最強(qiáng)大的 JavaScript 引擎之一 V8 JavaScript Engine 之上,因此代碼執(zhí)行速度快,使得服務(wù)器能夠同時(shí)處理多達(dá) 10000 個(gè)并發(fā)請(qǐng)求。
易于開(kāi)發(fā):創(chuàng)建多個(gè)微服務(wù)會(huì)導(dǎo)致重復(fù)的代碼。Node.js 的微服務(wù)框架很容易創(chuàng)建,因?yàn)樗橄罅舜蟛糠值牡讓酉到y(tǒng)。所以用這種編程語(yǔ)言創(chuàng)建一個(gè)微服務(wù)可以像寫(xiě)幾行代碼一樣簡(jiǎn)單。
實(shí)施微服務(wù)架構(gòu)
我們從創(chuàng)建用于用戶管理的微服務(wù)開(kāi)始,它將使用 TCP 數(shù)據(jù)包進(jìn)行通信,并負(fù)責(zé)對(duì)用戶進(jìn)行 CRUD 操作。我們將使用 PacketSender 對(duì)其進(jìn)行測(cè)試,PacketSender 是一個(gè)免費(fèi)的工具,用于發(fā)送支持 TCP 的網(wǎng)絡(luò)數(shù)據(jù)包。
微服務(wù)的架構(gòu)和作用域被進(jìn)一步界定。因此,從演示的角度來(lái)看,通過(guò) HTTP 實(shí)現(xiàn)一個(gè)微服務(wù)與實(shí)現(xiàn) NodeJS API 沒(méi)有什么不同。
同時(shí),通過(guò) HTTP 來(lái)使用 REST 也很容易,但如果從這個(gè)協(xié)議切換到其他協(xié)議時(shí),會(huì)出現(xiàn)一些問(wèn)題。這也是本文中我們將會(huì)使用 TCP 包的異步模式來(lái)與微服務(wù)通信的原因。
我們將使用 NestJS 作為應(yīng)用的框架。它并非 NodeJS 微服務(wù)框架,而是一個(gè)用于構(gòu)建服務(wù)器端應(yīng)用的框架。但是,由于其內(nèi)置了多個(gè)微服務(wù)特性,使得工作變得更加容易。
步驟一:微服務(wù)設(shè)置
用 Node.js 構(gòu)建微服務(wù)相當(dāng)容易,尤其是用 NestJS 框架。開(kāi)始時(shí),可以使用 CLI 創(chuàng)建一個(gè)新的 NestJS 應(yīng)用,使用如下命令:
該命令會(huì)創(chuàng)建并初始化一個(gè)新項(xiàng)目。要開(kāi)始構(gòu)建一個(gè)微服務(wù),你需要安裝以下軟件包:
最后,為了讓微服務(wù)啟動(dòng)和運(yùn)行,我們需要用以下內(nèi)容更新 main.ts 文件:
NestJS 支持幾個(gè)內(nèi)置的傳輸層實(shí)現(xiàn),稱為傳輸器。上面的代碼將創(chuàng)建一個(gè)微服務(wù),通過(guò) TCP 傳輸層綁定到本地機(jī)器的 8875 端口進(jìn)行通信。
步驟 2:微服務(wù)監(jiān)聽(tīng)消息
我們可以使用消息模式或事件模式來(lái)與微服務(wù)通信。
消息模式的作用就像一個(gè)請(qǐng)求 - 響應(yīng)方法,它適用于在服務(wù)之間交換消息,而當(dāng)你只想發(fā)布事件而不等待響應(yīng)時(shí),就可以使用事件模式。
在我們的案例中,我們只實(shí)現(xiàn)根據(jù)給定的輸入創(chuàng)建一個(gè)用戶的功能,并且將獲得創(chuàng)建的用戶。因此,我們將在 app.controller.ts 文件中注冊(cè)一個(gè)名為 create_user 的消息模式。
我們抽象出創(chuàng)建新用戶的邏輯,因?yàn)樗梢愿鶕?jù)需求和使用的數(shù)據(jù)庫(kù)以各種方式實(shí)現(xiàn),我們將只關(guān)注與微服務(wù)相關(guān)的主題。
我們用來(lái)創(chuàng)建一個(gè)新用戶的有效負(fù)載有以下格式:
一個(gè)帶有 email 和 password 的簡(jiǎn)單對(duì)象
步驟 3:測(cè)試微服務(wù)
為了測(cè)試這個(gè)微服務(wù),我們將使用 PacketSender 向應(yīng)用發(fā)送一個(gè) TCP 包。為此,將地址和端口設(shè)置為 127.0.0.1:8875,并從右側(cè)的下拉菜單中選擇 TCP。要對(duì)我們的信息進(jìn)行編碼,請(qǐng)使用 ASCII 字段,并用以下值來(lái)完成:
pattern:是我們正在尋找的信息,create_user。
data:是我們要發(fā)送的 JSON 對(duì)象,一個(gè)帶有 email 和 password 的對(duì)象。
值 122 代表我們的消息的長(zhǎng)度,從第一個(gè)大括號(hào)開(kāi)始到最后一個(gè)大括號(hào)(包括兩個(gè))。

數(shù)據(jù)包發(fā)送器配置
如果我們點(diǎn)擊 Send 按鈕,我們會(huì)看到如下日志:

日志活動(dòng)
第二個(gè)是我們發(fā)送給微服務(wù)的內(nèi)容,第一個(gè)是我們收到的內(nèi)容。里面的響應(yīng)是由我們的微服務(wù)返回的對(duì)象,即被創(chuàng)建的用戶。
步驟 4:API 網(wǎng)關(guān)
現(xiàn)在我們有了微服務(wù),并進(jìn)行了快速測(cè)試,看它是否能接收請(qǐng)求并返回響應(yīng),現(xiàn)在是時(shí)候創(chuàng)建一個(gè) API 網(wǎng)關(guān)并將其連接到微服務(wù)上了。
為此,我們將使用上面描述的相同步驟創(chuàng)建一個(gè)新的 NestJS 應(yīng)用,然后用以下內(nèi)容更新 app.module.ts 文件。
我們將使用 .env 文件,我們將在其中存儲(chǔ)任何與配置有關(guān)的值。這些文件將在一個(gè)配置服務(wù)的幫助下被讀取。該微服務(wù)可以在 host 127.0.0.1:8875 處找到,其中 port 為 8875。
通過(guò)上面的代碼,我們使用 ClientProxy 注入一個(gè)新的對(duì)象,代表與我們的用戶 - 微服務(wù)的連接。這個(gè) NestJS 類提供了幾個(gè)內(nèi)置的工具來(lái)與遠(yuǎn)程微服務(wù)交換信息。
為了使用這個(gè)鏈接對(duì)象,我們可以在 AppController 或 AppService 中注入它,如下所示:
現(xiàn)在,每次 API 在路由 create-user 處受到 POST 請(qǐng)求時(shí),API 網(wǎng)關(guān)將把請(qǐng)求和有效載荷一起轉(zhuǎn)發(fā)給微服務(wù),然后從微服務(wù)返回響應(yīng)給用戶。