etcd 框架實踐【Go 版】

FunTester發表於2024-07-01

etcd 是一個分散式的鍵值儲存系統,由 CoreOS 公司開發,主要用於為分散式系統提供可靠和高可用的配置管理和服務發現功能。etcd 基於 Raft 一致性演算法設計,可以有效地處理網路分割槽等容錯問題,確保資料在叢集中的一致性和可靠性。

etcd 被廣泛應用於 Kubernetes、Cloud Foundry、Mesos 等分散式系統和雲原生應用中,充當了可信賴的配置儲存和服務註冊發現等重要角色。除此之外,etcd 也可作為分散式鎖、佇列服務、訊息釋出訂閱系統等使用。總之,etcd 作為一個可靠的分散式鍵值儲存框架,為構建分散式系統提供了很好的基礎支援。

特點與使用場景

etcd 作為一個分散式的鍵值儲存系統,具有以下一些顯著的特點:

  1. 簡單的資料模型。etcd 採用鍵值對的資料模型,非常簡單直觀,易於使用和理解。同時支援監視機制和原子事務操作。
  2. 強一致性保證。etcd 基於 Raft 一致性演算法,能夠有效處理網路分割槽等容錯場景,確保資料在叢集中的完全一致性。在任何時候,叢集中只有一個主節點處理寫入操作。
  3. 高可用性。etcd 透過 Raft 演算法自動處理節點故障切換,即使部分節點當機,只要叢集中存在多數派節點,整個叢集依然可用。
  4. 良好的擴充套件性。etcd 支援動態新增或刪除叢集節點,實現水平擴充套件或縮減叢集規模。易於按需配置適合的叢集大小。
  5. 監視和通知機制。etcd 支援監視某個鍵字首的變化,並實時通知。適合儲存配置資訊,實現配置中心。
  6. 完善的訪問控制。etcd 支援基於 RBAC 的訪問控制,並支援認證、傳輸加密等安全特性。
  7. 方便的整合能力。etcd 提供了易於使用的 RESTful HTTP API,支援多種語言的客戶端庫,便於整合到應用程式中。

於此對應的,etcd 主要應用於以下幾個場景:

  1. 服務發現。etcd 常用於服務發現,在微服務架構中尤為重要。服務可以將自身的資訊(如 IP 地址、埠等)註冊到 etcd 中,其他服務可以從 etcd 中查詢所需的服務地址,簡化了服務間的通訊和協調。
  2. 配置管理。etcd 是一個理想的配置管理儲存系統,能夠儲存應用程式和系統的配置資訊,並且支援實時更新。透過監聽機制,應用程式可以實時響應配置的變更,避免了配置檔案頻繁修改帶來的麻煩。
  3. 分散式鎖。etcd 提供了原子操作和分散式鎖功能,可以用於協調分散式系統中的任務排程。透過使用 etcd 的分散式鎖機制,多個節點可以安全地進行同步操作,防止競爭條件和資料不一致問題。
  4. 領導選舉。在分散式系統中,領導選舉是一個常見需求。etcd 透過其強一致性的特性和分散式鎖機制,能夠實現高效的領導選舉,確保系統中只有一個領導節點在工作。
  5. 叢集管理。etcd 經常用作叢集管理工具,例如 Kubernetes 的核心元件之一就是 etcd。它用於儲存整個叢集的狀態資料,包括節點資訊、Pod 狀態、配置資料等,確保叢集的一致性和可靠性。
  6. 後設資料儲存。etcd 可以作為分散式系統的後設資料儲存。例如,在大資料處理系統中,etcd 可以儲存任務排程資訊、節點狀態等後設資料,幫助系統高效執行。
  7. 分散式協調和一致性。etcd 的強一致性和高可用性特性,使其適合作為分散式系統的協調和一致性儲存。在需要多個元件協同工作的場景中,etcd 可以提供可靠的資料儲存和一致性保證。

Go 語言實踐

依賴

首先我們新增依賴,這次我依然選擇了命令列新增。

go get go.etcd.io/etcd/client/v3

執行完之後,mod 檔案增加了一下內容:

go.etcd.io/etcd/api/v3 v3.5.14 // indirect  
go.etcd.io/etcd/client/pkg/v3 v3.5.14 // indirect  
go.etcd.io/etcd/client/v3 v3.5.14 // indirect

準備

首先我們使用前兩天學到的 zap 框架新增一個全域性的日誌物件 logger:

var Logger *zap.SugaredLogger // 日誌  

