Go應用效能優化的8個最佳實踐,快速提升資源利用效率!

2023-06-21 12:01:04

作者|Ifedayo Adesiyan
翻譯|Seal軟體
連結|https://earthly.dev/blog/optimize-golang-for-kubernetes/

 
優化伺服器負載對於確保執行在 Kubernetes 上的 Golang 應用程式的高效能和可延伸性至關重要。隨著企業越來越多地採用容器化的方式和 Kubernetes 來部署和管理應用程式,採取減少伺服器負載的最佳實踐勢在必行,進而達到最佳的資源利用效率、成本效益並改善使用者體驗。
 

執行 Golang 應用程式的多個容器可以放在一個 Kubernetes 叢集內,並部署在多個節點上。每個容器可以使用 CPU、記憶體以及儲存等系統資源。如果這些資源沒有被高效地管理起來,可能會導致伺服器負載不斷增加,從而降低效能並增加支出。
 

因此,針對 Kubernetes 的 Golang 應用優化對於完成有效的資源利用、降低伺服器負載以及保證應用在生產環境中順利執行至關重要。
 

在本文中,我們將研究在 Kubernetes 上優化 Golang 應用的最佳方法,重點是降低伺服器負載。我們會討論一些方法,包括用 readiness 和 liveness 探針進行監控,利用彈性伸縮等功能來優化資源利用。
 

通過採取這些最佳實踐,可以提高在 Kubernetes 上執行的 Golang 應用的效能和可延伸性,這將改善使用者體驗、節約成本,並提升運維效率。
 

本文中所有程式碼範例可以存取下方網址檢視:
https://github.com/theifedayo/earthly.dev/tree/main/01-optimizing-go-k8s
 

前期準備

本文需要你對 Go 和 Kubernetes 有中等程度的瞭解。此外,需要你已經安裝 Go、Docker 和 Kubernetes (及其他執行 Kubernetes 的工具,如kubectl)並且已經在系統內設定完畢。
 

你可以通過終端輸入以下命令驗證是否已經安裝 Go:
go version
 

如果已經安裝,應該返回以下內容:
go version go1.19.3 darwin/amd64
 

使用以下命令可以驗證 Docker 是否安裝:
docker --version
 

輸出應為(或其他類似的內容):
Docker version 20.10.22, build 3a2c30b
 

驗證 Kubernetes 是否安裝也是類似的方法。
 

理解 Golang 應用程式和 Kubernetes

谷歌開發了 Golang,也稱「Go」,其目標是高效、多並行和可延伸。它對建立高效能軟體很有效,特別是對需要並行處理的應用,如網路伺服器、網路軟體和分散式系統。
 

容器化應用的部署、擴充套件和管理都可以由開源容器編排引擎 Kubernetes 自動化。通過抽象化底層基礎設施的細節,並促進有效的資源利用率、可延伸性和容錯率,它提供了一個靈活和可延伸的框架,用於部署和管理跨節點叢集的容器化應用程式。
 


Docker 容器
 

當 Golang 應用程式被部署到 Kubernetes 上時,它們通常被打包為 Docker 容器。這些獨立的、輕量級的環境隔離了程式及其所有的依賴項。這些容器可以通過 Kubernetes 簡單地部署和管理,實現有效的資源利用並且能夠根據應用程式的要求進行彈性伸縮。
 


Dockerfile 到 Kubernetes pod 的生命週期
 

執行在 Kubernetes 上的 Golang 應用能夠利用幾個特性來提升效能、可延伸性以及可靠性。例如,Kubernetes 能夠在不同的叢集節點上部署同一應用程式的多個範例,以分散負載並提升可用性。Golang 程式的部署、維護和可延伸性也可以通過 Kubernetes 中的負載均衡、服務發現和捲動更新工具來加強。
 

為了確保資源效率,防止資源爭奪或無節制使用,Golang 應用程式還可以利用 Kubernetes 的內建資源管理功能,包括 CPU 和記憶體限制、資源配額和資源剖析等。
 

