SEO

undefined: strings.Builder

重点

  • 解决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",
}
"/login-srv"

最新版的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/etcdgit clonego getdownload zip%GOPATH%/src/go.etcd.io/etcd
cd %GOPATH%/src/go.etcd.io/etcdGOOS=linux GOARCH=amd64 go build -o etcdcd %GOPATH%/src/go.etcd.io/etcd/etcdctlGOOS=linux GOARCH=amd64 go build -o etcdctl%GOPATH%/src/go.etcd.io/etcdDockerfile-releasedocker build -f Dockerfile-release .docker image lsdocker tag <双none的镜像id> etcd:latestdocker 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:2379ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379

解决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