golang使用服務發現系統consul

changjixiong發表於2017-06-06

  本文的完整程式碼見 https://github.com/changjixiong/goNotes/tree/master/consulnotes ,如果文中沒有顯示連結說明連結在被轉發的時候被幹掉了,請搜尋找到原文閱讀。

consul是什麼

"Consul is a distributed, highly available, datacenter-aware, service discovery and configuration system. It can be used to present services and nodes in a flexible and powerful interface that allows clients to always have an up-to-date view of the infrastructure they are a part of."

  引用一段網上對consul文件的翻譯(http://consul.la/intro/what-is-consul)

Consul有多個元件,但是整體來看,它是你基礎設施中用於發現和配置服務的一個工具。它提供如下幾個關鍵功能:

* 服務發現: Consul的某些客戶端可以提供一個服務,例如api或者mysql,其它客戶端可以使用Consul去發現這個服務的提供者。使用DNS或者HTTP,應用可以很容易的找到他們所依賴的服務。
* 健康檢查: Consul客戶端可以提供一些健康檢查,這些健康檢查可以關聯到一個指定的服務(服務是否返回200 OK),也可以關聯到本地節點(記憶體使用率是否在90%以下)。這些資訊可以被一個操作員用來監控叢集的健康狀態,被服務發現元件路由時用來遠離不健康的主機。
* 鍵值儲存: 應用可以使用Consul提供的分層鍵值儲存用於一些目的,包括動態配置、特徵標記、協作、leader選舉等等。通過一個簡單的HTTP API可以很容易的使用這個元件。
* 多資料中心: Consul對多資料中心有非常好的支援,這意味著Consul使用者不必擔心由於建立更多抽象層而產生的多個區域。
Consul被設計為對DevOps群體和應用開發者友好,他非常適合現代的、可伸縮的基礎設施。

範例

  網上關於consul的文件及使用說明有很多,然而卻缺少關於使用的範例,接下來的內容將用一個範例來演示如何找到可服務的節點。完整的程式碼見https://github.com/changjixion ... notes

  假設在一個系統中,節點A需要訪問某種服務,該服務有N個節點可提供服務,這些節點位於服務叢集groupB,節點A只需要連線上groupB中的任一節點即可獲得服務。

啟動consul

  consul提供開發模式用於啟動單節點服務供開發除錯用,執行命令consul agent -dev 啟動consul,輸出的資訊中有一行

Client Addr: 127.0.0.1 (HTTP: 8500, HTTPS: -1, DNS: 8600, RPC: 8400)

  顯示了consul執行引數,通過網址http://127.0.0.1:8500/ui/#/dc1/nodes可以檢視節點與服務

註冊服務並新增健康檢查

  下面的程式碼將向consul註冊一個服務

import (
    "fmt"
    "log"

    "net/http"

    consulapi "github.com/hashicorp/consul/api"
)

func consulCheck(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "consulCheck")
}

func registerServer() {

    config := consulapi.DefaultConfig()
    client, err := consulapi.NewClient(config)

    if err != nil {
        log.Fatal("consul client error : ", err)
    }

    checkPort := 8080

    registration := new(consulapi.AgentServiceRegistration)
    registration.ID = "serverNode_1"
    registration.Name = "serverNode"
    registration.Port = 9527
    registration.Tags = []string{"serverNode"}
    registration.Address = "127.0.0.1"
    registration.Check = &consulapi.AgentServiceCheck{
        HTTP:                           fmt.Sprintf("http://%s:%d%s", registration.Address, checkPort, "/check"),
        Timeout:                        "3s",
        Interval:                       "5s",
        DeregisterCriticalServiceAfter: "30s", //check失敗後30秒刪除本服務
    }

    err = client.Agent().ServiceRegister(registration)

    if err != nil {
        log.Fatal("register server error : ", err)
    }

    http.HandleFunc("/check", consulCheck)
    http.ListenAndServe(fmt.Sprintf(":%d", checkPort), nil)

}

  consulapi.DefaultConfig()的原始碼顯示預設採用的是http方式連線"127.0.0.1:8500",前文中顯示consul開發模式預設提供的http服務是在127.0.0.1:8500,在實際使用中需要設定為實際的引數。

  consulapi.AgentServiceCheck中的HTTP指定了健康檢查的介面地址即127.0.0.1:8080/check,consulCheck函式響應這個介面呼叫,返回200狀態碼及一段字串"consulCheck",健康檢查還有其他幾種方式,具體可以參考官方文件。

  consulapi.AgentServiceCheck中的DeregisterCriticalServiceAfter指定檢查不通過後多長時間登出本服務,這裡設定為30秒。

  向consul註冊的服務地址為127.0.0.1:9527,以下是在127.0.0.1:9527上提供的echo服務。

ln, err := net.Listen("tcp", "0.0.0.0:9527")

    if nil != err {
        panic("Error: " + err.Error())
    }

    for {
        conn, err := ln.Accept()

        if err != nil {
            panic("Error: " + err.Error())
        }

        go EchoServer(conn)
    }

  服務啟動後,訪問http://127.0.0.1:8500/ui/#/dc1/nodes 會發現 "2 services",點開後會在頁面上看到serverNode 127.0.0.1:9527,表明服務資訊已經註冊。以下資訊顯示健康檢查通過。

HTTP GET http://127.0.0.1:8080/check: 200 OK Output: consulCheck

使用服務

  服務使用方client通過以下程式碼向consul查詢可用的服務(忽略錯誤處理)

client, err := consulapi.NewClient(consulapi.DefaultConfig())//非預設情況下需要設定實際的引數
...
services, err = client.Agent().Services()
...
if _, found := services["serverNode_1"]; !found {
            log.Println("serverNode_1 not found")
            continue
} //查詢名為serverNode_1的服務

  查詢到服務後連線服務併傳送資料

conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", service.Address, service.Port))
...

  client先啟動,服務後啟動,然後服務關閉,執行日誌如下:

serverNode_1 not found
get: EchoServerHello World, 001
get: EchoServerHello World, 002
...
get: EchoServerHello World, 008
Read Buffer Error: EOF
dial tcp 127.0.0.1:9527: getsockopt: connection refused
dial tcp 127.0.0.1:9527: getsockopt: connection refused
...
dial tcp 127.0.0.1:9527: getsockopt: connection refused
serverNode_1 not found
serverNode_1 not found

  服務啟動前提示serverNode_1沒找到,服務啟動後資料互動正常,服務關閉後consul尚未登出服務client提示服務無法連線,稍後consul登出了失效的服務,client顯示服務沒有找到。

使用場景設想

  假設一個網路遊戲有N個副本服務節點提供服務,在生產執行期間,有的節點可能故障,有些節點可能負載過高,有些節點可能故障後自行回覆需要能重新上線提供服務。通過consul系統可以隨時讓閘道器伺服器或者邏輯伺服器獲取可用的副本服務節點並將請求轉發到該節點,保持副本服務的高效可用。

  其他型別的服務也可以採用同樣的方式進行水平擴充套件。進一步的,可以在負載高的時候啟動新節點,在負載低的時候關閉部分節點,在雲伺服器上實現這些非常方便,並且由於是按使用計費,通過負載增加或關閉節點也可以避免雲伺服器資源的浪費從而降低費用。

一點問題

  服務註冊時設定檢查失敗後30秒登出服務,實際執行中大約80秒才登出服務,原因待查。

相關文章