此外,Kubernetes 提供了紀錄檔和監控能力,以瞭解 Golang 程式的功能運轉和健康狀況,高效推動問題解決和 debug。
 

並行性、效率、簡潔性、社群驅動以及與雲原生原則的一致性,使 Golang 成為開發基於 Kubernetes 應用的熱門選項,因為它可以讓開發者建立高效能、可延伸和有彈性的應用程式,並能在容器化環境中輕鬆部署和管理。
 

總而言之,Golang 和 Kubernetes 的結合可以提供一個高效能、可延伸以及可靠的應用程式構建和部署平臺。為 Kubernetes 優化 Golang 應用,需要利用 Kubernetes 提供的功能來實現高效的資源利用、擴充套件、請求、響應和錯誤處理,以確保在容器化環境中的最佳效能和可靠性。
 

優化 Golang 應用程式的最佳實踐

為 Kubernetes 優化 Golang 應用程式包括利用 Kubernetes 的功能,如資源限制、服務發現和監控,以確保在容器化環境中的最佳效能和可靠性,並優化 Go 程式碼,如使用 Go 垃圾回收和連線池。這種優化有助於確保在 Kubernetes 上執行的 Golang 應用程式能夠有效地處理高負載,動態擴充套件,併為使用者提供可靠的服務。
 

1、使用最小化和高效的容器基礎映象

容器效能和資源利用可能會受到你為 Golang 應用程式使用的基礎映象的影響。使用專門為執行容器化應用程式而建立的最小的基礎映象是至關重要的。
 

最受歡迎的選擇之一是 Docker Hub 提供的官方 Golang 基礎映象,它基於官方 Golang 發行版,提供基本的執行環境。你也可以考慮採用更輕量的選擇,比如建立在 Alpine 是一個輕量級的 Linux 發行版,通常用於 Docker 映象,以最小化映象的大小進而減少攻擊面。
 

下方是使用官方 Golang 基礎映象的 Docker 程式碼的例子:

# Use the official Golang base image
FROM golang:1.16

# Set working directory
WORKDIR /app

# Copy and build the Go application
COPY . .
RUN go build -o myapp

# Run the application
CMD ["./myapp"]

 

下面的 Docker 命令使用的是基於 Alpine Linux 的官方 Go 基礎映象的一個特定版本:
FROM golang:1.16-alpine
 

FROM docker 語句指定了要使用的基礎映象,在本例中是 golang:1.16-alpine。
 

2、優化資源分配

對於減少伺服器負載和優化效能來說,高效分配資源到 Golang 容器是至關重要的。它對於提高應用效能、避免過度設定和實現可延伸性至關重要。如果資源(如 CPU 和記憶體)沒有被有效分配,某些容器可能沒有足夠的資源,從而導致效能不佳。
 

通過優化資源分配,可以確保所有容器都足夠的資源來保證它們順利執行,這可以提高效能。Kubernetes 提供了為容器設定資源限制和請求的機制,這可以幫助防止資源的過度分配或分配不足。根據你的 Golang 應用程式的資源需求,仔細設定這些設定非常重要。
 

我們將建立一個 YAML 檔案,為容器設定資源限制和請求,以演示如何優化資源分配:

#.....
#   .....
      containers:
        - name: my-golang-container
          image: my-golang-image
          resources:
            limits:
              cpu: 500m
              memory: 512Mi
            requests:
              cpu: 200m
              memory: 256Mi

 

在 YAML 檔案中,容器被定義了具體的資源限制和對CPU和記憶體的請求。

  • resources 部分定義了 CPU、記憶體資源限制以及容器請求。
  • limits 指定了容器消耗的最大資源量。在本例,限制為 500 milliCPU(0.5 CPU 核心)以及 512MB記憶體。
  • requests 指定了容器執行所需的最小資源量。在本例,限制為 200 milliCPU(0.2 CPU 核心)以及256MB 記憶體。
     

