摘要:編寫代碼的開發人員必須負責代碼的生產部署。構建和部署鏈需要重大更改,以便為微服務環境提供正確的關注點分離。該對象會在之后的時被這時的回調函數會被調用,并輸出。微服務部署及集成部署微服務時有一個原則一個容器中只放一個服務,可以使用編
前幾天在微信群做的一次分享,整理出來分享給大家,相關代碼請戳 https://github.com/Carrotzpc/docker_web_app
微服務架構是一種構造應用程序的替代性方法。應用程序被分解為更小、完全獨立的組件,這使得它們擁有更高的敏捷性、可伸縮性和可用性。一個復雜的應用被拆分為若干微服務,微服務更需要一種成熟的交付能力。持續集成、部署和全自動測試都必不可少。編寫代碼的開發人員必須負責代碼的生產部署。構建和部署鏈需要重大更改,以便為微服務環境提供正確的關注點分離。后續我們會聊一下如何在時速云平臺上集成 DevOps。
Node.js? is a JavaScript runtime built on Chrome"s V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient. Node.js" package ecosystem, npm, is the largest ecosystem of open source libraries in the world. --- https://nodejs.org
Node.js 是構建微服務的利器,為什么這么說呢,我們先看下 Node.js 有哪些優勢:
Node.js 采用事件驅動、異步編程,為網絡服務而設計
Node.js 非阻塞模式的IO處理給 Node.js 帶來在相對低系統資源耗用下的高性能與出眾的負載能力,非常適合用作依賴其它IO資源的中間層服務
Node.js輕量高效,可以認為是數據密集型分布式部署環境下的實時應用系統的完美解決方案。
這些優勢正好與微服務的優勢:敏捷性、可伸縮性和可用性相契合(捂臉笑),再看下 Node.js 的缺點:
單進程,單線程,只支持單核CPU,不能充分的利用多核CPU服務器。一旦這個進程 down 了,那么整個 web 服務就 down 了
異步編程,callback 回調地獄
第一個缺點可以通過啟動多個實例來實現CPU充分利用以及負載均衡,話說這不是 K8s 的原生功能嗎。第二個缺點更不是事兒,現在可以通過 generator、promise等來寫同步代碼,爽的不要不要的。
下面我們主要從 Docker 和 Node.js 出發聊一下高質量Node.js微服務的編寫和部署:
Node.js 異步流程控制:generator 與 promise
Express、Koa 的異常處理
如何編寫 Dockerfile
微服務部署及 DevOps 集成
1. Node.js 異步流程控制:Generator 與 PromiseNode.js 的設計初衷為了性能而異步,現在已經可以寫同步的代碼了,你造嗎?目前 Node.js 的LTS版本早就支持了Generator, Promise這兩個特性,也有許多優秀的第三方庫 bluebird、q 這樣的模塊支持的也非常好,性能甚至比原生的還好,可以用 bluebird 替換 Node.js 原生的 Promise:
global.Promise = require("bluebird")
blurbird 的性能是 V8 里內置的 Promise 3 倍左右(bluebird 的優化方式見 https://github.com/petkaanton... )。
1.1 ES2015 GeneratorGenerators are functions which can be exited and later re-entered. Their context (variable bindings) will be saved across re-entrances. --- https://developer.mozilla.org...*
generator 就像一個取號機,你可以通過取一張票來向機器請求一個號碼。你接收了你的號碼,但是機器不會自動為你提供下一個。換句話說,取票機“暫停”直到有人請求另一個號碼(next()),此時它才會向后運行。下面我們看一個簡單的示例:
function* idMaker(){ var index = 0 while(index < 3) yield index++ } var gen = idMaker() gen.next() // {value: 0, done: false} gen.next() // {value: 1, done: false} gen.next() // {value: 2, done: false} gen.next() // {value: undefined, done: true} // ...
從上面的代碼的輸出可以看出:
generator 函數的定義,是通過 function *(){} 實現的
對 generator 函數的調用返回的實際是一個遍歷器,隨后代碼通過使用遍歷器的 next() 方法來獲得函數的輸出
通過使用yield語句來中斷 generator 函數的運行,并且可以返回一個中間結果
每次調用next()方法,generator 函數將執行到下一個yield語句或者是return語句。
下面我們就對上面代碼的每次next調用進行一個詳細的解釋:
第1次調用next()方法的時候,函數執行到第一次循環的yield index++語句停了下來,并且返回了0這個value,隨同value返回的done屬性表明 generator 函數的運行還沒有結束
第2次調用next()方法的時候,函數執行到第二循環的yield index++語句停了下來,并且返回了1這個value,隨同value返回的done屬性表明 generator 函數的運行還沒有結束
... ...
第4次調用next()方法的時候,由于循環已經結束了,所以函數調用立即返回,done屬性表明 generator 函數已經結束運行,value是undefined的,因為這次調用并沒有執行任何語句
PS:如果在 generator 函數內部需要調用另外一個 generator 函數,那么對目標函數的調用就需要使用yield*。
1.2 ES2015 PromiseThe Promise object is used for asynchronous computations. A Promise represents an operation that hasn"t completed yet, but is expected in the future. --- https://developer.mozilla.org...
所謂 Promise,就是一個對象,用來傳遞異步操作的消息。它代表了某個未來才會知道結果的事件(通常是一個異步操作),并且這個事件提供統一的 API,可供進一步處理。
一個 Promise 一般有3種狀態:
pending: 初始狀態,不是fulfilled,也不是rejected.
fulfilled: 操作成功完成.
rejected: 操作失敗.
一個 Promise 的生命周期如下圖:
下面我們看一段具體代碼:
function asyncFunction() { return new Promise(function (resolve, reject) { setTimeout(function () { resolve("Async Hello world") }, 16) }) } asyncFunction().then(function (value) { console.log(value) // => "Async Hello world" }).catch(function (error) { console.log(error) })
asyncFunction 這個函數會返回 Promise 對象, 對于這個 Promise 對象,我們調用它的then 方法來設置resolve后的回調函數,catch方法來設置發生錯誤時的回調函數。
該 Promise 對象會在setTimeout之后的16ms時被resolve, 這時then的回調函數會被調用,并輸出 "Async Hello world"。
在這種情況下catch的回調函數并不會被執行(因為 Promise 返回了resolve), 不過如果運行環境沒有提供 setTimeout 函數的話,那么上面代碼在執行中就會產生異常,在 catch 中設置的回調函數就會被執行。
小結如果是編寫一個 SDK 或 API,推薦使用傳統的 callback 或者 Promise,不使用 generator 的原因是:
generator 的出現不是為了解決異步問題
使用 generator 是會傳染的,當你嘗試yield一下的時候,它要求你也必須在一個 generator function 內
(《如何用 Node.js 編寫一個 API 客戶端》@leizongmin)
由此看來學習 Promise 是水到渠成的事情。
2. Express、Koa 的異常處理一個友好的錯誤處理機制應該滿足三個條件:
對于引發異常的用戶,返回 500 頁面
其他用戶不受影響,可以正常訪問
不影響整個進程的正常運行
下面我們就以這三個條件為原則,具體介紹下 Express、Koa 中的異常處理:
2.1 Express 異常處理在 Express 中有一個內置的錯誤處理中間件,這個中間件會處理任何遇到的錯誤。如果你在 Express 中傳遞了一個錯誤給next(),而沒有自己定義的錯誤處理函數處理這個錯誤,這個錯誤就會被 Express 默認的錯誤處理函數捕獲并處理,而且會把錯誤的堆棧信息返回到客戶端,這樣的錯誤處理是非常不友好的,還好我們可以通過設置NODE_ENV環境變量為production,這樣 Express 就會在生產環境模式下運行應用,生產環境模式下 Express 不會把錯誤的堆棧信息返回到客戶端。
在 Express 項目中可以定義一個錯誤處理的中間件用來替換 Express 默認的錯誤處理函數:
app.use(errorHandler) function errorHandler(err, req, res, next) { if (res.headersSent) { return next(err) } res.status(500) switch(req.accepts(["html", "json"])) { case "html": res.render("error", { error: err }) break default: res.send("500 Internal Server Error") } }
在所有其他app.use()以及路由之后引入以上代碼,可以滿足以上三個友好錯誤處理條件,是一種非常友好的錯誤處理機制。
2.2 Koa 異常處理我們以Koa 1.x為例,看代碼:
app.use(function *(next) { try { yield next } catch (err) { this.status = err.status || 500 this.body = err this.app.emit("error", err, this) } })
把上面的代碼放在所有app.use()函數前面,這樣基本上所有的同步錯誤均會被 try{} catch(err){} 捕獲到了,具體原理大家可以了解下 Koa 中間件的機制。
2.3 未捕獲的異常 uncaughtException上面的兩種異常處理方法,只能捕獲同步錯誤,而異步代碼產生的錯誤才是致命的,uncaughtException錯誤會導致當前的所有用戶連接都被中斷,甚至不能返回一個正常的HTTP 錯誤碼,用戶只能等到瀏覽器超時才能看到一個no data received錯誤。
這是一種非常野蠻粗暴的異常處理機制,任何線上服務都不應該因為uncaughtException 導致服務器崩潰。在Node.js 我們可以通過以下代碼捕獲 uncaughtException錯誤:
process.on("uncaughtException", function (err) { console.error("Unexpected exception: " + err) console.error("Unexpected exception stack: " + err.stack) // Do something here: // Such as send a email to admin // process.exit(1) })
捕獲uncaughtException后,Node.js 的進程就不會退出,但是當 Node.js 拋出 uncaughtException 異常時就會丟失當前環境的堆棧,導致 Node.js 不能正常進行內存回收。也就是說,每一次、uncaughtException 都有可能導致內存泄露。既然如此,退而求其次,我們可以在滿足前兩個條件的情況下退出進程以便重啟服務。當然還可以利用domain模塊做更細致的異常處理,這里就不做介紹了。
3. 如何編寫 Dockerfile 3.1 基礎鏡像選擇我們先選用 Node.js 官方推薦的node:argon官方LTS版本最新鏡像,鏡像大小為656.9 MB(解壓后大小,下文提到的鏡像大小沒有特殊說明的均指解壓后的大小)
The first thing we need to do is define from what image we want to build from. Here we will use the latest LTS (long term support) version argon of node available from the Docker Hub --- https://nodejs.org/en/docs/gu...
我們事先寫好了兩個文件package.json, app.js:
{ "name": "docker_web_app", "version": "1.0.0", "description": "Node.js on Docker", "author": "Zhangpc", "main": "app.js", "scripts": { "start": "node app.js" }, "dependencies": { "express": "^4.13.3" } }
// app.js "use strict"; const express = require("express") // Constants const PORT = 8080 // App const app = express() app.get("/", function (req, res) { res.send("Hello world ") }) app.listen(PORT) console.log("Running on http://localhost:" + PORT)
下面開始編寫 Dockerfile,由于直接從 Dockerhub 拉取鏡像速度較慢,我們選用時速云的docker官方鏡像 docker_library/node,這些官方鏡像都是與 Dockerhub 實時同步的:
# Dockerfile.argon FROM index.tenxcloud.com/docker_library/node:argon # Create app directory RUN mkdir -p /usr/src/app WORKDIR /usr/src/app # Install app dependencies COPY package.json /usr/src/app/ RUN npm install # Bundle app source COPY . /usr/src/app # Expose port EXPOSE 8080 CMD [ "npm", "start" ]
執行以下命令進行構建:
docker build -t zhangpc/docker_web_app:argon .
最終得到的鏡像大小是660.3 MB,體積略大,Docker 容器的優勢是輕量和可移植,所以承載它的操作系統即基礎鏡像也應該迎合這個特性,于是我想到了Alpine Linux,一個面向安全的,輕量的 Linux 發行版,基于 musl libc和busybox。
下面我們使用alpine:edge作為基礎鏡像,鏡像大小為4.799 MB:
# Dockerfile.alpine FROM index.tenxcloud.com/docker_library/alpine:edge # Install node.js by apk RUN echo "@edge http://nl.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories RUN apk update && apk upgrade RUN apk add --no-cache nodejs-lts@edge # If you have native dependencies, you"ll need extra tools # RUN apk add --no-cache make gcc g++ python # Create app directory RUN mkdir -p /usr/src/app WORKDIR /usr/src/app # If your project depends on many package, you can use cnpm instead of npm # RUN npm install cnpm -g --registry=https://registry.npm.taobao.org # RUN cnpm install # Install app dependencies COPY package.json /usr/src/app/ RUN npm install # Bundle app source COPY . /usr/src/app # Expose port EXPOSE 8080 CMD [ "npm", "start" ]
執行以下命令進行構建:
docker build -t zhangpc/docker_web_app:alpine .
最終得到的鏡像大小是31.51 MB,足足縮小了20倍,運行兩個鏡像均測試通過。
3.2 還有優化的空間嗎?首先,大小上還是可以優化的,我們知道 Dockerfile 的每條指令都會將結果提交為新的鏡像,下一條指令將會基于上一步指令的鏡像的基礎上構建,所以如果我們要想清除構建過程中產生的緩存,就得保證產生緩存的命令和清除緩存的命令在同一條 Dockerfile 指令中,因此修改 Dockerfile 如下:
# Dockerfile.alpine-mini FROM index.tenxcloud.com/docker_library/alpine:edge # Create app directory and bundle app source RUN mkdir -p /usr/src/app WORKDIR /usr/src/app COPY . /usr/src/app # Install node.js and app dependencies RUN echo "@edge http://nl.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories && apk update && apk upgrade && apk add --no-cache nodejs-lts@edge && npm install && npm uninstall -g npm && rm -rf /tmp/* && rm -rf /root/.npm/ # Expose port EXPOSE 8080 CMD [ "node", "app.js" ]
執行以下命令進行構建:
docker build -t zhangpc/docker_web_app:alpine .
最終得到的鏡像大小是21.47 MB,縮小了10M。
其次,我們發現在構建過程中有一些依賴是基本不變的,例如安裝 Node.js 以及項目依賴,我們可以把這些不變的依賴集成在基礎鏡像中,這樣可以大幅提升構建速度,基本上是秒級構建。當然也可以把這些基本不變的指令集中在 Dockerfile 的前面部分,并保持前面部分不變,這樣就可以利用緩存提升構建速度。
最后,如果使用了 Express 框架,在構建生產環境鏡像時可以設置NODE_ENV環境變量為production,可以大幅提升應用的性能,還有其他諸多好處,下面會有介紹。
小結我們構建的三個鏡像大小對比見上圖,鏡像的大小越小,發布的時候越快捷,而且可以提高安全性,因為更少的代碼和程序在容器中意味著更小的攻擊面。使用node:argon作為基礎鏡像構建出的鏡像(tag 為 argon)壓縮后的大小大概為254 MB,也不是很大,如果對Alpine Linux心存顧慮的童鞋可以選用 Node.js 官方推薦的node:argon作為基礎鏡像構建微服務。
4. 微服務部署及 devops 集成部署微服務時有一個原則:一個容器中只放一個服務,可以使用stack 編排把各個微服務組合成一個完整的應用:
4.1 Dokcer 環境微服務部署安裝好 Docker 環境后,直接運行我們構建好的容器即可:
docker run -d --restart=always -p 8080:8080 --name docker_web_app_alpine zhangpc/docker_web_app:alpine4.2 使用時速云平臺集成 DevOps
時速云目前支持github、gitlab、bitbucket、coding 等代碼倉庫,并已實現完全由API接入授權、webhook等,只要你開發時使用的是這些代碼倉庫,都可以接入時速云的 CI/CD 服務:
下面我們簡單介紹下接入流程:
創建項目,參考文檔 http://doc.tenxcloud.com/doc/...
開啟CI
更改代碼并提交,項目自動構建
用構建出來的鏡像(tag為master)創建一個容器
開啟CD,并綁定剛剛創建的容器
更改代碼,測試 DevOps
我們可以看到代碼更改已經經過構建(CI)、部署(CD)體現在了容器上。
參考資料《微服務、SOA 和 API:是敵是友?》http://www.ibm.com/developerw...
《解析微服務架構(一):什么是微服務》 http://t.cn/RtXiKLS
《微服務選型之Modern Node.js》 https://github.com/i5ting/mod...
帥龍攻城獅《鏡像構建優化之路》 http://blog.tenxcloud.com/?p=...
《微容器:更小的,更輕便的Docker容器》 http://blog.tenxcloud.com/?p=...
黃鑫攻城獅的內部分享《Dockerfile技巧分享》
《Node 出現 uncaughtException 之后的優雅退出方案》 http://www.infoq.com/cn/artic...
《Express Error handling》 https://expressjs.com/en/guid...
《Promise 迷你書》 http://liubin.org/promises-book/
《如何把 Callback 接口包裝成 Promise 接口》 http://www.75team.com/post/ho...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/26652.html
摘要:編寫代碼的開發人員必須負責代碼的生產部署。構建和部署鏈需要重大更改,以便為微服務環境提供正確的關注點分離。該對象會在之后的時被這時的回調函數會被調用,并輸出。微服務部署及集成部署微服務時有一個原則一個容器中只放一個服務,可以使用編 前幾天在微信群做的一次分享,整理出來分享給大家,相關代碼請戳 https://github.com/Carrotzpc/docker_web_app sho...
摘要:在貓屎氤氳的霧氣里角仰望天花板,手機微信提醒這次構建成功或失敗,并附帶污言穢語。這時他可以開始往工位走,坐下時,微信又會提醒本次部署到成功或失敗。與企業微信的集成在決定使用之前,需要知道的是,是一個高度依賴社區的項目。 前言 相信我,一切事情的發生都是趕鴨子上架,沒有例外。人類所有偉大的變革都是迫不得已,可又是那么順其自然。比如容器(docker)技術的誕生,比如箭在弦上的創業,比如野...
摘要:前端每周清單專注前端領域內容,以對外文資料的搜集為主,幫助開發者了解一周前端熱點分為新聞熱點開發教程工程實踐深度閱讀開源項目巔峰人生等欄目。對該漏洞的綜合評級為高危。目前,相關利用方式已經在互聯網上公開,近期出現攻擊嘗試爆發的可能。 前端每周清單專注前端領域內容,以對外文資料的搜集為主,幫助開發者了解一周前端熱點;分為新聞熱點、開發教程、工程實踐、深度閱讀、開源項目、巔峰人生等欄目。歡...
摘要:第一步搭開發環境首先,我們需要在本地搭建好微信小程序的開發環境。在微信小程序中,所有的網絡請求受到嚴格限制,不滿足條件的域名和協議無法請求。第五步配置微信小程序云端示例鏡像中,已經部署好了,但是還需要在下修改配置中的域名證書私鑰。 「小程序」這個劃時代的產品發布快一周了,互聯網技術人都在摩拳擦掌,躍躍欲試。可是小程序目前還在內測,首批只發放了 200 個內測資格(淚流滿面)。本以為沒有...
閱讀 3385·2021-11-24 09:38
閱讀 1385·2021-11-22 15:08
閱讀 1454·2021-09-29 09:35
閱讀 475·2021-09-02 15:11
閱讀 1304·2019-08-30 12:55
閱讀 384·2019-08-29 17:16
閱讀 492·2019-08-29 11:30
閱讀 415·2019-08-26 13:23