返回文章列表

Go應用程式平行處理與叢集管理

本文探討 Go 應用程式在平行處理和叢集管理的實踐方式,包含使用 PM2 管理 Go 應用程式,以及 Docker Swarm 的叢集建立、服務佈署和容錯機制等。文章涵蓋了 PM2 的設定檔撰寫、Docker Swarm 的初始化和節點加入,以及服務建立和狀態監控等關鍵步驟,並以實際案例說明如何提升 Go

Web 開發 系統設計

在分散式系統中,有效管理 Go 應用程式的平行處理和佈署至關重要。本文將介紹如何使用 PM2 管理 Go 應用程式,並探討 Docker Swarm 的叢集管理和服務擴充套件機制,以確保應用程式在高負載環境下的穩定性和可用性。透過 PM2,可以簡化 Go 應用程式的佈署和監控,實作零停機時間重啟和負載平衡等功能。同時,Docker Swarm 提供了容器協調能力,方便建立和管理 Docker 叢集,並實作服務的自動擴充套件和容錯。結合 PM2 和 Docker Swarm,可以有效提升 Go 應用程式在分散式環境下的可靠性和擴充套件性,滿足現代應用程式對高用性和高效能的需求。

VIII. 平行性 - 透過處理程式模型擴充套件

雖然Go程式由於其強大的平行模型和能夠將處理擴充套件到多個CPU核心,不直接適用於此選項,但值得探討此選項作為透過處理程式模型提供冗餘的一種方式,並且在佈署新應用程式版本時提供優雅的升級路徑。透過使用Docker,我們也可以滿足12FA中提出的基本處理程式管理需求 - 回應當機的程式並處理使用者啟動的重新啟動和關閉。

基本處理程式管理

在確保應用程式保持活動狀態方面,Docker具有內建的--restart開關,可以控制程式意外離開或VPS或伺服器意外重新啟動後的生命週期。

執行應用程式時,您有多種重新啟動策略可供選擇:

  • no - 當應用程式離開時不重新啟動容器(預設值)
  • on-failure - 僅當應用程式以非零離開狀態離開時重新啟動容器
  • on-failure[:max-retries] - 可選設定應用程式重新啟動的最大次數
  • unless-stopped - 如果您手動停止容器,則在Docker啟動時不會重新啟動
  • always - 當應用程式離開或Docker啟動時(例如在重新啟動後)重新啟動應用程式

顯然,對於任何應用程式,預設值都可以是always,除非您有非常好的理由保持應用程式不可用。玄貓主張使用docker stop && docker rm,這將完全刪除容器,因此即使採用激進的重新啟動策略,也不會有副作用 - 例如在Docker主機重新啟動後啟動未受保護的管理容器。

在特殊情況下,重新啟動策略可以保護您免受例項的意外當機。通常,人們會從在主機上透過init.d(系統V init工具)執行Redis等服務開始。這確保了Redis在主機重新啟動後會啟動,但不能確保Redis在意外當機的情況下會自動重新啟動。玄貓曾經遇到過Redis因OOM(記憶體不足)錯誤而當機的情況,而當時仍有大量的RAM可用。雖然在GitHub上提交了問題,但最終玄貓將Redis移到了Docker中,可以立即利用此重新啟動策略。

測試重新啟動策略

我們甚至可以相當有效地使用Redis測試我們的重新啟動策略。Redis提供了一個命令DEBUG SEGFAULT,將模擬當機,我們可以看到Docker如何回應它。我們的Redis服務(參見第V章)已經以--restart=always啟動,因此我們只需要發出命令使其當機。

$ ./redis-cli debug segfault
Error: Server closed the connection

在一個終端中,您可以使用docker events檢視Docker事件。在另一個終端中,您可以執行services/redis中提供的redis-cli輔助工具,如下所示:

root@verdana:~# docker events
2017-02-06T20:11:30+01:00 container exec_create: redis-cli debug segfault 557...81a (image=redis:3.2-alpine, name=redis)
2017-02-06T20:11:30+01:00 container exec_start: redis-cli debug segfault 557...81a (image=redis:3.2-alpine, name=redis)
2017-02-06T20:11:30+01:00 container die 557...81a (exitCode=139, image=redis:3.2-alpine, name=redis)
2017-02-06T20:11:30+01:00 container start 557...81a (image=redis:3.2-alpine, name=redis)

為了簡潔起見,玄貓縮短了輸出。如您所見 - Docker在Redis容器內執行redis-cli,並且在記錄die事件後立即重新啟動容器。耗時不到一秒。當我們每幾天遇到一次當機時,這將是一個足夠好的臨時解決方案。

可用性計算

考慮到如果服務當機大約需要2秒,並且每3天發生一次,那麼系統的可用性將為99.993%。假設我們有3個例項,並且它們不會同時當機,那麼可用性將略高於99.998%。為了留存記錄,報告的OOM當機問題可在GitHub上找到。