在 Kubernetes 中,CPU 資源被分配給容器,以確保每個容器都能獲得必要的計算能力。通過設定 CPU 限制和請求,可以防止容器消耗過多的 CPU,導致其他容器沒有資源。這種對 CPU 資源的有效分配有助於確保你的應用程式順利執行,並確保伺服器負載保持穩定。
 

通過謹慎設定 CPU 和記憶體限制,容器內執行的應用程式的要求也會影響容器的大小。例如,如果應用需要大量記憶體來處理資料,可能需要相應地增加記憶體限制。在部署容器後,需要監控其資源使用並在必要時優化其大小。可以通過使用一些工具實現,如 Kubernetes 的 HPA 以基於資源使用狀況來自動調整副本數量。
 

設定資源限制和請求的完整 Kubernetes 部署設定如下所示:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-golang-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-golang-app
  template:
    metadata:
      labels:
        app: my-golang-app
    spec:
      containers:
        - name: my-golang-container
          image: my-golang-image
          resources:
            limits:
              cpu: 500m
              memory: 512Mi
            requests:
              cpu: 200m
              memory: 256Mi

 

3、優化垃圾回收

垃圾回收(GC)是 Golang 應用中記憶體管理的重要方面。Kubernetes 提供了垃圾回收設定的設定選項,如 GOGC 環境變數,它能夠控制觸發垃圾回收週期的條件。通過優化垃圾回收設定,你可以減少記憶體使用並優化 Golang 容器的整體效能。
 

當在 Kubernetes 叢集內執行 Golang app 時,未經優化的 GC 可能導致一些影響伺服器效能的問題。最常見的問題是記憶體漏失,進而使得 app 去消耗更多記憶體,從而導致更糟糕的效能和更高的伺服器負載。
 

以下程式碼在 Golang 中優化了垃圾回收設定。在程式碼裡,我們需要匯入2個包:「os」和「fmt」。fmt 包用於格式化輸入和輸出,「os」包提供一個獨立於平臺的作業系統功能介面:

package main

import (
    "fmt"
    "os"
)

 

定義 Go 程式的主要功能:
func main() {
 

我們決定使用 fmt.Println() 函數將 "GOGC "變數的當前值傳送到控制檯。GOGC 變數是一個控制 Go 中垃圾回收器的設定。

 // Get current GOGC value
    gogc := os.Getenv("GOGC")
    fmt.Println("Current GOGC value:", gogc);

 

現在,我們可以將 GOGC 變數值設定為50。這會更改 Go 垃圾回收的行為,可能會提升程式效能:

  // Set GOGC value to 50
   os.Setenv("GOGC", "50")

   // Run your Golang application
   // ...
}

 

完整程式碼如下:

package main

import (
    "fmt"
    "os"
)

func main() {
    // Get current GOGC value
    gogc := os.Getenv("GOGC")
    fmt.Println("Current GOGC value:", gogc);

    // Set GOGC value to 50
    os.Setenv("GOGC", "50")

    // Run your Golang application
    // ...
}

 

通過 os.Setenv("GOGC", "50") ,將 GOGC 設定為50。GOGC 引數決定了 Go 中觸發垃圾回收的 heap 的百分比。較低的值(如 50)意味著垃圾回收機制會被頻繁觸發,而較高的值(如100)意味著垃圾回收機制觸發次數較少。
 

頻繁的垃圾回收能夠幫助確保程式只使用它所需要的記憶體,為在同一伺服器上執行的其他程序釋放資源。監控程式效能和在必要時調整 GOGC 值以平衡記憶體使用和垃圾回收十分重要。
 

4、使用連線池

連線池是一種允許重複使用資料庫連線的技術,無需為每個請求建立新連線。這可以大大減少建立和拆除連線的開銷,從而提高效能和減少伺服器負載。
 

在 Kubernetes 環境中,連線池可以通過儘量減少對資料庫的連線數來幫助降低伺服器負載。當執行在 Kubernetes 的應用程式建立一個連線到資料庫中,它使用特定資源數量,如 CPU、記憶體和網路頻寬。這些資源是有限的,如果建立了太多連線就會被耗盡。
 

