golang 使用etcd

SEO

etcd编译时,报undefined: strings.Builder错误,如何解决? (升级go1.12)
etcd镜像版本过低,如何拿到高版本的etcd镜像? (docker)
etcd为什么本机能连,容器连不上? (etcd没有部到0.0.0.0,而是localhost了)
etcd如何进行服务注册,服务发现? (grpc和其他协议的服务发现逻辑是不同的)
etcdctl怎么用? (租约lease, 键值读写put,get,del, key值监听watch)

重点

  • 解决etcd本机能连,容器连不上的问题。
  • 解决etcd容器版本过低(2.0 – > 3.3)
  • 解决etcdctl不会用的问题
  • 解决服务发现的解决方案
  • 解决etcd内网不通的问题

简介

etcd 是一个非常好用的中间件服务,职能很多,这里只介绍服务发现,其它的不多说了~

etcd作服务发现时,整个工程可以分为四个角色:
客户端app主服务节点app子服务节点etcd中间件,
它们是怎么运作服务发现的呢?

  1. app服务节点由开发者开发并部署完成,得到一个 ip:端口(172.21.41.11), 存入etcd,并被一个url-path形式的key索引("/login-srv/node/1")。,同理分布式部署,有好多个通业务节点,各自对应如下:
{
    "/login-srv/node/1": "172.21.41.11:8080",
    "/login-srv/node/2": "172.21.41.12:8080",
    "/login-srv/node/3": "172.21.41.13:8080",
    "/login-srv/node/4": "172.21.41.14:8080",
}
  1. 客户端到达app主服务节点,主服务节点通过etcd,按照key "/login-srv" 索引(匹配到了上述所有),拿到所有子服务的对应端口和ip列表,并通过一定hash来抉择使用哪个结点。
  2. 子服务节点需要定期发送续期指令到etcd,告诉它你活着,不然服务索引会被剔除。

最新版的etcd镜像 (或者可以直接通过docker pull etcd:3.4)

容器跑起来时,一定要指定0.0.0.0的开放网络,不然默认是localhost/127.0.0.1, 宿主机访问不到容器,除非挂载了host模式的网
通过docker search etcd,最多人start的是v2,版本很久。所以我们需要从官网从新拉取最新版的编译并打包进容器里。

官网: https://github.com/etcd-io/etcd
通过git clone / go get / download zip/ 下载至%GOPATH%/src/go.etcd.io/etcd

  • cd %GOPATH%/src/go.etcd.io/etcd 跳至下好的etcd文件夹
  • GOOS=linux GOARCH=amd64 go build -o etcd 交叉编译成镜像环境的可执行etcd程序,产出一个etcd文件
  • cd %GOPATH%/src/go.etcd.io/etcd/etcdctl 跳至etcdctl文件夹
  • GOOS=linux GOARCH=amd64 go build -o etcdctl交叉编译成镜像环境的可执行etcdctl程序,产出一个etcdctl文件
  • %GOPATH%/src/go.etcd.io/etcd 下的Dockerfile-release和前两步准备好的etcd,etcdctl,复制并放在一个新文件夹里
  • 在该新文件夹下: docker build -f Dockerfile-release .
  • docker image ls 后,可以看到一个刚打包好的双none镜像,docker tag <双none的镜像id> etcd:latest
  • docker run -itd -p 2379:2379 -p 2380:2380 --rm -e ETCD_ADVERTISE_CLIENT_URLS=http://0.0.0.0:2379 -e ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379 etcd:latest 运行该容器

golang进行测试是否连接上

main.go

package main

import (
	"github.com/coreos/etcd/clientv3"
	"fmt"
	"time"
	"context"
)

func main() {

	cli, err := clientv3.New(clientv3.Config{
		Endpoints:   []string{"localhost:2379", "localhost:22379", "localhost:32379"},
		// Endpoints:   []string{"localhost:4001"},
		DialTimeout: 5 * time.Second,
	})
	if err != nil {
		fmt.Println("connect failed, err:", err)
		return
	}

	fmt.Println("connect succ")

	defer cli.Close()
	//设置1秒超时,访问etcd有超时控制
	t1:=time.Now()
	ctx, _ := context.WithCancel(context.TODO())
	//操作etcd
	_, err = cli.Put(ctx, "key", "v")
	//操作完毕,取消etcd
	// cancel()

	t2 :=time.Now()
	fmt.Println("put耗时",t2.Sub(t1))
	if err != nil {
		fmt.Println("put failed, err:", err)
		return
	}
	//取值,设置超时为1秒
	ctx, _ = context.WithTimeout(context.Background(), 10*time.Second)
	t1= time.Now()
	resp, err := cli.Get(ctx, "key")
	fmt.Println("get 耗时:",time.Now().Sub(t1))
// 	cancel()
	if err != nil {
		fmt.Println("get failed, err:", err)
		return
	}
	for _, ev := range resp.Kvs {
		fmt.Printf("%s : %s\n", ev.Key, ev.Value)
	}

	//测试redis
}