高階處理程式管理

Supervisord

還有外部軟體可用於提供與Docker --restart策略類別似的功能。一個流行的用於管理程式的軟體稱為Supervisord。在與Docker相關的場景中,玄貓大多見到它被用來將多個應用程式組合到一個Docker映像中,最值得注意的是Nginx網路伺服器和PHP指令碼語言(php-fpm)。這違背了單一責任原則,因此違背了良好的實踐(每個容器一個應用程式)。

Supervisord允許您透過網路介面重新啟動個別服務,甚至有人嘗試在其上提供叢集層,以便檢視和管理佈署中的程式。

在使用Supervisord定義要執行的應用程式時,您需要建立一個組態區塊,類別似於以下內容:

[program:nginx]
command=/usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf
directory = /etc/nginx
autostart=true
autorestart=true
priority=10
stdout_events_enabled=true
stderr_events_enabled=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

內容解密:

  1. [program:nginx] 定義了一個名為 nginx 的程式組態區塊。
  2. command=/usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf 指定了執行Nginx的命令,其中 -g 'daemon off;' 確保Nginx以前台模式執行,而 -c 指定了組態檔案的路徑。
  3. directory = /etc/nginx 設定了執行命令的工作目錄。
  4. autostart=trueautorestart=true 分別確保了Nginx在Supervisord啟動時自動啟動,並且在Nginx離開時自動重新啟動,這與Docker的 --restart=always 類別似。
  5. stdout_events_enabled=truestderr_events_enabled=true 允許Supervisord捕捉Nginx的標準輸出和錯誤輸出的事件。
  6. stdout_logfile=/dev/stdoutstderr_logfile=/dev/stderr 將Nginx的輸出重定向到Supervisord的標準輸出和錯誤輸出,這樣可以在Docker日誌中看到Nginx的日誌。

諸如 autostartautorestart 之類別的選項相當於Docker中的 --restart=alwaysdirectory 類別似於Docker的 -w 開關,還有很多其他可用的選項已在Supervisord檔案中記錄。

玄貓認為,將Nginx和PHP結合在一起是可以的,主要是因為玄貓尚未見到為每個服務使用專用伺服器的Nginx/php佈署。這裡還有一定的效能優勢,因為Nginx可以使用Unix通訊端與PHP通訊。在Go(或NodeJS)中,很容易建立一個HTTP伺服器,因此不太需要緊密耦合一個網路伺服器,因為應用程式已經同時執行這兩項功能。經驗法則是將服務個別封裝到Docker映像中 - 這樣,在某些時候,您可以從Apache遷移到Lighttpd,然後再遷移到Nginx - 所有這些都可以獨立完成。

使用 PM2 進行 Go 應用程式的平行處理與擴充套件

在現代軟體開發中,特別是在雲端運算和微服務架構中,如何有效地管理和擴充套件應用程式的平行處理能力,是一個重要的議題。對於使用 Go 語言開發的應用程式來說,雖然 Go 語言本身提供了強大的平行處理能力(透過 goroutines),但在某些情況下,使用程式管理工具來管理和擴充套件應用程式仍然是有用的。本文將探討使用 PM2 這個程式管理工具來執行和管理 Go 應用程式。

為什麼選擇 PM2?

PM2 是一個流行的 Node.js 程式管理工具,它提供了諸如零停機時間重啟、負載平衡等功能。雖然它主要是為 Node.js 應用程式設計的,但它也可以用來執行其他語言的應用程式,包括 Go。

PM2 的主要功能

  • 零停機時間重啟:允許在不中斷服務的情況下重啟應用程式。
  • 負載平衡:可以將請求分配到多個例項,提高應用程式的處理能力。

組態 PM2 執行 Go 應用程式

要使用 PM2 執行 Go 應用程式,需要建立一個 process.yml 組態檔案。這個檔案定義瞭如何執行 Go 應用程式。

示例組態

apps:
  - script: ./gotwitter
    name: 'gotwitter'
    interpreter: none
    execute-command: true
    instances: 2
    exec_mode: fork
    watch: false
    out_file: /dev/stdout
    error_file: /dev/stderr

組態解說

  • script: 指定要執行的可執行檔案。
  • name: 為應用程式例項指定名稱。
  • interpreter: 設定為 none 表示直接執行可執行檔案,不使用直譯器。
  • instances: 指定要執行的例項數量。
  • exec_mode: 設定為 fork 表示使用 exec 系統呼叫建立新程式。

執行和管理 Go 應用程式

使用 PM2 執行 Go 應用程式後,可以透過 PM2 的命令列工具來管理和監控應用程式。

檢視應用程式狀態

./pm2_exec status

結果分析

