全乾工程師的基本素養

近期越來越爲當初選擇想接觸後臺開發的想法感到後悔,因爲作爲全乾工程師,工作量可不是簡單的前端+後臺多了一倍,而是...多了很多倍...

這篇文章裏我會記錄一些平時做node後臺的一些小點,非常零碎,不適合完整閱讀。

1. 日誌定位之awk的妙用

定位日誌是web後臺開發的日常,由於我這個部門基礎設施比較原始,並沒有諸如ELK的強大日誌系統。一切都靠grepgrepgrep

但是,除了grep還有另一個強大的過濾日誌工具,那就是awk

只要日誌符合規範,就能夠用awk按照分隔符來輕鬆取出對應字段並加上邏輯進行處理。比如我們的日誌就是按照空格去分隔的,假設時間的是第4個,IP的是第8個。

zcat ~/log/error/2019081617.log.gz | grep 'nd_mng_frame' | awk '{if(\$4<\"20:26:00\" && \$4>\"17:45:00\"){print \$8}}' | sort | uniq -c | wc -l

上面這個命令,就可以通過去重IP,找到這段時間內調用這條cgi的用戶量。

sort是排序

uniq是去重 -c參數是統計出每個IP重複的數量

wc是用來統計行數,有多少個IP就多少個用戶

2. ssh的加解密算法配置

其實一般來說ssh配置已經沒有什麼坑了,一般的機器可以如下配置:

Host NewDevelop
    User qspace
    HostName 9.77.20.191
    Port 36000
    ServerAliveInterval 120

這樣,直接通過ssh NewDevelop就能連接到對應機器。

但是萬萬沒想到,在這個部門遇到個老古董機器,普通的ssh機器是無法連接的。

這臺機器是用了特殊的加解密算法,經過一段時間的google和摸索,終於摸索出了一套算法配置:

Host Compile
    User qspace
    HostName 10.123.20.20
    Port 36000
    ServerAliveInterval 120
    Ciphers aes128-cbc,aes128-ctr
    HostKeyAlgorithms ssh-dss
    KexAlgorithms diffie-hellman-group1-sha1

3. gzip加密過的html解碼

有個需求是需要在node端解析用戶設置的url,然後從中提取出圖文。沒錯就是模仿微信的圖文消息那種感覺。

(問題是,人家微信是提供了js api,然後用戶直接在html裏調用api來設置頭圖的,我們敢嗎,敢也沒人用==)

總之用了一個xss庫,本來是用來過濾html中的xss的,結果被我用來解析出裏面的各種標籤和圖片。策略是解析出<title>和第一個<img>標籤中的圖。

然後遇到一個投訴,說解析出來是亂碼。一開始懷疑是編碼問題,但是發現那個返回的html已經設置了utf-8的,就算是gb2312,我node後臺也有做識別和轉碼。這個url用瀏覽器訪問沒有任何問題,雖然是用asp的,但是返回的html還是比較符合規範的。

所以問題出在哪呢?試了半天發現,原來這個網址用的是gzip壓縮,我並沒有做解壓,瀏覽器則都是自動識別+解壓的。

其實一般的場景下,用request這個自帶的模塊傳對應參數,就能自動做到解壓gzip。但我們場景特殊,對於外網請求都要用一個C++插件去proxy轉發,那個插件可沒幫我們做gzip解壓。

還好node有自帶的解壓庫zlib,這個庫就可以解壓gzip壓縮過的文檔流。

大致判斷是這樣的(不要吐槽var,我們還在用node 0.12)

if (data.head.indexOf('Content-Encoding: gzip') > -1) {
    // 對於通過gzip壓縮返回的,需要解壓
    var gunzipPromise = new Promise(function(resolve, reject) {
        self.ungzip(data.body, function(unzipData) {
            resolve(unzipData);
        });
    });
    return gunzipPromise;
}

但是在寫這個ungzip方法的時候發現,如果直接用自帶的zlib.gunzip去解壓整個html,會報錯Error: unexpected end of file

經過一番摸索,我發現還有一種基於流的解壓方式:

/**
    * 對於gzip過的html文檔流進行解壓
    * 很奇怪的一點是如果用zlib.gunzip這種回調方式,會報錯Error: unexpected end of file
    * 所以採用了流的方式,把除了結尾部分的二進制解壓後輸出成string返回
    * 即使error也無視啦
    * @param {*} buffer gzip過的二進制數據
    * @param {*} callback 流的回調,傳入的data是string
*/
ungzip: function(buffer, callback) {
    var Readable = require('stream').Readable;
    var stream = new Readable();
    stream.push(buffer);
    stream.push(null);
    var gunzip = zlib.createGunzip();
    var body = '';
    var callbackWrapper = function(e) {
        callback(body);
    };
    stream.pipe(gunzip).on('data', function(chunk) {
        body = body + chunk.toString();
    }).on('close', callbackWrapper).on('error', callbackWrapper);
},

雖然對於這個用戶的案例來說,每次解壓到最後一定會報錯,但是沒關係,數據已經拿到了,直接回調就是了。

4. rsync的優化

