etcd與service-registration-discovery
宣告:本文對etcd的原理,實現細節,效能等均不考慮,僅將etcd作為一個分散式的K-V儲存元件。本文提價程式碼均在: ofollow,noindex">github.com/yeqown/server-common/tree/master/framework/etcd
一個核心
etcd, 分散式Key-Value儲存工具。詳細資料 由此去
兩個物件
- 服務提供者(在測試環境中,我定義為單獨的服務例項),也就是服務的提供者,需要向其他服務暴露自己的ip和埠,方便呼叫。
- 服務呼叫者(同樣地,在測試環境中我定義為反向代理閘道器程式),也就是服務的呼叫者,需要獲取到 可使用 地服務地址並呼叫。
關於服務註冊與發現
就具體場景而言:我們的生產環境中使用了一個代理閘道器伺服器,用於轉發移動端和PC端的API請求,並完成其他功能。所有的服務例項配置都是硬編碼在閘道器程式中,頂多就是抽離出來成了一個配置檔案。這樣做的缺點很明顯:“非動態”。也就意味著,一旦有服務Down掉,那麼使用者訪問則可能異常,甚至導致整個服務的崩潰;其次,需要對服務進行擴容的情況下,則需要先進行服務部署再更新閘道器程式,步驟繁瑣且容易出錯。
註冊:對於同一組服務,配置一個統一的字首(如圖上的”/specServer”),不同例項使用ID加以區分。
將現行服務改造成為上述模式需要解決的問題:
- etcd 配置安裝
- 閘道器程式改造(監聽etcd的節點夾子/prefix;適配動態的服務例項呼叫)
- 服務例項改造(註冊服務例項到etcd;心跳更新;其他配套設施,異常退出刪除註冊資訊)
etcd安裝配置在github.com已經非常詳細了。在這裡貼一下我在本地測試時候啟動的指令碼(這部分是從etcd-demo獲取到的,做了針對埠的改動):
#!/bin/bash # For each machine TOKEN=token-01 CLUSTER_STATE=new NAME_1=machine1 NAME_2=machine2 NAME_3=machine3 HOST_1=127.0.0.1 HOST_2=127.0.0.1 HOST_3=127.0.0.1 CLUSTER=${NAME_1}=http://${HOST_1}:2380,${NAME_2}=http://${HOST_2}:2381,${NAME_3}=http://${HOST_3}:2382 # For machine 1 THIS_NAME=${NAME_1} THIS_IP=${HOST_1} etcd --data-dir=machine1.etcd --name ${THIS_NAME} \ --initial-advertise-peer-urls http://${THIS_IP}:2380 --listen-peer-urls http://${THIS_IP}:2380 \ --advertise-client-urls http://${THIS_IP}:2377 --listen-client-urls http://${THIS_IP}:2377 \ --initial-cluster ${CLUSTER} \ --initial-cluster-state ${CLUSTER_STATE} --initial-cluster-token ${TOKEN} & # For machine 2 THIS_NAME=${NAME_2} THIS_IP=${HOST_2} etcd --data-dir=machine2.etcd --name ${THIS_NAME} \ --initial-advertise-peer-urls http://${THIS_IP}:2381 --listen-peer-urls http://${THIS_IP}:2381 \ --advertise-client-urls http://${THIS_IP}:2378 --listen-client-urls http://${THIS_IP}:2378 \ --initial-cluster ${CLUSTER} \ --initial-cluster-state ${CLUSTER_STATE} --initial-cluster-token ${TOKEN} & # For machine 3 THIS_NAME=${NAME_3} THIS_IP=${HOST_3} etcd --data-dir=machine3.etcd --name ${THIS_NAME} \ --initial-advertise-peer-urls http://${THIS_IP}:2382 --listen-peer-urls http://${THIS_IP}:2382 \ --advertise-client-urls http://${THIS_IP}:2379 --listen-client-urls http://${THIS_IP}:2379 \ --initial-cluster ${CLUSTER} \ --initial-cluster-state ${CLUSTER_STATE} --initial-cluster-token ${TOKEN} &
對於程式的改造,鑑於服務較多且etcd操作流程大體一致,便簡單包裝了一下,專案地址見文首位置。
1.對於呼叫方使用示例如下:
// etcdtest/gw.go func main() { // ... endpoints := []string{ "http://127.0.0.1:2377", "http://127.0.0.1:2379", "http://127.0.0.1:2378", } // 連線etcd獲取KeysAPI kapi, err := etcd.Connect(endpoints...) if err != nil { fmt.Println(err) os.Exit(2) } // debug more, more log ~ etcd.OpenDebug(true) // etcd watch, 監聽/prefix目錄下的改動(“expire;set;update;delete”) // 如:set {Key: /prefix/srv_3457, CreatedIndex: 1155, ModifiedIndex: 1155, TTL: 12} // 並更新watcher.members, 維持最新的節點狀態和數量 watcher = etcd.NewWatcher(kapi, "prefix") go watcher.Watch() // ... } func ServeHTTP() { // ... srvs := watcher.RangeMember() // 獲取所有可用的服務節點 // ... }
2.對於請求提供方,使用示例如下:
// etcdtest/server.go func main() { // ... endpoints := []string{ "http://127.0.0.1:2377", "http://127.0.0.1:2379", "http://127.0.0.1:2378", } etcd.OpenDebug(true) kapi, err := etcd.Connect(endpoints...) if err != nil { fmt.Errorf(err.Error()) os.Exit(2) } // 根據服務生成一個provider, 用於生成K:V provider := etcd.NewProvider( fmt.Sprintf("srv_%d", *port),// name fmt.Sprintf("http://127.0.0.1:%d", *port), // addr ) ctx, cancel := context.WithCancel(context.Background()) // 每10s設定一個TTL=12s的 “/prefix/id”:“http://host:port” 的的鍵值對 // 10s和12s是寫死的,沒有考慮動態~~,後續考慮升級,目前僅僅是測試。 go provider.Heartbeat(ctx, kapi, &etcd.ProvideOptions{ NamePrefix: "prefix", SetOpts:nil, }) //... }
關於詳細的程式碼,可以參見: