consul

consulgolang开发的一个高可用的分布式服务注册系统,有service discoverykey/value,健康检查, 节点选举,多数据中心等功能,与zookeeperetcd等相似。微服务框架go-micro默认使用consul作服务发现。

安装

go get github.com/hashicorp/consul

运行

安装好consul后, 我们需要运行agentagent可以以server mode或者client mode方式运行,通常一个数据中心需要3个或者5个server mode, 其他的运行client modeclient agent是一个轻量级的进程,完成健康检查和转发请求到相应的server agent, 可参考consul cluster

consul agent -dev
# or
consul agent -dev -advertise=127.0.0.1

服务运行后, 我们可以在浏览器中打开consul的控制界面了,访问http://localhost:8500,界面如下图:

consul web ui

服务发现

consul 提供服务注册和服务查询的功能。服务指定的我们提供某个需求实现的接口,在consul中,我们的服务看起来如下所示:

{
  "service": {
    "name": "redis",
    "tags": ["primary"],
    "address": "",
    "port": 8000,
    "enable_tag_override": false,
    "checks": [
      {
        "script": "/usr/local/bin/check_redis.py",
        "interval": "10s"
      }
    ]
  }
}

上面就算是定义了一个服务name=redis, 我们给出了该服务的tags, 以及服务的addressportchecks指定我们对该服务需要做定时健康检查。

consul服务注册有两种方式:服务定义和HTTP API调用。通常我们使用服务定义,下面我们说明如何来注册服务。

  • 服务定义
    保存下面文件到/etc/consul.d/web.json
{
  "service": {
    "name": "web",
    "tags": ["rails"],
    "address": "127.0.0.1",
    "port": 8000
  }
}

启动consul agent

consul agent -dev -advertise=127.0.0.1 -config-dir=/etc/consul.d

consul控制界面,可以发现已经成功注册了我们的web服务,如下图所示:

consul web ui

  • HTTP API
package main

import (
    "fmt"
    consulapi "github.com/hashicorp/consul/api"
    "log"
    "time"
)

// start consul
// consul agent -dev -enable-script-checks --advertise=127.0.0.1

// to listen localhost:8080
// nc -lp 8000

// consul web
// http://127.0.0.1:8500/ui

const Id = "1234567890"

