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 及工程能力

        共 13048字,需瀏覽 27分鐘

         ·

        2021-05-14 19:15

        前端早早聊大會(huì),前端成長的新起點(diǎn),與掘金聯(lián)合舉辦。加微信 codingdreamer 進(jìn)大會(huì)專屬周邊群,贏在新的起跑線。


        第二十六屆|前端互動(dòng)專場,了解互動(dòng)編程|互動(dòng)性能|互動(dòng)美術(shù)|地圖世界等等的可能性,5-15 下午直播,5 位講師(螞蟻/淘寶/字節(jié)等等),點(diǎn)我上車?? (報(bào)名地址):

        所有往期都有全程錄播,可以購買年票一次性解鎖全部

        ??更多活動(dòng)


        正文如下

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

        聊聊面試

        說起面試,其實(shí)每個(gè)面試官他都有自己的一套方法論。

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

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

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

        Node.js 可以做什么

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

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

        那么無論是做哪個(gè)領(lǐng)域,我們?cè)诿嬖嚨臅r(shí)候都盡量的需要有一套相同的一個(gè)標(biāo)準(zhǔn)去衡量,所以我抽象了一些考察候選人的側(cè)重點(diǎn)。

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

        編程范式

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

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

        系統(tǒng)思維

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

        工程能力

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

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

        總結(jié)

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

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

        編程范式

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

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

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

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

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

        案例

        問題:磁盤上某目錄下有 100 個(gè) JSON 文件,請(qǐng)合并成 1 個(gè) JSON 文件。

        解法一

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

        解法二

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

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

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

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

        解法三

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

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

        因?yàn)榇蠹叶贾涝?Node.js 里面,stream 這個(gè)模塊是非常特殊,也是非常核心的一個(gè)模塊,它可以以流的形式去讀取一個(gè)文件,避免內(nèi)存占用過大的一個(gè)問題。

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

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

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

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

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

        我的觀點(diǎn)是這樣的,其實(shí)你不必要非得回答出這種解法三,但是如果你回答出解法三的時(shí)候,我可能會(huì)覺得是給我暗示,然后咱們能不能別再浪費(fèi)時(shí)間了,直接上強(qiáng)度。對(duì),如果你回答出解法三的話,我可能很多問題都略過了,因?yàn)槲矣X得你是一個(gè)練家子,所以這是我個(gè)人的考察的方式。

        系統(tǒng)思維

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

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

        案例

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

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

        這里面的要求是用原生 API,為什么用原生 API?因?yàn)槿绻阌每蚣艿脑挘鋵?shí)幾行代碼就實(shí)現(xiàn)了。

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

        思路 - 校驗(yàn)文件

        首先我們會(huì)檢查文件是否存在是否是文件?而不是一個(gè)目錄。

        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ù)器上有一個(gè)約定的目錄 rootDir,我的靜態(tài)資源全部存在這里,所有的 URL 請(qǐng)求過來,我都是能夠映射到約定目錄下面的某個(gè)文件

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

        思路 - 資源合法性

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

        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é)尾的?如果不是的話,我認(rèn)為他請(qǐng)求的不是一個(gè)合法的靜態(tài)資源,我也會(huì)返回 404 或 500、或是其他你認(rèn)為覺得比較語義化的 Code。

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

        這個(gè)時(shí)候我可能就要校驗(yàn)這個(gè)文件到底是不是存在于 rootDir 里面。下面如果不存在的話,我一樣是不讓訪問的。

        這個(gè)時(shí)候我會(huì)用 realpathSync,因?yàn)檫€是軟鏈接的問題,我可能去校驗(yàn)真實(shí)的,我會(huì)取得它文件的真實(shí)路徑,然后去判斷這個(gè)是文件路徑的合法性的一個(gè)校驗(yàn)。

        思路 - 304

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

        // 實(shí)現(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());

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

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

        // 此處省略 chatset 檢測(cè),假設(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 }));
        }

        接下來我們就要把這個(gè)文件返回了,這個(gè)時(shí)候我們可能因?yàn)槟承┵Y源我們可以提高它的首字節(jié)響應(yīng),提高 TTFB,這個(gè)時(shí)候比如說 html,我就可以用流的方式去響應(yīng)它,這個(gè)也是一個(gè)你自己對(duì)這個(gè)事情的思考。

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

        思路 - 其他

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

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

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

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

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

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

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

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

        工程能力

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

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

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

        案例

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

        背景 0.0

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

        那么這個(gè)時(shí)候我們先說一下背景,因?yàn)閹啄昵昂痛蠹乙粯?,我們都是前端站?duì) react,特別是阿里站隊(duì)的是 react,那么我們的 react 就在 15、16 年就迅速的在業(yè)務(wù)線鋪開。

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

        背景 1.0

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

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

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

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

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

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

        首先我們的請(qǐng)求響應(yīng)越來越慢,這個(gè)很好理解,隨著業(yè)務(wù)的越來越多,Java 接入的越來越多,我們的 Node 的服務(wù)逐漸就不夠用了。這個(gè)好辦,于是我們就加機(jī)器擴(kuò)容,擴(kuò)容了之后又發(fā)現(xiàn)有問題。因?yàn)槲覀冊(cè)谥行幕倪@種渲染服務(wù)里面,我們?yōu)榱诵阅?,我們?huì)大量的在內(nèi)存里面去對(duì) react 模塊做緩存,如果你不緩存的話,性能肯定是不理想的。

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

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

        背景 2.0

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

        那么在同機(jī)的情況下,我們采用了簡單的HTTP,因?yàn)樵谕谧?strong>內(nèi)環(huán)地址 127 的情況下,它的 HTTP 性能是可以接受的,并且是在長連接的情況下,那么在這種方案下,現(xiàn)在我們這種方案已經(jīng)被淘汰了,只是當(dāng)時(shí)在這種方案下??雌饋硎钱?dāng)時(shí)最善的一個(gè)方案,最好的一個(gè)方案,或者說最合適的一個(gè)方案。

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

        按照這種這樣的方案,在落地的一個(gè)過程中,我們其實(shí)遇到了很多的問題,特別是對(duì) 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)時(shí)很多 Java 同學(xué)就不同意。

        思路 - 部署

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

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

        思路 - 安全

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

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

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

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

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

        這個(gè)時(shí)候 Node 就會(huì)掛掉,這也是沒有辦法的,因?yàn)槟愕拇a已經(jīng)影響到這個(gè)業(yè)務(wù)了,如果你不及時(shí)的把它 kill 掉的話,甚至?xí)B累到 Java,整臺(tái)機(jī)器可能都會(huì)被這個(gè)容器干掉。

        思路 - 監(jiān)控

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

        我們?cè)诒O(jiān)控平臺(tái),我們可以對(duì) Node.js 進(jìn)行一個(gè)探活的, 然后如果發(fā)現(xiàn)某臺(tái) Linux 掛的話,是會(huì)給我們發(fā)送報(bào)警,同時(shí)我們還有錯(cuò)誤采集指令,我們可以在指定日志 Node 的日志目錄,然后我們配置關(guān)鍵字,也是可以通過報(bào)警的方式來通知我們。

        思路 - 性能

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

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

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

        思路 - 容災(zāi)

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

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

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

        總結(jié)

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

        建議

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

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

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

        第二條多上手實(shí)踐,特別是工程化,因?yàn)槲矣X得工程化你干的事情很雜,但是雜的過程中,如果你都是去認(rèn)真思考每個(gè)點(diǎn)的話,其實(shí)給你帶來的收益是比較大的。

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

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

        我們?cè)谇岸斯こ袒雒钚械臅r(shí)候,也會(huì)針對(duì) Mac、Windows、Linux 去做各種適配,這里面其實(shí)你有很多東西是可以學(xué)習(xí)到系統(tǒng)之間的差異的。

        推薦書

        最后是薦書環(huán)節(jié),因?yàn)槲覀€(gè)人是不太會(huì)看紙質(zhì)的技術(shù)書籍的,這可能是我的一個(gè)習(xí)慣,或者說也不一定是靠習(xí)慣,但是我這邊就推薦我最近在看的一本書,就是講本田宗一郎的,我覺得其實(shí)跟其實(shí)跟今天的主題關(guān)系不大,但是我個(gè)人是比較喜歡比較熱血的一些東西,比較有激情的一些事情,然后也愛看一些比較有做事比較有激情的一些出類拔萃的一些人物的自傳。

        我覺得不光是在賽車領(lǐng)域,不一定在賽道上一定是熱血的,那么你從非常艱苦的條件中,創(chuàng)造出非常卓越的產(chǎn)品的時(shí)候,就像我們的工程化一樣,你從刀跟火種的時(shí)代逐漸的去做工具化,做平臺(tái)化,甚至做智能化,把前端工程領(lǐng)域就是開疆拓土。Talk to 成為一個(gè)非常優(yōu)秀的產(chǎn)品或者說是平臺(tái)的時(shí)候,這個(gè)也是非常非常熱血的一個(gè)事情。Ok,那么今天我的分享就到這里。

        聯(lián)系我


        別忘了 5-15 的第二十六屆|前端互動(dòng)專場,了解互動(dòng)編程|互動(dòng)性能|互動(dòng)美術(shù)|地圖世界等等的可能性,5-15 下午直播,5 位講師(螞蟻/淘寶/字節(jié)等等),點(diǎn)我上車?? (報(bào)名地址):

        所有往期都有全程錄播,可以購買年票一次性解鎖全部

        ??更多活動(dòng)


        別忘了給文章點(diǎn)贊


        瀏覽 73
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        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>
            亚洲无码中文字幕在线观看 | 老太色HD色老太HD. | 日本乱轮视频网站 | 午夜天堂精品久久久久 | 成 人 在线偷拍视频 | 国产精品18p | 性爱无码高清视频 | 久久综合中文字幕 | 欧美大屌操 | 学生妹作爱片 |