输出:

connect succ
put耗时 9.0007ms
get 耗时: 3.998ms
key : v

(非grpc)服务发现

如果你的内部通信服务网是grpc实现的,那么官方提供了支持。例子:

import (
	"go.etcd.io/etcd/clientv3"
	etcdnaming "go.etcd.io/etcd/clientv3/naming"

	"google.golang.org/grpc"
)

...

cli, cerr := clientv3.NewFromURL("http://localhost:2379")
r := &etcdnaming.GRPCResolver{Client: cli}
b := grpc.RoundRobin(r)
conn, gerr := grpc.Dial("my-service", grpc.WithBalancer(b), grpc.WithBlock(), ...)

如果是其他协议网,则需要手动实现均衡如下:

使用了 https://www.jianshu.com/p/7c0d23c818a5 这里的工具,作了一点路径和包的修改。
程序比较简单,封装后,只有app子服务方添加自己的ip和端口进etcd,和客户端从etcd获取满足的ip和端口。

package main

import (
	"fmt"
	core "github.com/mistaker/etcdTool"
	"time"
)

// 测试前,确保etcd run in 2379
func main() {
	// 模拟服务方将服务注册进etcd
	ser, e := core.NewServiceReg([]string{"localhost:2379"}, 5)
	if e!=nil {
		panic(e)
	}
	if e := ser.PutService("/user-login/node/111/", "10.0.1.1:8081"); e != nil {
		panic(e)
	}
	if e := ser.PutService("/user-login/node/112/", "10.0.1.1:8082"); e != nil {
		panic(e)
	}

	time.Sleep(7 * time.Second)

	// 模仿客户端从etcd获取服务路径
	cli, e := core.NewClientDis([]string{"localhost:2379"})
	if e!=nil {
		panic(e)
	}
	rs, e := cli.GetService("/user-login/")
	fmt.Println(rs, e)
	select {}
}

输出

续租成功
[10.0.1.1:8081 10.0.1.1:8082] <nil>
2019-07-03 18:23:46.995901 I | set data key : /user-login/node/111/ val: 10.0.1.1:8081
2019-07-03 18:23:46.995901 I | set data key : /user-login/node/112/ val: 10.0.1.1:8082

如果需要做到连接复用,而不是每次都newconn,则需要自己维护conn池。

etcdctl常见命令

  • 这里不贴key的历史版本数据,因为他可能会被策略删除,不可靠。
export ETCDCTL_API=3          # 使用grpc2还是grpc3来连接etcd服务,记得使用3.详情百度一下。

etcdctl put foo bar           # 设置foo的值是bar

etcdctl get foo --print-value-only # 获取foo的值

etcdctl get --prefix --limit=2 foo  # 获取foo前缀的值,会打印key

etcdctl del foo   # 删除foo的key

etcdctl watch foo  # 观察foo的变化

etcdctl lease grant 10  # 创建一个续约对象
etcdctl put --lease=32695410dcc0ca06 foo bar # 将这个续约对象,绑定给foo
etcdctl lease revoke 32695410dcc0ca06  # 将这个续约对象删除,被绑定的所有key都会被del

etcdctl lease timetolive --keys 694d5765fc71500b  # 查看存活时间以及被绑定的key

FAQ

为什么容器连不上,本机连的上

关键点是容器的环境变量,需要设定
ETCD_ADVERTISE_CLIENT_URLS=http://0.0.0.0:2379
ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379
默认发起的主服务是监听127.0.0.1,这个ip在容器里,只会听内环,听不到宿主机的一些网段,改为0.0.0.0则通听!

解决undefined: strings.Builder

本地编译时,需要使用go版本1.12↑,这个错误,在我用1.9时出现了,查了一下百度,是1.9之后添加的东西~~

为啥etcd只适合存配置,不适合存用户进度?
  1. 配置量级是O(a) 常量级,用户进度量级是O(n),n是用户数量。etcd的raft强一致算法,注定了他的qps不行,千万不要像redis一样,使用他。
  2. etcd的历史版本revision系数,是全局的,而不是某个key的粒度。存太多,会让这个revision疯狂自增,触发它的历史数据归档替换。使得revision不可靠。
续约对象依次被多个key绑定,过期时间是从绑定开始算,还是多个key同时失效。

同时失效。续约对象在初始化时,就定死了生命周期。当然它可以通过命令来保持存活etcdctl lease keep-alive 32695410dcc0ca06

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值