func testRegister() {

    fmt.Println("test begin .")
    config := consulapi.DefaultConfig()
    //config.Address = "localhost"
    fmt.Println("defautl config : ", config)
    client, err := consulapi.NewClient(config)
    if err != nil {
        log.Fatal("consul client error : ", err)
    }
    //创建一个新服务。
    registration := new(consulapi.AgentServiceRegistration)
    registration.ID = Id
    registration.Name = "web"
    registration.Port = 8000
    registration.Tags = []string{"rails"}
    registration.Address = "127.0.0.1"

    //  //增加check。
    //  check := new(consulapi.AgentServiceCheck)
    //  check.HTTP = fmt.Sprintf("http://%s:%d%s", registration.Address, registration.Port, "/check")
    //  //设置超时 5s。
    //  check.Timeout = "5s"
    //  //设置间隔 5s。
    //  check.Interval = "5s"
    //  //注册check服务。
    //  registration.Check = check
    //  log.Println("get check.HTTP:", check)
    //
    //  err = client.Agent().ServiceRegister(registration)
    //
    //  if err != nil {
    //      log.Fatal("register server error : ", err)
    //  }

    //增加check。
    check := new(consulapi.AgentServiceCheck)

    check.Args = []string{"sh", "-c", "sleep 1 && exit 0"}
    //设置超时 5s。
    check.Timeout = "5s"
    //设置间隔 5s。
    check.Interval = "5s"
    //注册check服务。
    registration.Check = check

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

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

func testDeregister() {
    fmt.Println("test begin .")
    config := consulapi.DefaultConfig()
    //config.Address = "localhost"
    fmt.Println("defautl config : ", config)
    client, err := consulapi.NewClient(config)
    if err != nil {
        log.Fatal("consul client error : ", err)
    }

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

func main() {
    log.Println("ready to register service")
    testRegister()

    time.Sleep(1 * time.Minute)

    log.Println("ready to deregister service")
    testDeregister()
}

服务查询, 服务消费者为了或者某个名字的服务提供者的具体信息(服务地址,端口等等),需要从consul agent进行查询。

  • 域名查询 dig @127.0.0.1 -p 8600 web.service.consul
    结果如下所示:
    dig dns query

    另外还可以通过命令dig @127.0.0.1 -p 8600 web.service.consul SRV获得更详细的服务信息。
    dig service query
  • 通过HTTP API查询curl http://localhost:8500/v1/catalog/service/web
    HTTP API service query

key/value

consul提供用于存储分布式环境需要的配置以及服务信息的key/value数据库。当然我们也可以根据需要存储我们自己的数据,示例演示了基本的key/value操作,代码如下所示:

package main

import (
    "github.com/hashicorp/consul/api"
    "log"
)

func main() {
    config := api.DefaultConfig()
    client, err := api.NewClient(config)
    if err != nil {
        log.Fatal(err)
    }
    kv := client.KV()

    pair := &api.KVPair{Key: "key-1", Value: []byte("10")}

    if _, err := kv.Put(pair, nil); err != nil {
        log.Fatal(err)
    }

    if p, _, err := kv.Get(pair.Key, nil); err != nil {
        log.Fatal(err)
    } else {
        log.Println(p.Key, "=>", string(p.Value))
    }

    if ps, _, err := kv.List(pair.Key, nil); err != nil {
        log.Fatal(err)
    } else {
        for _, p := range ps {
            log.Println(p.Key, "=>", string(p.Value))
        }
    }

    if ks, _, err := kv.Keys(pair.Key, "/", nil); err != nil {
        log.Fatal(err)
    } else {
        for _, k := range ks {
            log.Println(k)
        }
    }

    if _, err := kv.Delete(pair.Key, nil); err != nil {
        log.Fatal(err)
    } else {
        log.Println("delete key '", pair.Key, "' successfully")
    }
}

健康检查

consul在每个server上都运行了一个轻量级的agent,因此健康检查(health check)可以从两个层次来进行,一个是针对service的应用级别检查,另一个是针对系统级别的检查。跟service一样,我们同样可以采用两种方式来定义检查: 检查定义和HTTP APIconsul提供五种方式的健康检查:

  • Script + Interval

这种方式即使采用脚本定时(默认30s)的去检查,脚本的exit code和输出内容(最大长度4K,多余的将被截取)作为检查的结果。consul agent启动时,需设置enable_script_checks选项true

  • HTTP + Interval

定时(默认30s)发送HTTP请求,根据请求返回的状态码来确定服务的检查结果,2xx表示服务正常, 429表示请求过多(警告状态, warning), 其他状态表示服务不正常(failure)。通常采用curl或者某种HTTP工具来实现。

  • TCP + Interval

定时(默认30s)连接指定的hostname/ip(默认locahost)和port,如果连接成功,表示服务正常,否则不正常。

  • Time to Live (TTL)

由服务定时(指定的TTL时长)去维护某个检查状态,如果服务无法正常去更新检查状态,则该状态会被标记为失败(failure)。

  • Docker + Interval

我们的服务采用docker方式来部署并使用Docker Exec API来运行时,需设置enable_script_checks选项true

如何定义健康检查可参考前文所述服务注册#HTTP API

分布式锁的实现

参考文章
分布式锁的实现方式目前有redisSETNXetcd。下面使用consul来实现。
consul官方关于信号量的说明

package main

import (
    "fmt"
    consulapi "github.com/hashicorp/consul/api"
    "log"
    "sync"
    "time"
)

var n int = 0

func print(wg *sync.WaitGroup, char string) {
    go func() {
        config := consulapi.DefaultConfig()
        client, err := consulapi.NewClient(config)

        lock, err := client.LockKey("lockthis")
        if err != nil {
            log.Fatal(err)
        }

        defer wg.Done()
        for {
            _, err := lock.Lock(nil)
            if err != nil {
                log.Fatal(err)
            }
            if n >= 20 {
                if err := lock.Unlock(); err != nil {
                    log.Fatal(err)
                }
                break
            }
            n++
            fmt.Println(char, "->", n)
            time.Sleep(1 * time.Second)
            if err := lock.Unlock(); err != nil {
                log.Fatal(err)
            }
        }
    }()
}

func main() {
    wg := &sync.WaitGroup{}
    wg.Add(2)

    print(wg, "A")
    print(wg, "B")

    wg.Wait()
    fmt.Println("Done")
}

执行该代码会依次打印出字母AB

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,980评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,422评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,130评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,553评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,408评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,326评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,720评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,373评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,678评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,722评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,486评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,335评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,738评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,283评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,692评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,893评论 2 335