func init() {  
    encoderConfig := zapcore.EncoderConfig{ // 建立編碼配置  
       TimeKey:        "T",                           // 時間鍵  
       LevelKey:       "L",                           // 日誌級別鍵  
       NameKey:        "log",                         // 日誌名稱鍵  
       CallerKey:      "C",                           // 日誌呼叫鍵  
       MessageKey:     "msg",                         // 日誌訊息鍵  
       StacktraceKey:  "stacktrace",                  // 堆疊跟蹤鍵  
       LineEnding:     zapcore.DefaultLineEnding,     // 行結束符,預設為 \n       EncodeLevel:    zapcore.CapitalLevelEncoder,   // 日誌級別編碼器,將日誌級別轉換為大寫  
       EncodeTime:     zapcore.ISO8601TimeEncoder,    // 時間編碼器,將時間格式化為 ISO8601 格式  
       EncodeDuration: zapcore.StringDurationEncoder, // 持續時間編碼器,將持續時間編碼為字串  
       EncodeCaller:   zapcore.ShortCallerEncoder,    // 呼叫編碼器,顯示檔名和行號  
    }  
    encoder := zapcore.NewConsoleEncoder(encoderConfig)                    // 建立控制檯編碼器,使用編碼配置  
    atomicLevel := zap.NewAtomicLevel()                                    // 建立原子級別,用於動態設定日誌級別  
    atomicLevel.SetLevel(zap.InfoLevel)                                    // 設定日誌級別,只有 Info 級別及以上的日誌才會輸出  
    core := zapcore.NewCore(encoder, zapcore.Lock(os.Stdout), atomicLevel) // 將日誌輸出到標準輸出  
    Logger = zap.New(core, zap.AddCaller(), zap.Development()).Sugar()     // 建立 Logger,新增呼叫者和開發模式  
}

服務端

單節點的 etcd 服務比較簡單,叢集的稍微麻煩一些,由於我只是用來作為演示服務,所以選擇了最簡單的方法:

brew install etcd

然後執行 etcd 命令即可啟動一個 etcd 服務,預設埠號是 2379

客戶端

我定義了一個全域性的客戶端,程式碼如下:

const timeOut = 5 * time.Second // 超時時間

var cli *clientv3.Client // etcd 客戶端,全域性變數  

// init  
//  
//  @Description: 初始化連線  
func init() {  
    client, err := clientv3.New(clientv3.Config{  
       Endpoints:   []string{"localhost:2379"}, // etcd 伺服器地址  
       DialTimeout: timeOut,                    // 連線超時時間  
    })  
    if err != nil {  
       panic("連線伺服器失敗!!!") // 初始化失敗  
    } else {  
       cli = client // 初始化成功  
    }  
}

然後我定義了一個關閉客戶端的方法:

// close  
//  
//  @Description: 關閉連線  
func close() {  
    func(cli *clientv3.Client) {  
       err := cli.Close()  
       if err != nil {  
          Logger.Error("關閉連線失敗!!!")  
       }  
    }(cli)  
}

讀寫測試

下面是根據官方文件寫了一個讀寫測試:

func TestEtcd(t *testing.T) {  
    defer close()                                                     // 關閉連線  
    ctx, cancel := context.WithTimeout(context.Background(), timeOut) //  
    _, _ = cli.Put(ctx, "key", "value")                               // 寫入鍵值對  
    cancel()                                                          // 取消上下文  
    ctx, cancel = context.WithTimeout(context.Background(), timeOut)  // 重新建立上下文  
    resp, _ := cli.Get(ctx, "", clientv3.WithPrefix())                // 獲取所有鍵值對  
    cancel()                                                          // 取消上下文  
    for _, kv := range resp.Kvs {                                     // 遍歷鍵值對  
       Logger.Infof("%s: %s\n", kv.Key, kv.Value)       // 列印鍵值對  
       cli.Delete(context.Background(), string(kv.Key)) // 刪除鍵值對  
    }  
}

控制檯輸出內容:

=== RUN   TestEtcd
2024-06-04T20:10:16.212+0800    INFO    test/etcd_test.go:79    key: value

2024-06-04T20:10:16.222+0800    INFO    test/etcd_test.go:85    
2024-06-04T20:10:16.222+0800    INFO    test/etcd_test.go:86    刪除結果: 0
--- PASS: TestEtcd (0.04s)
PASS

etcd 框架一個主要特徵就是分散式,可以用來進行分散式鎖的實現,以及基於分散式鎖其他功能的實現,下面分享 etcd 鎖的使用。

func TestLock(t *testing.T) {  
    defer close()                                         // 關閉連線  
    session, _ := concurrency.NewSession(cli)             // 建立會話  
    defer session.Close()                                 // 關閉會話  
    mutex := concurrency.NewMutex(session, "/funtester/") // 建立互斥鎖,鎖定 funtester 鍵  
    ctx := context.Background()                           // 建立上下文  
    if err := mutex.Lock(ctx); err != nil {               // 加鎖,如果失敗,列印錯誤資訊  
       Logger.Error("加鎖失敗:", err)  
       return  
    }  
    Logger.Info("加鎖成功")                       // 加鎖成功  
    if err := mutex.Unlock(ctx); err != nil { // 解鎖,如果失敗,列印錯誤資訊  
       Logger.Error("解鎖失敗:", err)  
       return  
    }  
    Logger.Info("解鎖成功") // 解鎖成功  
}

控制檯列印日誌如下:

=== RUN   TestLock
2024-06-04T22:14:17.820+0800    INFO    test/etcd_test.go:92    加鎖成功
2024-06-04T22:14:17.826+0800    INFO    test/etcd_test.go:97    解鎖成功
--- PASS: TestLock (0.06s)
PASS
  • 2021 年原創合集
  • 2022 年原創合集
  • 2023 年原創合集
  • 服務端功能測試
  • 效能測試專題
  • Java、Groovy、Go、Python
  • 單元&白盒&工具合集
  • 測試方案&BUG&爬蟲&UI 自動化
  • 測試理論雞湯
  • 社群風采&影片合集
如果覺得我的文章對您有用,請隨意打賞。您的支援將鼓勵我繼續創作!
打賞支援
暫無回覆。

相關文章