┌───────────┬────┬──────┬─────┬────────┬─────────┬────────┬─────┬──────────┬──────────┐
│ App name │ id │ mode │ pid │ status │ restart │ uptime │ cpu │ mem │ watching │
├───────────┼────┼──────┼─────┼────────┼─────────┼────────┼─────┼──────────┼──────────┤
│ gotwitter │ 0 │ fork │ 20 │ online │ 0 │ 91s │ 0% │ 4.0 MB │ enabled │
│ gotwitter │ 1 │ fork │ 24 │ online │ 0 │ 91s │ 0% │ 4.1 MB │ enabled │
└───────────┴────┴──────┴─────┴────────┴─────────┴────────┴─────┴──────────┴──────────┘

每個應用程式例項都會讀取 NODE_APP_INSTANCE 環境變數,並根據該變數的值決定監聽的埠。

負載平衡和反向代理

由於 PM2 在 fork 模式下不提供內建的負載平衡功能,因此需要使用額外的工具(如 HAProxy 或 Nginx)來實作負載平衡。

HAProxy 組態示例

global
    maxconn 10000

frontend public
    bind *:80
    mode http
    timeout client 30s
    default_backend gotwitter

backend gotwitter
    balance roundrobin
    mode http
    timeout server 5s
    timeout connect 5s
    server app1 pm2-gotwitter:3000 check
    server app2 pm2-gotwitter:3001 check

Docker Swarm 叢集管理與服務擴充套件

Docker Swarm 是 Docker 官方提供的容器協調工具,允許使用者將多個 Docker 主機組成一個虛擬的 Docker 主機,實作容器的叢集管理和服務擴充套件。隨著 Docker 1.12 版本的發布,Swarm 功能已經內建於 Docker 中,使得建立和管理叢集變得更加容易。

初始化 Swarm 叢集

建立 Swarm 叢集的第一步是初始化叢集。假設我們從一個乾淨的 Docker 1.12.0 安裝開始,可以使用以下命令初始化 Swarm:

docker swarm init

此命令將當前節點設定為管理員節點,並提供加入叢集所需的 Token。

內容解密:

  • docker swarm init 初始化 Swarm 叢集,將當前節點設定為管理員。
  • 初始化完成後,會顯示加入叢集所需的命令,包括工作節點和新增管理員的 Token。

加入節點到 Swarm 叢集

要建立一個高用性的叢集,我們需要新增多個管理員節點。可以使用以下命令將新節點加入叢集:

docker swarm join --token <TOKEN> <MANAGER-IP>:2377

內容解密:

  • docker swarm join 命令用於將新節點加入現有的 Swarm 叢集。
  • --token 引數指定加入叢集所需的驗證 Token。
  • <MANAGER-IP>:2377 指定管理員節點的 IP 地址和埠號。

建立服務

在 Swarm 叢集中建立服務,可以使用 docker service create 命令。例如,建立一個名為 helloworld 的服務,使用 alpine 映象,並執行 ping google.com 命令:

docker service create --replicas 5 --name helloworld alpine ping google.com

內容解密:

  • --replicas 5 指定服務的副本數量為 5。
  • --name helloworld 為服務命名。
  • alpine ping google.com 指定服務執行的命令。

檢視服務狀態

可以使用 docker service lsdocker service ps 命令檢視服務的狀態和詳細資訊。

docker service ls
docker service ps helloworld

內容解密:

  • docker service ls 列出所有服務及其狀態。
  • docker service ps <SERVICE-NAME> 檢視指定服務的詳細執行狀態。

測試容錯能力

當 Swarm 叢集中的某個管理員節點失效時,其他管理員節點應該能夠接管其工作。為了測試容錯能力,我們可以關閉其中一個管理員節點,並觀察服務的狀態。

內容解密:

  • 當管理員節點失效時,Swarm 會嘗試在其他可用節點上重新啟動服務。
  • 如果只有兩個管理員節點,當其中一個失效時,叢集可能無法正常運作,因為無法達成共識(Quorum)。

新增管理員節點以提高容錯能力

為了提高容錯能力,可以新增更多管理員節點到 Swarm 叢集中。根據 RAFT 共識演算法的要求,為了容忍 $N$ 個節點的故障,需要至少 $2N+1$ 個管理員節點。因此,對於需要容忍一個節點故障的情況,至少需要三個管理員節點。

docker swarm join-token manager

此命令提供將新節點加入為管理員所需的 Token 和命令。

內容解密:

  • docker swarm join-token manager 取得將新節點加入為管理員的 Token 和命令。
  • 新增管理員節點可以提高 Swarm 叢集的容錯能力和高用性。

綜上所述,Docker Swarm 提供了一個強大的容器協調工具,能夠實作服務的擴充套件、管理和容錯。透過合理的組態和管理,可以確保應用服務的高用性和可靠性。