golang使用服務發現系統consul

  本文的完整代碼見 https://github.com/changjixiong/goNotes/tree/master/consulnotes ,若是文中沒有顯示連接說明連接在被轉發的時候被幹掉了,請搜索找到原文閱讀。node

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.」mysql

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

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

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

範例

  網上關於consul的文檔及使用說明有不少,然而卻缺乏關於使用的範例,接下來的內容將用一個範例來演示如何找到可服務的節點。完整的代碼見https://github.com/changjixiong/goNotes/tree/master/consulnotesgithub

  假設在一個系統中,節點A須要訪問某種服務,該服務有N個節點可提供服務,這些節點位於服務集羣groupB,節點A只須要鏈接上groupB中的任一節點便可得到服務。golang

啓動consul

  consul提供開發模式用於啓動單節點服務供開發調試用,運行命令consul agent -dev 啓動consul,輸出的信息中有一行web


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

  顯示了consul運行參數,經過網址http://127.0.0.1:8500/ui/#/dc1/nodes能夠查看節點與服務shell

註冊服務並添加健康檢查

  下面的代碼將向consul註冊一個服務api

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秒才註銷服務,緣由待查。