go-consul实战

consul简介

官网介绍地址 视频介绍的挺不错的,不过是英文的

Consul is a service mesh solution providing a full featured control plane with service discovery, configuration, and segmentation functionality. Each of these features can be used individually as needed, or they can be used together to build a full service mesh. Consul requires a data plane and supports both a proxy and native integration model. Consul ships with a simple built-in proxy so that everything works out of the box, but also supports 3rd party proxy integrations such as Envoy.
大致的意思是,Consul提供服务网格(可自行搜索一下含义)解决方案,功能齐全,支持控制平面,服务发现、配置和分段功能。这些功能中的每个特性都可以根据需要单独使用,也可以一起使用来构建一个完整的服务网格。Consul需要一个数据平面,并支持代理和原生集成模型。Consul提供了一个简单的内置代理,因此一切都可以开箱即用,但也支持第三方代理集成,如Envoy。

学习参考的资料
官网
中文文档

环境搭建

前面介绍了consul的功能很强大,提供了服务网格的整体解决方案。本文主要使用 consul来演示其中服务发现这个单独的特性。本案例采用的是consul单机进行测试。启动服务consul服务端,需要占用6个端口,因此只部署了单实例,内存占用 17m, 占用端口8300,8301,8302,8500,8502,8600

说明

本文的实操环境是基于虚拟机完成,虚拟机通过静态ip与本机相连,虚拟机局域网ip地址固定为 10.248.174.155。consul的版本为1.11.1

# 虚拟机操作系统
> uname -a
> Linux n248-174-155 4.14.81.bm.15-amd64 #1 SMP Debian 4.14.81.bm.15 Sun Sep 8 05:02:31 UTC 2019 x86_64 GNU/Linux

下载安装

# 下载
wget https://releases.hashicorp.com/consul/1.11.1/consul_1.11.1_linux_amd64.zip
# 解压
unzip consul_1.11.1_linux_amd64.zip

解压后只有一个可执行文件,将这个可执行文件加入到环境变量PATH中即可,记得使用source 命令让PATH的变量重新加载

测试是否安装成功

# consul version 查看版本号
root@n248-174-155:~/file$ consul version
Consul v1.11.1
Revision 2c56447e
Protocol 2 spoken by default, understands 2 to 3 (agent will automatically use protocol >2 when speaking to compatible agents)

另外由于启动时,指定了参数开始 web-ui 所以可以在web端可视化的展示服务的状态,将ip替换成你的ip即可
http://10.248.174.155:8500/

命令实战

一般来说,官方提供的各种语言相关的sdk都原生命令有对应的联系,consul是用go语言编写的,原生api对go的支持非常好。
由于已经将consul 加入到PATH变量中了,所以接下来的操作都和在哪个目录完全没有关系了。

启动 consul

# 后台启动方式,指定了日志文件路径/home/root/consul/consul.log
nohup consul agent -dev -client 0.0.0.0 -ui > /home/root/consul/consul.log 2>&1 &

查看进程

root@n248-174-155:~/file$ netstat -ntpl|grep consul
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
tcp        0      0 127.0.0.1:8300          0.0.0.0:*               LISTEN      793138/consul
tcp        0      0 127.0.0.1:8301          0.0.0.0:*               LISTEN      793138/consul
tcp        0      0 127.0.0.1:8302          0.0.0.0:*               LISTEN      793138/consul
tcp6       0      0 :::8500                 :::*                    LISTEN      793138/consul
tcp6       0      0 :::8502                 :::*                    LISTEN      793138/consul
tcp6       0      0 :::8600                 :::*                    LISTEN      793138/consul

新增kv

consul kv put services/svr1/001 '{"Addr":"192.168.4.5","Port":3358}'
consul kv put services/svr1/002 '{"Addr":"192.164.45.22","Port":3458}'
consul kv put services/svr2/001 '{"Addr":"192.138.44.55","Port":3456}'

新增key 指定额外flags

consul kv put -flags=25 service/svr1/003 '{"Addr":"192.174.35.67","Port":8890}'

查询kv

consul kv get service/svr1/001

查询kv详情

consul kv get -detailed service/svr1/001

查询所有kv 递归查询

consul kv get -recurse

根据key前缀查询,返回所有kv

