如何構建一個高可用的node環境
主要解決問題:
- 故障恢復
- 多核利用
- 多進程共享端口
cluster (集羣)
cluster可以多核監聽同一個端口。實現多進程共享端口,這個在node底層已經做好了。
folk(child_process.fork)方式不能實現多進程共享端口,還需要nginx去做多個端口的負載均衡,一般來説用cluster要好點,folk方式適用與多個程序之間。
建立一個cluster.js文件
var cluster = require('cluster'); //cluster庫
var os = require('os'); // 獲取CPU 的數量
var numCPUs = os.cpus().length;
var process = require('process') // 管理進程用的
console.log('numCPUs:', numCPUs) // 打印cpu數量 ①
var workers = {};
if (cluster.isMaster) { // 這裏是進入主進程,第一次啓動的時候,運行這裏
// 主進程分支
cluster.on('death', function (worker) { ②
// 當一個工作進程結束時,重啓工作進程 delete workers[worker.pid]; 這裏主要是為了讓代碼即使報錯,也不會影響服務器運行。故障恢復
worker = cluster.fork();
workers[worker.pid] = worker;
});
// 初始開啓與CPU 數量相同的工作進程 多核利用 ③
for (var i = 0; i < numCPUs; i++) {
var worker = cluster.fork(); // 複製進程,有多少個核,複製多少個子進程,複製的過程會重新運行一遍該文件(因為是複製進程,代碼也會複製份在子進程運行)。
workers[worker.pid] = worker;
}
} else { // 這裏是子進程開啓的時候,就是主進程folk之後,會走到這裏。所以這裏會啓動與cpu相同數量的子進程服務。
// 子進程啓動服務器,多進程共享3000端口 ④
var app = require('./app');
app.use(async (ctx, next) => {
console.log('worker' + cluster.worker.id + ',PID:' + process.pid)
next()
})
app.listen(3000);
}
// 當主進程被終止時,關閉所有工作進程
process.on('SIGTERM', function () { ⑤
for (var pid in workers) {
process.kill(pid);
}
process.exit(0);
});
直接看代碼,這樣看可能看不太懂。我們用一個流程圖來展示。我在上面代碼標記了①-⑤ ,5個代碼塊。
這裏看運行情況,啓動後,打印了5次cpu數量(主進程一次,子進程4次),①這段代碼執行了5次。
然後我們通過訪問localhost:3000,得到當前訪問的是第三子進程。
更優雅的部署node (pm2)
- 內建負載均衡(使⽤用Node cluster 集羣模塊、子進程)
- 線程守護,keep alive
- 0秒停機重載,維護升級的時候不不需要停機.
- 現在 Linux (stable) & MacOSx (stable) & Windows (stable).多平台⽀支持
- 停⽌止不不穩定的進程(避免⽆無限循環)
- 控制枱檢測 https://id.keymetrics.io/api/oauth/login#/register
- 提供 HTTP API
命令行部署方法:
npm install -g pm2
pm2 start app.js --watch -i 2 // watch 監聽⽂文件變化
// -i 啓動多少個實例例
pm2 stop all
pm2 list
pm2 start app.js -i max # 根據機器器CPU核數,開啓對應數⽬目的進程
process.yml文件部署方法:
apps:
- script : app.js
instances: 2
watch : true
env :
NODE_ENV: production
運行pm2 start process.yml
pm2設置為開機啓動 pm2 startup
可以看到兩種方式的效果是一樣的,但是大多會選擇以yml文件來啓動。
docker概念
- Docker 屬於 Linux 容器的一種封裝,提供簡單易用的容器使用接口
- 1)提供一次性的環境。比如,本地測試他人的軟件、持續集成的時候提供單元測試和構建的環境。
- 2)提供彈性的雲服務。因為 Docker 容器可以隨開隨關,很適合動態擴容和縮容。
- 3)組建微服務架構。通過多個容器,一台機器可以跑多個服務,因此在本機就可以模擬出微服務架構。
- 4)image可以創建容器,每個容器有自己的容器端口,我們需要把它映射到主機端口
- 5)Docker Compose是 docker 提供的一個命令行工具,用來定義和運行由多個容器組成的應用。使用 compose,我們可以通過 YAML 文件聲明式的定義應用程序的各個服務,並由單個命令完成應用的創建和啓動。
特點
- ⾼高效的利利⽤用系統資源
- 快速的啓動時間
- 一致的運⾏行行環境
- 持續交付和部署
- 更更輕鬆的遷移
對⽐傳統虛擬機總結
|
特性
|
容器器
|
虛擬機
|
|
啓動
|
秒級
|
分鐘級
|
|
硬盤使⽤
|
一般為 MB
|
一般為 GB
|
|
性能
|
接近原⽣
|
弱於
|
|
系統支持量
|
單機⽀持上千個容器
|
一般⼏十個
|
三個核⼼心概念
- 鏡像
- 容器器
- 倉庫
和pm2類似,docker也有兩種方式啓動,一種是命令行方式,一種是Dockerfile定製鏡像方式。
DockerFile參數 :
|
FROM
|
MAINTAINER
|
RUN
|
ADD©
|
WORKDIR
|
VOLUME
|
EXPOSE
|
|
它依賴什麼鏡像
|
維護者信息
|
執行命令行命令
|
複製文件到指定路徑(ADD能解壓)
|
指定工作目錄
|
目錄掛載
|
容器的端口
|
常用的doker命令:
- 查看docker版本:docker version
- 顯示docker系統的信息:docker info
- 檢索image:docker search image_name
- 下載image : docker pull image_name
- 已下載鏡像列表: docker images
- 刪除鏡像: docker rmi image_name
- 啓動容器:docker run image_name
docker構建一個nginx服務器
- 拉取官⽅方鏡像
拉取官⽅方鏡像
docker pull nginx
查看鏡像
docker images nginx
啓動鏡像
docker run -p 80:80 -d nginx
查看進程
docker ps
docker ps -a // 查看全部
停⽌
docker stop id
刪除鏡像
docker rm id
- Dockerfile定製鏡像
#Dockerfile
FROM nginx:latest
RUN echo '<h1>Hello, docker</h1>' > /usr/share/nginx/html/index.html
# 定製鏡像
docker build -t mynginx .
# 運⾏
# -d 守護態運⾏
docker run -p 80:80 -d mynginx
Docker-Compose
Docker-Compose項目是Docker官方的開源項目,負責實現對Docker容器集羣的快速編排。
Docker-Compose將所管理的容器分為三層,分別是工程(project),服務(service)以及容器(container)。Docker-Compose運行目錄下的所有文件(docker-compose.yml,extends文件或環境變量文件等)組成一個工程,若無特殊指定工程名即為當前目錄名
docker-compose主要是可以集合多個服務,一起運行。比如一個項目有(前端、後台、數據庫、nginx)4個服務需要去啓動,如果單獨去啓動的話,我們需要運行4次docker。這裏我們能通過docker-compose,一起運行。
案例:nginx+node+pm2後台
nginx:
在nginx文件夾裏,裏面建立個conf.d文件夾。添加一個docker.conf文件
## nginx/conf.d/docker.conf
server {
listen 80;
location / {
root /var/www/html;
index index.html index.htm;
}
location ~ \.(gif|jpg|png)$ {
root /static;
index index.html index.htm;
}
location /api {
proxy_pass http://127.0.0.1:3000;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
node:
// app.js
const Koa = require('koa')
const app = new Koa()
app.use(ctx => {
ctx.body = 'Hello Docker'
})
app.listen(3000, () => {
console.log('app started at http://localhost:3000/')
})
// process.yml
apps:
- script : server.js
instances: 2
watch : true
env :
NODE_ENV: production
// Dockerfile
#Dockerfile
#制定node鏡像的版本
FROM node:10-alpine
#移動當前目錄下面的文件到app目錄下
ADD . /app/
#進入到app目錄下面,類似cd
WORKDIR /app
#安裝依賴
RUN npm install
#對外暴露的端口
EXPOSE 3000
#程序啓動腳本
CMD ["pm2-runtime", "start", "process.yml"]
然後構建docker-compose.yml
## docker-compose.yml
version: '3.1'
services:
app-pm2:
container_name: app-pm2
#構建容器
build: ./node
ports:
- "3000:3000"
nginx:
restart: always
image: nginx
ports:
- 80:80
volumes:
- ./nginx/conf.d/:/etc/nginx/conf.d/ #本地配置文件寫入到nginx配置目錄
- ./www/:/var/www/html/
然後創建一個www文件夾裏面放一個靜態html文件
//index.html
hello web!!
可以看到在docker-compose文件裏面,我們運行了兩個鏡像,一個是打包後的node名為app-pm2的鏡像,一個是nginx的鏡像。
同時我們把nginx的配置文件從本地寫到了docker運行的nginx目錄裏面。現在我們來看運行效果:
輸入:docker-compose up -d 後台啓動命令。
可以看到,兩個容器都被創建。現在我們先訪問80端口(nginx映射在80端口)。
訪問成功,成功訪問到www/index.html
然後我們訪問/api路徑,看是否能訪問到node服務器。
訪問成功。這裏一個docker-compose的案例就成功運行了。裏面用了nginx反向代理3000端口接口到80端口/api路徑,同時用了pm2去啓動node接口服務器。
項目搭建好之後,就需要持續集成了,這裏具體請參考我之前寫的文章。Gitlab-CI/CD雲端構建發佈