1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        揭秘:如何考察前端的 Node.js 及工程能力

        共 12410字,需瀏覽 25分鐘

         ·

        2021-08-06 00:23

        正文如下

        本文是第二十二屆 - 前端早早聊面試專場,也是早早聊第 161 場,來自阿里云高可用架構(gòu)團隊 - 雪卒 分享的圖文講稿

        聊聊面試

        說起面試,其實每個面試官他都有自己的一套方法論。

        我記得我剛?cè)肼毎⒗锏臅r候,我給團隊內(nèi)推了一個候選人,然后我團隊的另外一個高 P 面試的,面完之后我問他候選人怎么樣。他可能因為忙,然后也沒有怎么詳細說,他就回復(fù)我三個字,他說味不對,我的心里就挺一頭霧水的,就是什么是味兒不對。

        其實我覺得這么多年面試下來,面試官他是有自己的一個考量標(biāo)準,然后根據(jù)候選人他的表現(xiàn)來考察,說白了就是是否合他的胃口。

        所以說面試這個東西其實是非常主觀的一個事情,可能會經(jīng)常發(fā)生這個團隊覺得不合適,但是又入職了同家公司另一個團隊,這些都是跟面試官自己從事的領(lǐng)域、和他的工作經(jīng)歷、還有他對面試考量標(biāo)準有關(guān)。

        Node.js 可以做什么

        Node.js 領(lǐng)域是非常多的,生態(tài)是非常大的。它是一門語言,背后其實代表的是整個工程工具還有服務(wù),以及整個的一個大領(lǐng)域

        所以做 Node.js 的人其實也有出現(xiàn)隔行與隔山的現(xiàn)象。在這么多領(lǐng)域里面,有可能你是做客戶端領(lǐng)域的,有可能你是服務(wù)端領(lǐng)域的,有可能你是做實時推流的。

        那么無論是做哪個領(lǐng)域,我們在面試的時候都盡量的需要有一套相同的一個標(biāo)準去衡量,所以我抽象了一些考察候選人的側(cè)重點。

        考察候選人側(cè)重點

        編程范式

        我個人側(cè)重的,首先是編程范式,有的人問我為什么不是編程,為什么不是基礎(chǔ),難道不應(yīng)該基礎(chǔ)是第一位的嗎?

        沒錯,基礎(chǔ)確實是第一位的,那么我們在編程的過程中,其實基礎(chǔ)是必須的。我通過你的編程,你的代碼可以看出你的平時的編程習(xí)慣是怎么樣的,你的風(fēng)格是怎么樣的,你對問題的思考路徑是怎么樣的,所以我稱之為編程范式。

        系統(tǒng)思維

        第二個是系統(tǒng)思維,其實就是你對整個服務(wù)的設(shè)計,你是否是有一個全面的考量?這個是一般架構(gòu)師必須具備的一個能力。

        工程能力

        工程能力其實就是把你的腦子里的對系統(tǒng)圖、設(shè)計圖、架構(gòu)圖去更好的落地的一個過程。

        一個工程,我認為它是非常有技術(shù)含量的,他需要去考慮系統(tǒng)的邊界,并且還需要考慮從應(yīng)用開發(fā)完成,從部署到后面系統(tǒng)的可觀測性、可運維性。

        總結(jié)

        工程和系統(tǒng)之間其實它們往往邊界是模糊的,只是工程它可能在某些方面?zhèn)戎赜趫?zhí)行,那么這三者我認為是一個遞進的關(guān)系。

        首先你是有編程標(biāo)準的一個編程范式,然后在你做系統(tǒng)的時候,逐漸形成你的方法論,最后你是如何把這個系統(tǒng)去落地下去、執(zhí)行下去的。

        編程范式

        剛才講到范式,但是我的理解就是你在 Node.js 領(lǐng)域是有一定的規(guī)范的。

        比如 Node.js 標(biāo)準的原生API,大家都知道它的 API 的風(fēng)格是 callback 風(fēng)格,它的第一個入?yún)⑹?error,第二個入?yún)⑹?callback。那么這種 API 形式是 Node.js 特有的一個 API 的風(fēng)格或者說規(guī)范。

        模式可以理解成就是你在解問題的時候,在思考的時候,你會形成一些的編程模型,或者說是思考的方式,或者說是設(shè)計模式。

        那么通常我看到一些代碼跟我的想法接近了,或者是他的解題思路也好,他的代碼設(shè)計也好,我通常會覺得候選人跟我的味道還挺搭的。

        上圖我想表達的意思是條條大路都通羅馬,一道題你可能有 ABCD 4種解法,對吧?可能其中一種解法和我相符合的話,我對你的好感會增加,所以面試確實是一個主觀的東西。

        案例

        問題:磁盤上某目錄下有 100 個 JSON 文件,請合并成 1 個 JSON 文件。

        解法一

        這個問題其實還是比較簡單的,首先想到的是把 JSON 都 require,然后再一起合并。(如下圖)

        解法二

        從解法一可能看得出來,這其實是有坑的,因為我并沒有說 JSON 有多大單個,JSON 文件可能是非常大的,所以我就想到了我們?nèi)?strong>分批處理這些文件。(如下圖)

        每次讀 20 個文件,然后把它合并在一起,那這樣子是不是就避免了內(nèi)存占用的問題?

        看起來是這樣,但是這里面又有一個基礎(chǔ)知識的坑,就是 require 在 Node.js 模塊系統(tǒng)里面,它是會緩存住的,require 的內(nèi)存是常駐的。

        所以上面這樣的解法,其實是沒有解決內(nèi)存占用的問題,但是你的思路是對的,只是這里面是有基礎(chǔ)知識的一個坑。于是我們就把 require 換一個寫法,直接用 fs 模塊讀它,這個時候它就不會再內(nèi)存里面常駐。(如下圖)

        解法三

        到這里其實這道題的解法已經(jīng)差不多了,通常我都會再問一下候選者:你還有沒有其他的解法?

        這個時候大家可以思考一下,通常如果說第三種解法是我這邊我個人是比較青睞的解法,當(dāng)然不代表這道題就必須要這樣解,我只是想表達就是什么是編程范式。

        因為大家都知道在 Node.js 里面,stream 這個模塊是非常特殊,也是非常核心的一個模塊,它可以以流的形式去讀取一個文件,避免內(nèi)存占用過大的一個問題。

        它在處理批處理操作的時候,可以用 stream 的另外一個模式: objectMode。它是一種對象模式,我們把一件事情、或一個文件、或一個操作,抽象成一個對象。

        這個對象它可以放到流里面去,像水管道一樣的傳給下面處理流、讀入流,那么就可以挨個的去處理它。在開源的工具里面,給我印象比較深的就是 gulp,它就是利用了 objectMode 去做了構(gòu)建工具。(如下圖)

        上圖里面有兩個參數(shù),第一個是 objectMode,第二個就是 highWatermark。

        當(dāng) objectMode 為 true 的時候,它在程序里面賦值為 20,它表示一次性處理流可以容納 20 個處理任務(wù),這個時候其實它代表了某種并發(fā)的意義,大家可以體會一下,在水管里面,它其實可以容納20個處理流、或任務(wù)流。

        回到剛才的話題,那個同學(xué)可能就會問你這代碼也太多了,你解一個這么簡單的題目去搞這么多代碼,我前面的解法就已經(jīng)足夠用了。

        我的觀點是這樣的,其實你不必要非得回答出這種解法三,但是如果你回答出解法三的時候,我可能會覺得是給我暗示,然后咱們能不能別再浪費時間了,直接上強度。對,如果你回答出解法三的話,我可能很多問題都略過了,因為我覺得你是一個練家子,所以這是我個人的考察的方式。


        系統(tǒng)思維

        系統(tǒng)其實是一個整體,同時它有很多的部件(或者說組件、模塊)組成,這些部件必須是非常精密的,這些精密的部件就像齒輪一樣咬合在一起,它才能夠讓這個系統(tǒng)非常高效的穩(wěn)健的去運行。

        給我的感覺就是,讓候選人給我講一個項目的時候,它是否是以一個系統(tǒng)的角度去講,而不是僅僅是從業(yè)務(wù)上介紹我做這個平臺主要是什么功能,然后主要是有哪些 Feature,我更希望可能聽到的是怎么設(shè)計的,里面關(guān)鍵的架構(gòu)是由哪些組件組成呢?每個組件我的思考是怎么樣的

        案例

        問題:請用 Node.js 原生 API 實現(xiàn)一個靜態(tài)資源源站服務(wù)。

        什么是靜態(tài)資源源站服務(wù)?大家可能都用過 cdn,如果沒有用過 cdn,可能就也用過 nginx,再不濟可能會用過 express 或 koa 里面的中間件,就是 static 中間件,它可能是會把靜態(tài)資源作為一個服務(wù)暴露出去,讓我們的網(wǎng)頁可以請求到 css、jshtml 這些資源。

        這里面的要求是用原生 API,為什么用原生 API?因為如果你用框架的話,其實幾行代碼就實現(xiàn)了。

        在實現(xiàn)源站服務(wù)的時候,我們通常是怎么樣的一個思路?這里列舉了一些點:

        思路 - 校驗文件

        首先我們會檢查文件是否存在?是否是文件?而不是一個目錄。

        const http = require('http');
        const { join } = require('path');
        const { lstatSync } = require('fs');
        const { URL } = require('url')
        // 約定目錄
        const rootDir = join(require('os').homedir(), 'assets');

        http.createServer((req, res) => {
          const { pathname } = new URL(req.url);
          
          // 檢查文件是否存在
          try {
           const stats = lstatSync(join(rootDir, pathname));
            if (!stats.isFile()) {
             throw new Error('Not Found');
            }
          } catch (e) {
           res.statusCode = 404;
            return;
          }
        });

        假設(shè)我們服務(wù)器上有一個約定的目錄 rootDir,我的靜態(tài)資源全部存在這里,所有的 URL 請求過來,我都是能夠映射到約定目錄下面的某個文件

        首先檢測這個文件是否存在。這個文件如果不存在的話,就返回 404 Not Found,這是第一個想到的問題。然后我們可能會去查找文件,它是否是文件,而不是一個目錄,這是一些必要的查找。

        思路 - 資源合法性

        接著檢查資源的合法性,包括文件后綴,是否在約定目錄里等等。

        const { extname, join, realPathSync } = require('fs');
        const validExts - [ '.js''.css''.png'/*...*/ ];
        const fileExt = extname(pathname);

        // 文件后綴是否合法
        if(!validExts.includes(fileExt)) {
          res.statusCode = 404;
          return;
        }

        // 文件路徑是否合法
        const filePath = realPathSync(join(rootDir, pathname));
        if (!filePath.startsWith(rootDir)) {
         res.statusCode = 404;
          return;
        }

        我們先檢查一下文件的后綴,是不是 .js、.css 這些結(jié)尾的?如果不是的話,我認為他請求的不是一個合法的靜態(tài)資源,我也會返回 404 或 500、或是其他你認為覺得比較語義化的 Code。

        接下來的合法性校驗是一個比較忽略的問題。我的文件很可能會請求到其他目錄,而我這個目錄可能是我在服務(wù)器端不想讓你訪問到的,是比較私密的一些文件。

        這個時候我可能就要校驗這個文件到底是不是存在于 rootDir 里面。下面如果不存在的話,我一樣是不讓訪問的。

        這個時候我會用 realpathSync,因為還是軟鏈接的問題,我可能去校驗真實的,我會取得它文件的真實路徑,然后去判斷這個是文件路徑的合法性的一個校驗。

        思路 - 304

        緊接著還會考察一下候選者對 HTTP 的基本協(xié)議是否了解。我們在協(xié)商緩存這里大家都比較清楚,304 的協(xié)議你可以用。If-Modified-Since 或者 Etag 去實現(xiàn) 304 的邏輯。這里就略過了。

        // 實現(xiàn) 304,這里使用 If-Modified-Since,用 Etag 也是可以的
        const clientModifiledTimeStamp = req.headers['if-modified-since'];
        const expectedModifiedTimeStamp = new Date(stats.mtime).getTime();
        if (clientModifiledTimeStamp && clientModifiledTimeStamp === expectedModifiedTimeStamp) {
         res.statusCode = 304;
          res.setHeader('Last-Modified'new Date(expectedModifiedTimeStamp).toGMTString());
        }

        思路 - 200

        const fs = require('fs');
        const CONTENT_TYPE_MAP = {
         '.js''application/javascript',
         '.html''text/html',
         '.css''text/css',
          // ...
        };

        const MAX_AGE_MAP = {
         '.js'86400,
          '.html'3600,
        };

        // 200 輸出
        res.statusCode = 200;
        res.setHeader('Content-Type', CONTENT_TYPE_MAP[extname]);
        res.setHeader('Cache-Control'`max-age=${MAX_AGE_MAP[extname]}`);
        res.setHeader('Last-Modified'new Date(expectedModifiedTimeStamp).toGMTString());

        最后我們要輸出,會先設(shè)置響應(yīng)碼 200,然后我們會設(shè)置它的媒體類型,內(nèi)容類型,然后還要注意設(shè)置它不同的針對不同的資源類型,你是否有不同的 Cache-Control 策略,這個是一個容易忽略的。

        通常 html、js、css 是會緩存的時間會設(shè)置的比較大,這個地方并不一定以代碼上這個值為標(biāo)準,但是體現(xiàn)出你的一些思考,哪怕是這些數(shù)值都不一樣。

        // 此處省略 chatset 檢測,假設(shè)是 utf-8
        const charset = 'utf-8';
        if (extname ==== '.html'/* ... */) {
         // 提高 TTFB
          fs.createReadableStream(filePath, { encoding: charset }).pipe(res);
        else {
         res.end(fs.readFileSync(filePath, { encoding: charset }));
        }

        接下來我們就要把這個文件返回了,這個時候我們可能因為某些資源我們可以提高它的首字節(jié)響應(yīng),提高 TTFB,這個時候比如說 html,我就可以用流的方式去響應(yīng)它,這個也是一個你自己對這個事情的思考。

        這里是省略了 charset 的編碼檢測,如果你能說出來的話也是一個非常好的,因為這里面代碼是涉及到比較多的,所以放不下了,所以這里做了一個假設(shè)。到這里的話一個基本 HTTP 請求響應(yīng)的過程已經(jīng)完成了。

        思路 - 其他

        通常還是會在再深挖一下,問一下這個候選人有沒有什么其他的一些想法?那么我這里就羅列了一些

        • 緩存設(shè)計
        • URL Combo
        • Gzip壓縮
        • 圖片類型檢測
        • ......

        比如我們的源站服務(wù)如果是用于生產(chǎn)的話,它一定是一個集群,我們現(xiàn)在的集群基本上都是容器化的,也就是容器化的一個最重要的特點,它是無狀態(tài)的。那么剛才我們的例子,其實是放在靜態(tài)資源文件是放在磁盤,這就非常的不利于擴展,不利于水平擴展。

        這個時候我們是不是要設(shè)計一些緩存,通過這些緩存,無論是臨時的緩存,還是永久的緩存彈性存儲,我們都可以去設(shè)計一些,比如說內(nèi)存的緩存,先把一部分的文件做一個 LRU 的內(nèi)存的緩存,如果內(nèi)存命中了直接返回,如果沒有命中,我們再去磁盤上去查找文件,如果磁盤命中了返回,沒有命中我們再去遠端的比如說 redis 或者是阿里的 oss 這種彈性的這種存儲,最終再去找一下這個文件,既提高了高可用,也提高了性能響應(yīng)的效率。

        第二個 URL Combo 是作為一個 CDN 源站。目前來看是比較基礎(chǔ)的一個能力了,我比如說 url 后面拼接 1.js,2.js 這個時候,用一個請求去返回 1.js 和 2.js 整個內(nèi)容。

        第三個 Gzip,Gzip 通常其實是配置在 nginx,這個地方你也可以提一提,因為我們這邊這道題沒有提到前置有 nginx,但是在工業(yè)化標(biāo)準上面,或者是在普遍的一個線上的最佳實踐,我們不會在 Node 端去做 Gzip 的邏輯,因為那樣子會很耗CPU,這個不是 Node 擅長的。然后下面可以可能圖片類型檢測,可能就是考察的你對現(xiàn)在現(xiàn)有 cdn 能力的一個認知了。

        比如說我們在阿里的 cdn 它有這樣的服務(wù),就是我們對圖片其實是可以返回 webp 的格式了。就針對png 這些圖片我們。一旦通過瀏覽器的 user-agent 檢測,那么其實是可以更智能的返回webp,這種格式來減少帶寬。

        一套下來,這道題就變得比較豐滿。如果你能把從基礎(chǔ)到后面的各種緩存,各種 Feature,各種高階的功能的一些都能說出來的話,其實是我就會覺得你對源站的思考是比較完整的。

        工程能力

        工程其實就是把你的系統(tǒng)按照一定的規(guī)范去把它落地掉。

        比如說你拿著設(shè)計圖紙,這是你的系統(tǒng)設(shè)計,然后你把這個房子造出來,你造出來的房子不是說造完就拉倒,你還要引入物業(yè)對吧?

        你要引入物業(yè),然后讓用戶入住了之后都非常的舒服。平時的物業(yè)的與住戶的協(xié)同,和能提供的服務(wù)取決于你的工程所完成的質(zhì)量,也提高了用戶的一個舒適度。

        案例

        問題:異構(gòu)系統(tǒng)中 Java 與 Node.js 的同機部署方案。

        背景 0.0

        上述問題其實是我?guī)啄昵坝龅降囊粋€問題,然后我做了一個方案,這個問題我不會在面試的時候去這么問,因為這個是特有的一個阿里場景問題,我在這里只是僅僅是拿這個方案來舉一個例子,工程化它可能會遇到哪些方面的挑戰(zhàn)?

        那么這個時候我們先說一下背景,因為幾年前和大家一樣,我們都是前端站隊 react,特別是阿里站隊的是 react,那么我們的 react 就在 15、16 年就迅速的在業(yè)務(wù)線鋪開。

        那么當(dāng)時阿里的外部層的架構(gòu)全部都是 Java 技術(shù)棧,基本上這個地方我沒有把后面的微服務(wù),就后面的微服務(wù)我就不畫了,我就只是畫到 web 這一層。

        背景 1.0

        各個業(yè)務(wù)系統(tǒng)都是 Java,然后 react 在客戶端,在瀏覽器端去做 CSR 的客戶端渲染,逐漸跑了一段時間,發(fā)現(xiàn)我們業(yè)務(wù)上其實是有一定的訴求的。

        比如說我們有一些業(yè)務(wù)是要 seo 的訴求,我們?nèi)绻兛蛻舳虽秩镜脑?,對搜索引擎不夠友好,還有一些我們的廣告投放對吧?也是需要借助于 seo 的能力的。所以我們也考慮到商業(yè)的角度,我們需要有去做服務(wù)端渲染的訴求。

        那么同時有些站點在服務(wù)端渲染上,確實能夠提高一定的渲染性的。于是我們就設(shè)計了 react 的 ssr 的服務(wù),當(dāng)時是作為 1.0 的方案,在盡量不改動現(xiàn)有的架構(gòu)的情況下,去做了這么一個服務(wù)。

        那么剛開始我說的是異構(gòu)系統(tǒng),這里的異構(gòu)系統(tǒng)就變成了 Java 和 Node.js 之間他們兩個是不同的技術(shù)棧,和 react 里面的同構(gòu),不是一個相對立的一個概念。

        這里的異構(gòu)僅僅是說技術(shù)棧的不同,那么我們 1.0 的方案,我們就部署了一套極為龐大的 ssr service,就是 ssr5

        我們每個業(yè)務(wù)側(cè)的代碼都部署在前端代碼也都會被 Node.js 服務(wù)所拉取到。當(dāng)各個業(yè)務(wù)請求到具體的業(yè)務(wù)系統(tǒng),Java 側(cè)再請求我們統(tǒng)一的渲染服務(wù)。隨著業(yè)務(wù)的逐漸的鋪開,我們逐漸發(fā)現(xiàn)了一些問題。

        首先我們的請求響應(yīng)越來越慢,這個很好理解,隨著業(yè)務(wù)的越來越多,Java 接入的越來越多,我們的 Node 的服務(wù)逐漸就不夠用了。這個好辦,于是我們就加機器擴容,擴容了之后又發(fā)現(xiàn)有問題。因為我們在中心化的這種渲染服務(wù)里面,我們?yōu)榱诵阅?,我們會大量的在?nèi)存里面去對 react 模塊做緩存,如果你不緩存的話,性能肯定是不理想的。

        我們也有做過壓測的數(shù)據(jù),所以我們在設(shè)計之初在系統(tǒng)上我們就設(shè)計了緩存的方案。那么這就遇到一個問題,我們水平擴展就無法解決了,因為我們每個容器都需要緩存所有業(yè)務(wù)的前端代碼是數(shù)10萬個模塊和組件,這個時候中心化的問題就逐漸顯現(xiàn)了。

        如果說 CPU 出現(xiàn)渲染性能問題,我們還可以通過擴容來解決的話,那么我們的這種內(nèi)存的緩存的設(shè)計,遭遇的瓶頸,我們已經(jīng)無法通過擴容去解決了。所以在這種情況下,我們?nèi)プ隽?2.0 的方案。

        背景 2.0

        2.0 的方案是通過異構(gòu)系統(tǒng)的同級部署,也就是 Java 和 Node 同級部署去解決這樣的問題。那么我們每個 Node 的服務(wù),只會拉取相應(yīng)的業(yè)務(wù)系統(tǒng)的代碼,也就是說我不會包含其他業(yè)務(wù)的代碼,我只服務(wù)好我自己的業(yè)務(wù)就行了。

        那么在同機的情況下,我們采用了簡單的HTTP,因為在同期走內(nèi)環(huán)地址 127 的情況下,它的 HTTP 性能是可以接受的,并且是在長連接的情況下,那么在這種方案下,現(xiàn)在我們這種方案已經(jīng)被淘汰了,只是當(dāng)時在這種方案下。看起來是當(dāng)時最善的一個方案,最好的一個方案,或者說最合適的一個方案。

        我們這里就不再討論這個方案是否還有優(yōu)化的空間,我們就單獨去看 2.0 的問題,它其實是去中心化的問題,我們要把剛才說的無法水平擴展的問題去解決掉,同時我們的性能最終做出來也是有大幅的提升。

        按照這種這樣的方案,在落地的一個過程中,我們其實遇到了很多的問題,特別是對 Java 同學(xué)來說,你的 Node 是寄生在它的 Java 的業(yè)務(wù)容器里面的,你怎么去說服他,去把你的代碼放到它的容器里面,并且還要和 Java 應(yīng)用,共同占用內(nèi)存,共同的去占用 CPU 資源系統(tǒng)資源的爭奪。

        當(dāng)你的 Node.js 的死循環(huán)了怎么辦?把我的 CPU 都打滿了怎么辦?我的業(yè)務(wù)是不是被你卡死了?等等問題,包括你的部署怎么辦?難道在我的 Java 倉庫里面把你的代碼直接通過 get some module 打過來嗎?

        我不接受,當(dāng)時很多 Java 同學(xué)就不同意。

        思路 - 部署

        最終我們是怎么樣解決?首先在部署這一側(cè),因為是 Docker 的容器化部署,我們沒有把 Node 的代碼去直接嵌入到 Java 的倉庫,我們最終采用了 rpm 包的方式。

        Linux 它提供了 rpm 這種方式,它其實是對軟件包的一種管理,就像 npm 包一樣,我們其實也是可以把我們的服務(wù)就打成一個 RPM 包,然后只要在他們的 Docker 里面加一行,安裝 rpm 包,然后在容器啟動拉起來的時候,它也會把 Node 應(yīng)用拉起來,這個是部署上我們解決了第一個部署的問題。

        思路 - 安全

        然后在安全上面,我們這里的安全指的是我在 Node 應(yīng)用里面,我們?nèi)?zhí)行業(yè)務(wù)代碼的時候,他業(yè)務(wù)代碼其實是可能會寫一些很奇怪的一些對服務(wù)端來說是不太合適的代碼。這個時候可能會污染到服務(wù)端的一些環(huán)境。

        如果說這里可能就發(fā)生概率比較小,如果說有人想搞怪你的系統(tǒng)的話,它的代碼很可能就可以寫一些惡意代碼,當(dāng)然我們的業(yè)務(wù)同學(xué)是不會這么搞的,這里只是一個說明對安全這一塊我們的一個思考。

        所以我們是通過 vm 模塊來創(chuàng)建一個沙箱去運行它,這個可能可以最大程度的減少這個業(yè)務(wù)代碼對 Node 服務(wù)端的一個侵入。同時我們在壓測的過程中我們發(fā)現(xiàn) Node.js 很有可能因為業(yè)務(wù)代碼寫得不規(guī)范,很有可能會導(dǎo)致死循環(huán),這個是很可能發(fā)生的,而且我們已經(jīng)發(fā)生了這樣的問題。所以我們就必須要解這個問題。

        我們?yōu)榇碎_發(fā)了 egg-heartbeat,就是我們因為我們是基于 Egg.js 去搭建我們的 Node 服務(wù)的,所以這個是可能跟技術(shù)棧相關(guān),但我相信這個原理是類似的,你如果是用其他的技術(shù)在你需要思考這樣的問題,那么通過 Egg.js 的插件,我們可以通過一個進程去專門 worker,他提供了 agent 這種這種進程,這種它是可以對 worker 進程之間它是有一個它是可以連接做心跳檢測。

        那么我們通過這個插件,確保我們 worker 的一些死循環(huán)的代碼一旦進入,瞬間這個 worker 就會被kill 掉。那么避免了對吧?這就是一種壟斷措施了,就是當(dāng)我們的代碼發(fā)生問題的時候,我們就要把它熔斷掉。

        這個時候 Node 就會掛掉,這也是沒有辦法的,因為你的代碼已經(jīng)影響到這個業(yè)務(wù)了,如果你不及時的把它 kill 掉的話,甚至?xí)B累到 Java,整臺機器可能都會被這個容器干掉。

        思路 - 監(jiān)控

        在工程領(lǐng)域里面有一個很重要的就是系統(tǒng)的可觀測性,可觀測性更多的一些監(jiān)控的指標(biāo)的建立。當(dāng)時我們有阿里 monitor 這樣的監(jiān)控平臺,可以提供這種腳本式的健康檢查。

        我們在監(jiān)控平臺,我們可以對 Node.js 進行一個探活的, 然后如果發(fā)現(xiàn)某臺 Linux 掛的話,是會給我們發(fā)送報警,同時我們還有錯誤采集指令,我們可以在指定日志 Node 的日志目錄,然后我們配置關(guān)鍵字,也是可以通過報警的方式來通知我們。

        思路 - 性能

        那么接下來性能的話,我們有一個 Alinode 的平臺,它是基于 node runtime 去做的一個版本,那么主要是提供了平臺化的工具,白屏化的工具來觀測 Node 它的實時的一個占用系統(tǒng)占用的指標(biāo)。

        那么講到性能問題,其實在 rpm 包我們也提供了一些配置,因為有些業(yè)務(wù),其實它的頁面數(shù)量,它的 ssr 的比例并不高,有的會多一些高一些,有的會低一些,這個時候我們其實可以可以針對具體的情況來提供我們 node 的服務(wù)拉起來的一些配置。比如:

        • worker 進程數(shù):通常它是和 CPU 的個數(shù)相同的,但是我們?nèi)匀恢С挚梢耘渲?/section>
        • 內(nèi)存水位:就是我們的老生代的對象的空間,這個空間的大小會跟 V8 的 GC 的頻率有一定關(guān)系。
        • 模塊緩存策略:我究竟是緩存多久?都是 lru 還是什么?這是我們暴露的配置,可以通過不同的業(yè)務(wù)可以根據(jù)它的配置去做一些更改。

        思路 - 容災(zāi)

        最后一個就是容災(zāi)。我們?nèi)绻@個服務(wù)掛掉了,就像剛才說的死循環(huán)了,如果是發(fā)生死循環(huán)了,我們Node 的就會被熔斷。

        把 Node 熔斷了之后,我不應(yīng)該去影響業(yè)務(wù),頂多就是 ssr 服務(wù)端渲染失敗了,所以瀏覽器端繼續(xù)CS2 繼續(xù)客戶端渲染,這是一個你需要去把這個方案做好的一個兜底。

        剛才說了其實也只是說了一部分,從大面上來大概說了一下幾個點,其實我們還做了很多的一些事情,這里簡答列舉一下,比如說我們的前端代碼發(fā)布之后,我們怎么智能的同步到服務(wù)端,我們需要在服務(wù)端去做些什么,其實不用的,我們前端發(fā)布之后,我們其實打通了發(fā)布通道。Node 端它自動感知到,它會把最新的代碼拉下來,重新把這個模塊 reload 的等等這些工作。

        總結(jié)

        所以說工程化其實看你怎么做的,我相信不同的人做出來的方案是不同的包括他的想法都是不一樣的,即使是面對同一個命題。所以它是可大可小的,它的力度也是可以粗一點,可以細一點,這是你對工程化的理解。

        建議

        那么最后也給大家交流一下,也是我的一些心得。

        第一條多看一下源碼,特別是比較業(yè)界比較流行的庫和工具,比如說你最近要開發(fā)命令行,你可能去看一下 commander 這樣的庫,你可能如果要開發(fā)服務(wù)端,你可能要去看一下 express 和 koa,特別是 express 和 koa 它里面其實公用的很多的包都是非常好的。

        特別是HTTP相關(guān)的,你還可以通過那些包來了解很多 http rfc 可以看到很多 rfc 的文檔標(biāo)準,其實你學(xué)到的就是更多的一些系統(tǒng)或者網(wǎng)絡(luò)協(xié)議相關(guān)的東西。

        第二條多上手實踐,特別是工程化,因為我覺得工程化你干的事情很雜,但是雜的過程中,如果你都是去認真思考每個點的話,其實給你帶來的收益是比較大的。

        你很可能通過復(fù)雜的一些點,慢慢構(gòu)筑你的知識體系。所以說我認為前端工程化是非常有技術(shù)含量的,而不是說僅僅是打雜的,并不一定說一定是要深入 v8 底層要去做一些這種 runtime 的優(yōu)化什么那些才是有技術(shù)含量的,主要是看領(lǐng)域

        第三條多了解操作系統(tǒng),因為 Node 說到底它是一門服務(wù)端的語言,無論是你開發(fā)工具,開發(fā)命令行開發(fā)桌面應(yīng)用還是開發(fā)服務(wù),它其實都是跟操作系統(tǒng)息息相關(guān)的。

        我們在前端工程化做命令行的時候,也會針對 Mac、Windows、Linux 去做各種適配,這里面其實你有很多東西是可以學(xué)習(xí)到系統(tǒng)之間的差異的。




        瀏覽 49
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            男插女视频网站 | 农村少妇一区二区三区四区五区 | 日韩Av高清一区二区三区四区 | 被肏视频| 成人aⅴ天堂 | 动漫美女吸乳 | 色色色天堂| 嗯~啊你轻点好深啊h王爷漫画 | 国产三级国产经典 | 久久熟妇五十路一区 |