Golang提供了像「database/sql」這樣的庫,支援開箱即用的連線池。下面是如何在 Golang 中實現連線池的方法:
 

我們首先定義 package main 語句以將此 Go 程式碼檔案標識為主包的一部分。建立可執行程式需要主包:
package main
 

我們還必須匯入這個 Go 程式需要的外部包的列表。我們匯入了標準庫包 database/sql、fmt 和 log,以及第三方包 github.com/go-sql-driver/mysql,它為 database/sql 包提供了一個 MySQL 驅動:

import (
    "database/sql"
    "fmt"
    "log"
    "time"

    _ "github.com/go-sql-driver/mysql"
)

 

我們使用 sql.Open() 函數建立新資料庫連線池,它需要2個引數:要使用的資料庫驅動程式的名稱(mysql)和一個包含使用者證書、主機名、埠和資料庫名稱的聯結器字串。如果與資料庫的連線失敗,該程式會記錄錯誤資訊並且使用 log.Fatal() 的方式退出。否則,程式將關閉資料庫連線推遲到函數結束:

    // Create a database connection pool
    db, err := sql.Open("mysql", "user:password@tcp(db-hostname:3306)/mydb")
    if err != nil {
        log.Fatal("Failed to connect to database:", err)
    }
    defer db.Close()

 

我們有一個連線池,但我們需要使用 db 物件上的方法來設定資料庫連線池設定。SetMaxOpenConns() 方法設定了資料庫的最大開放連線數(本例中為10),而 SetMaxIdleConns() 方法設定了在池中保留的最大空閒連線數(5)。最後,SetConnMaxLifetime() 方法設定了一個連線的最大壽命(本例中為5分鐘):

    // Set connection pool settings
    db.SetMaxOpenConns(10)
    db.SetMaxIdleConns(5)
    db.SetConnMaxLifetime(time.Minute * 5)

    // Use the connection pool to perform database operations
    // ...
}

 

完整程式碼如下:

package main

import (
    "database/sql"
    "fmt"
    "log"
    "time"

    _ "github.com/go-sql-driver/mysql"
)

func main() {
    // Create a database connection pool
    db, err := sql.Open("mysql", "user:password@tcp(db-hostname:3306)/mydb")
    if err != nil {
        log.Fatal("Failed to connect to database:", err)
    }
    defer db.Close()

    // Set connection pool settings
    db.SetMaxOpenConns(10)
    db.SetMaxIdleConns(5)
    db.SetConnMaxLifetime(time.Minute * 5)

    // Use the connection pool to perform database operations
    // ...
}

 

確定 db.SetMaxOpenConns()db.SetMaxIdleConns()db.SetConnMaxLifetime() 方法的適當值取決於各種因素,如:

  • 資料庫上的預期流量和工作負載
  • 預計的並行連線的數量
  • 資料庫伺服器的容量
  • 資料庫查詢的平均響應時間
  • 其他使用同一資料庫的應用程式的預期負載和效能

 
總而言之,先設定這些值:

  • db.SetMaxOpenConns() 應該被設定為一個小於或等於資料庫伺服器可以處理的最大連線數的值。這個值應該設定得足夠高,以處理預期的流量和工作量,但又足夠低,以避免資料庫伺服器不堪重負。對於大多數網路應用來說,5到50之間的值通常是合理的。

  • db.SetMaxIdleConns() 應該被設定為一個足以處理預期的空閒連線的值。如果應用程式經常開啟和關閉連線,這個值應該設定得更高。一個在2到10之間的值通常是合理的。

  • db.SetConnMaxLifetime() 應該被設定為一個大於資料庫查詢平均時間的值。這個值應該設定得足夠高,以避免頻繁的連線更換,但又要足夠低,以防止長期閒置的連線不必要地消耗資料庫資源。幾分鐘到幾小時之間的值通常是合理的。

 
值得注意的是,這些值應該根據不斷變化的工作負載和效能要求定期審查和調整。監控資料庫伺服器和應用程式的效能指標可以幫助確定這些值是否需要調整。
 

5、利用健康檢查和 readiness 探針


