golang使用服务发现系统consul

changjixiong · · 14978 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

本文的完整代码见 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/changjixiong/goNotes/tree/master/consulnotes

  假设在一个系统中,节点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秒才注销服务,原因待查。


有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

14978 次点击  ∙  1 赞  
加入收藏 微博
1 回复  |  直到 2018-08-28 19:06:34
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传