作爲全乾工程師,devops當然也是要自己動手的,那麼涉及到devops就難免涉及到持續集成,涉及到持續集成就難免涉及到rsync這個好用的命令。就這麼一個命令,支撐起了那麼多重要代碼的傳輸也是神奇。

但是rsync有個問題在於,如果你的目錄是動態生成的(比如根據分支名),那麼極有可能你的目標機器上沒有這樣一個目錄。這個時候rsync命令就會報錯。

其實rsync是有參數來解決這個問題的,但是比較難用,那就是--include-from
這個東西一般是和--exclude-from一起用的,所以甚至需要專門讀取一個配置文件,+代表include,-代表exclude。

在搜索了一大堆令人一頭霧水的stackoverflow問題後,我決定還是放棄這個想法。

現在說下背景,之所以會有動態rsync路徑的問題,是因爲自己希望能夠根據git push每次產生的對比文件列表來增量地rsync構建後的文件,這樣就能大大降低CI的時間。(因爲歷史原因,我們的項目非常、非常龐大,如果全量rsync構建後的文件,時間可以長達1000s)

而構建後的路徑,是按照git分支名區分的,目標機器(測試機)不一定有對應的目錄,比如你開了一個新分支這種情況,目標機器沒有對應目錄,導致增量rsync單獨文件的時候報錯。

爲了解決這個問題我甚至想過在測試機維護一個node服務,通過監聽git的webhook來動態創建目錄,但是在研究了那臺機器的linux環境我果斷選擇了放棄,這臺機器不僅外網不通,安裝全靠手動上傳,連vim、ssh的配置都被魔改過,我實在不想碰這個機器。

結果一位老程序員對我的提醒如醍醐灌頂:

“你直接創建一個同樣目錄結構的上級目錄,只複製需要增量rsync的文件,然後直接rsync這個目錄就行了”

這簡直有種從更高維看事情的感覺,雖然很粗魯,但是確實很香。

5. child_process的使用

前端出身的全乾工程師,可能經常會寫一些nodejs的腳本來代替shell腳本來做一些devops的事情,(事實上shell腳本確實不是很好用)。

但是畢竟nodejs也是基於調用系統底層接口來達到操作系統的目的,不可避免的經常需要用child_process來跑一些shell腳本,比如上面提到的rsync
當然,你可以通過像shelljs這種庫來調用,但是...我這個項目吧,歷史原因,歷史原因。

舉個例子,下面這個rsync的調用:

const child = require('child_process')
child.execSync(`rsync -avr ${src} [email protected]::SyncQQmail${dist}`)

如果像這樣調用的話,你會發現rsync的輸出並沒有到console中,這是因爲如果不指定輸出的參數,child_process不會把結果輸出到console中。

可以這樣指定輸出參數:

child.execSync(`rsync -acvzK --exclude=".svn" ./server/proto/ [email protected]::SyncQQmail/nodejslogic/logicsvr/${BRANCH_NAME}/server/proto/`, {
    stdio: [null, process.stdout, process.stderr]
})

這樣的話,輸出就會到console中。

6. docker 的多分支測試環境調試(這裏可能之後單獨分一篇文章)

經過半年的努力,終於把項目遷移到git + docker的分支部署測試環境模式,不管怎麼說,對我來說,比原來svn的開發模式舒服多了。

但是目前分支實在太多(50+),每個微服務也都是共用這個母機的,所以做了多個母機來承載不同分支的測試用docker.

進入docker的命令是

docker exec -it V2.7.8_FinalDocker su - root

由於某些項目採用了docker-compose,當你重啓mac的時候發現mac開機特別慢(不要問我爲什麼會重啓,你敢相信互聯網公司會不時斷電),很有可能是因爲自動啓動了docker以及裏面所有的容器。重啓docker倒是沒什麼關係啦,但是所有容器一起啓動就有點...你可以通過docker ps來查看當前正在運行的容器

# docker ps
CONTAINER ID        IMAGE                                         COMMAND                  CREATED             STATUS              PORTS                              NAMES
1f3b771d1b6d        mongo                                         "docker-entrypoint.s…"   7 weeks ago         Up 2 days           0.0.0.0:27017->27017/tcp           orange-ci_mongo_1
fe890bc157de        dockerimage.isd.com/tsw/tsw:node-base         "/TSW/bin/proxy/star…"   2 months ago        Up 2 days           80/tcp, 0.0.0.0:8080->8080/tcp     orange-ci_master_1
1ca45aba6b61        mysql:5.6                                     "docker-entrypoint.s…"   2 months ago        Up 2 days           0.0.0.0:3306->3306/tcp             orange-ci_mysql_1
af06d04efce6        memcached                                     "docker-entrypoint.s…"   2 months ago        Up 2 days           0.0.0.0:11211->11211/tcp           orange-ci_memcached_1
f23c21999d4d        dockerimage.isd.com/orange-ci/orange-runner   "sh ./start"             2 months ago        Up 2 days           0.0.0.0:8087-8088->8087-8088/tcp   orange-ci_runner_1

你瞧,一重啓mac,這些mongodb、mysql、memcached全都重啓,那可真是要命。

所以可以通過docker update -restart=no CONTAINER-ID來把這些容器的自動重啓關掉。

參考這個鏈接:https://stackoverflow.com/que...

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章