consul kv get -recurse service/svr1/

根据key前缀查询,只返回key

consul kv get -keys service/svr1/

删除kv

consul kv delete service/svr1/001

根据前缀删除

consul kv delete -recurse service/svr1/

watch 某个前缀的结点,会一次性返回所有结点的信息

consul watch -type=keyprefix -prefix=/services/svr1/001

服务注册

# 指定服务ip地址、端口号、服务唯一id、服务名、服务的多个tag
consul services register -address=124.3.2.4 -port=4458 -id=1234001 -name=mysvr-rpc -tag=v.1.1 -tag=user

为服务注册添加健康检查

heath.json 文件内容

{
  "ID": "service:1234001",
  "Name": "自定义结点健康检查",
  "Notes": "回调http接口查看",
  "DeregisterCriticalServiceAfter": "30s",
  "HTTP": "http://ip:port/consul/health/?id=555",
  "Method": "GET",
  "Interval": "10s",
  "Timeout": "5s",
  "ServiceID":"1234001"
}
  • ID: 健康检查的唯一id,类似于db里面的主键
  • Name: 健康检查的名字,后续可以在自带的web面板展示出来
  • DeregisterCriticalServiceAfter: 代表服务停止多久后进行注销该服务
  • HTTP: 去哪个地址检测服务的健康状态
  • Method: 发送请求的方式
  • Interval:健康检查时间间隔,即心跳间隔
  • Timeout:请求超时时间
  • ServiceID:额外参数,如果指定则是检测具体某个服务的健康状态,不指定则是consul的agent健康状态
# heath.json 定义的是只针对某个服务结点的健康检查,直接从文件中读取具体的数据
curl --request PUT --data @heath.json http://127.0.0.1:8500/v1/agent/check/register

服务卸载

# 根据服务的唯一id卸载服务
consul services deregister -id=1234001

获取所有健康检查条目

curl http://127.0.0.1:8500/v1/agent/checks

获取服务信息

# 只返回健康没问题的
curl http://127.0.0.1:8500/v1/health/service/mysvr-rpc?passing=1

go-consul实战

consul自带服务发现与注册功能,下面将做一个服务注册与发现的例子,仅供参考。

服务注册

注意如果你指定了http的健康检查方式,一定要在对应的地址启动http服务,否则健康检查会失败,直接摘除服务。或者可以使用grpc的方式进行代替。

package consul

import (
	"context"
	"fmt"
	"google.golang.org/grpc/health/grpc_health_v1"
	"log"
	"strings"

	"github.com/hashicorp/consul/api"
	uuid "github.com/satori/go.uuid"
)

const (
	consulIp   = "10.248.174.155"
	consulPort = "8500"
)

func RandomStr(len int) string {
	nUid := uuid.NewV4().String()
	str := strings.Replace(nUid, "-", "", -1)
	if len < 0 || len >= 32 {
		return str
	}
	return str[:len]
}

// HttpReg 服务注册时候提供一个健康检查地址,支持http,grpc
func HttpReg(svrName string, ipAddr string, port int) string {
	// 创建Consul客户端连接
	client, err := api.NewClient(&api.Config{
		Address: fmt.Sprintf("http://%v:%v", consulIp, consulPort),
	})
	if err != nil {
		log.Fatalf("client 创建失败,退出:%v\n", err)
	}
	svrID := RandomStr(32)
	// 设置Consul对服务健康检查的参数
	check := api.AgentServiceCheck{
		HTTP:                           fmt.Sprintf("http://%v:%v/consul/health/?id=%v", "127.0.0.1", 80, svrID), // 健康检查地址,自定义ip和端口
		Interval:                       "3s",                                                                        // 健康检查频率
		Timeout:                        "2s",                                                                        // 健康检查超时
		Notes:                          "Consul 代码健康检查",
		DeregisterCriticalServiceAfter: "5s", // 健康检查失败30s后 consul自动将注册服务删除
		Name:                           "代码自定义检查svr1",
		//GRPC:     fmt.Sprintf("%v:%v/%v", svcHost, svcPort, "svrName"),
	}

	//设置微服务向Consul注册信息
	reg := &api.AgentServiceRegistration{
		ID:      svrID,                      // 服务节点的ID
		Name:    svrName,                    // 服务名称
		Address: ipAddr,                     // 服务IP
		Port:    port,                       // 服务端口
		Tags:    []string{"v1.1", "backup"}, // 标签,可在服务发现时筛选服务,类似v1.1
		Check:   &check,                     // 健康检查
	}

	// 执行注册
	if err := client.Agent().ServiceRegister(reg); err != nil {
		log.Fatalln(err)
	}
	return svrID
}
//HttpUnReg 服务卸载
func HttpUnReg(svrID string) {
	// 创建Consul客户端连接
	client, err := api.NewClient(&api.Config{
		Address: fmt.Sprintf("http://%v:%v", consulIp, consulPort),
	})
	if err != nil {
		log.Fatalf("client 创建失败,退出:%v\n", err)
	}

	// 执行服务卸载
	if err := client.Agent().ServiceDeregister(svrID); err != nil {
		log.Fatalln(err)
	}
}