健康檢查和 readiness 探針
 

Kubernetes 提供了健康檢查和 readiness 探針,允許你監控容器的狀態,並確定它們是否準備好接收流量。通過利用這些功能,你可以確保只有健康和準備好的容器才能接收流量,減少由不健康或不準備好的容器引起的伺服器負載。你可以在 Golang 應用中實現自定義的健康檢查和 readiness 探針,以提供關於其狀態的準確資訊。
 

為了監控容器的狀態,讓我們從新增一個 readiness 探針開始:

....
    ....
        containers:
            ....
            readinessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 10
            periodSeconds: 5

 

  • readinessProbe 定義容器的 readiness 探針。readiness 探針被用於檢查容器是否準備好接收流量。

  • httpGet 指定 readiness 探針應該使用的 HTTP GET 請求以檢查容器的健康狀況。

  • path: /health 表示用於檢查容器健康狀況的 HTTP GET 請求的路徑。在這種情況下,它被設定為"/health"。

  • initialDelaySeconds: 10 指定在容器啟動後,在啟動準備狀態探針之前的等待秒數。在本例中,它被設定為10秒。

  • periodSeconds: 5 指定連續的 readiness 探針檢查之間的時間間隔,單位是秒。在本例,它被設定為5秒。

 
現在,我們可以立刻新增 liveness 探針:

....
    ....
        containers:
            ....
            readinessProbe:
            .....
            livenessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 30
            periodSeconds: 10

 

  • livenessProbe 定義容器的 liveness 探針。liveness 探針被用於檢查容器是否正常執行和健康。

  • httpGet 指定 liveness 探針應該使用 HTTP GET 請求來檢查容器健康狀況。

  • path: /health 指用於 HTTP GET 請求的路徑以檢查容器的健康狀況。在本例中,被設定為「/health」。

  • initialDelaySeconds: 10 設定了在容器啟動後,liveness 探針啟動之前所等待的秒數。在本例中為10秒。

  • periodSeconds: 5 指 liveness 探針連續檢查的間隔時間,單位是秒。本例為5秒。

 
YAML 檔案如下所示:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-golang-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-golang-app
  template:
    metadata:
      labels:
        app: my-golang-app
    spec:
      containers:
        - name: my-golang-container
          image: my-golang-image
          ports:
            - containerPort: 8080
          readinessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 10
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 30
            periodSeconds: 10

 

為 Kubernetes 優化 Golang 應用程式需要仔細考慮資源分配、垃圾回收設定、網路通訊和容器健康狀況。通過遵循以上最佳實踐,可以在 Kubernetes 中減少伺服器負載和優化 Golang 應用程式的效能。通過利用 Kubernetes 和 Golang 應用的能力,您可以構建一個專為現代雲環境優化好的可延伸、高效能的容器化應用程式。
 

減輕伺服器負載技巧

1、實現自動彈性伸縮:自動彈性伸縮允許基於資源利用率來自動調整 pod 數量。使用 Kubernetes HPA 可以實現,該功能會基於 CPU 或記憶體的利用率來自動增加或減少 pod 數量。
 


 

如上圖所示,叢集自動伸縮可以在 Kubernetes 叢集內自動設定新節點以處理增加的負載,確保高效的資源利用並防止伺服器過載。
 

2、Kubernetes 的負載均衡:Kubernetes 提供內建的機制進行負載軍訓,如服務和 ingress。服務將 pod 暴露到網路並使用 round-robin 或 IP hash 等負載均衡演演算法,將進入的流量分配到多個pod上。
 

3、快取和利用Kubernetes快取系統:Kubernetes 提供快取系統,如 Memcached 和 Redis,它們可以被部署為 pod 並用於儲存經常存取的資料,減少重複查詢後端服務的需要,降低伺服器負載。
 

總結

總而言之,為 Kubernetes 優化 Golang 應用程式對於確保高效、可靠地在容器化環境內部署和運維至關重要。通過遵循文中提到的最佳實踐,可以有效降低伺服器負載,從而提升應用效能、提高成本效率。