//========如果使用grpc接口实现健康检查,则需要实现HealthServer 接口,服务启动时候注册这个pb==========

// HealthImpl 健康检查实现
type HealthImpl struct{}

// Check 实现健康检查接口,这里直接返回健康状态
func (h *HealthImpl) Check(ctx context.Context, req *grpc_health_v1.HealthCheckRequest) (*grpc_health_v1.HealthCheckResponse, error) {
	return &grpc_health_v1.HealthCheckResponse{
		Status: grpc_health_v1.HealthCheckResponse_SERVING,
	}, nil
}

// Watch 让HealthImpl实现RegisterHealthServer内部的interface接口
func (h *HealthImpl) Watch(req *grpc_health_v1.HealthCheckRequest, w grpc_health_v1.Health_WatchServer) error {
	return nil
}

服务注册单元测试

package consul

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

func TestClient(t *testing.T) {

	// Get a new client
	client, err := api.NewClient(&api.Config{
		Address: "http://10.248.174.155:8500",
	})
	if err != nil {
		log.Fatalln(err)
	}

	// Get a handle to the KV API
	kv := client.KV()

	// PUT a new KV pair
	p := &api.KVPair{Key: "my-svr", Value: []byte("1000"), Flags: 32}
	_, err = kv.Put(p, nil)
	if err != nil {
		log.Fatalln(err)
	}

	// Lookup the pair
	pair, _, err := kv.Get("my-svr", nil)
	if err != nil {
		log.Fatalln(err)
	}

	fmt.Printf("KV: %v %s\n", pair.Key, pair.Value)
}

func TestHttpReg(t *testing.T) {
	svrID := HttpReg("mysvr-rpc", "127.0.0.1", 3456)
	log.Printf("服务注册成功:%v\n", svrID)
	for true {
		// 制定一个定时器,模拟20s后摘除服务
		t := time.NewTicker(20 * time.Second)
		select {
		case tt := <-t.C:
			HttpUnReg(svrID)
			log.Printf("服务卸载,tt:%v\n", tt)
			return
		}
	}
}

服务发现

package consul

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

// GetNode 没有watch的api 无法感知结点的变更,如果做本地缓存则需要启动协程去定时轮询,全量更新到本地缓存
func GetNode(svrName string) {
	// 创建Consul客户端连接
	client, err := api.NewClient(&api.Config{
		Address: fmt.Sprintf("http://%v:%v", consulIp, consulPort),
	})
	if err != nil {
		log.Fatalf("client 创建失败,退出:%v\n", err)
	}

	// 根据服务名和tag 是否健康检查通过 以及其它option 筛选service实例
	service, meta, serr := client.Health().Service(svrName, "", true, nil)
	log.Printf("结束svr%+v,meta:%+v, err:%v\n", service, meta, serr)
	for _, s := range service {
		fmt.Printf("服务:%+v\n", s.Service)
	}

}

服务发现单元测试

package consul

import "testing"

func TestGetNode(t *testing.T) {
	GetNode("mysvr-rpc")
}

总结

本文没有深入介绍consul的一些底层原理性东西,主要介绍了consul的安装及功能的使用,介绍了一些常用的api命令,最后用go语言简单实现了一下服务注册与发现的过程。

你可能感兴趣的:(golang,实战演练,go,consul,微服务,服务发现,golang)