1.安装etcd
拉取镜像
docker pull bitnami/etcd

创建网络
docker network create app-tier --driver bridge

运行容器
docker run -d --name etcd-server --network app-tier --publish 2379:2379 --publish 2380:2380 --env ALLOW_NONE_AUTHENTICATION=yes --env ETCD_ADVERTISE_CLIENT_URLS=http://etcd-server:2379 bitnami/etcd:latest

Etcd-keeper(也称为etcd-keeperd)是一个用于管理和监控etcd集群的工具。
拉取Etcd-keeper镜像
docker pull evildecay/etcdkeeper

检查etcd容器ip
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' etcd-server
使用获取到的ip放在下面,比如我是172.18.0.2
docker run -d -p 8080:8080 -e ETCD_SERVERS=http://172.18.0.2:2379 --network=app-tier --name etcd-keeper evildecay/etcdkeeper

查看etcd帮助信息
docker exec etcd-server etcdctl -h

2.代码演示
目录结构如下
新建个项目grpc-etcd,再这之下新建个目录chitchat,再这之下新建client,proto,server

grpc基础调用
proto包下新建helloworld.proto
//指定版本 默认版本2
syntax = "proto3";
// 指定等会文件生成出来的package
package server;
//代码生成目录
option go_package = "grpc-etcd/chitchat/proto";
service SayService{
//rpc服务的函数名 (传人参数)返回(返回参数)
//一元调用
rpc SayHello(SayRequest) returns(SayResponse){}
}
//定义消息
message SayRequest{
string name = 1;
string msg = 5;
}
message SayResponse{
string msg = 5;
}生成代码
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relati ve .\chitchat\proto\helloworld.proto


下载缺失依赖
go mod tidy

在server包下新建server.go
package main
import (
"context"
"google.golang.org/grpc"
"grpc/chitchat/proto"
"log"
"net"
)
func main() {
//tcp监听9090
listen, err := net.Listen("tcp", "localhost:9090")
if err != nil {
log.Fatal("出现错误", err)
return
}
newServer := grpc.NewServer()
proto.RegisterSayServiceServer(newServer, &server{})
log.Printf("server listen : %d", listen.Addr())
err = newServer.Serve(listen)
if err != nil {
log.Fatal(err)
}
}
type server struct {
proto.UnimplementedSayServiceServer
}
func (server) SayHello(ctx context.Context, in *proto.SayRequest) (*proto.SayResponse, error) {
log.Printf("服务端接收到 : %v\n", in)
return &proto.SayResponse{
Msg: "Hello client , This is server!",
}, nil
}一个简单的一元调用服务端代码,不再阐述。
同样在client包下新建client.go
代码如下:
package main
import (
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"grpc/chitchat/proto"
"log"
)
func main() {
//建立与服务器的连接
connect, err := grpc.Dial("localhost:9090", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatal(err)
return
}
defer connect.Close()
//创建客户端对象 client
client := proto.NewSayServiceClient(connect)
sayHello(client)
}
func sayHello(client proto.SayServiceClient) {
//创建一个上下文对象 ctx
ctx := context.Background()
sayRequest := &proto.SayRequest{
Name: "Dreams",
Msg: "Hello server , This is client!",
}
sayHello, err := client.SayHello(ctx, sayRequest)
if err != nil {
log.Fatal(err)
return
}
log.Println("客户端接收到 :", sayHello.Msg)
}一个简单的grpc客户端,,不再阐述。
运行如下:


etcd注册
功能正常,现在来编写etcd注册和发现
下载etcd依赖
go get go.etcd.io/etcd/client/v3
新建一个etcd包
在etcd下新建etcd.go
package etcd
import clientv3 "go.etcd.io/etcd/client/v3"
func GetEtcdClient() (*clientv3.Client, error) {
client, err := clientv3.New(clientv3.Config{
Endpoints: []string{"localhost:2379"},
})
return client, err
}上述代码用于获取 etcd 客户端。
clientv3.New 是一个函数,用于创建一个 etcd 客户端。它接受一个 clientv3.Config 类型的参数,并返回一个 *clientv3.Client 类型的指针和一个 error 类型的值。
clientv3.Config 结构体中的字段包含了与 etcd 客户端相关的配置信息,例如要连接的端点地址、认证信息等。
在etcd下新建grpc_etcd.go
package etcd
import (
"context"
"fmt"
clientv3 "go.etcd.io/etcd/client/v3"
)
func GrpcRegister(serviceName, addr string) error {
client, err := GetEtcdClient()
//可以对serviceName做处理
key := serviceName
ctx := context.Background()
//创建租约
leaseRes, err := client.Grant(ctx, 10)
if err != nil {
return err
}
//向etcd注册
_, err = client.Put(ctx, key, addr, clientv3.WithLease(leaseRes.ID))
if err != nil {
return err
}
alive, err := client.KeepAlive(ctx, leaseRes.ID)
if err != nil {
return err
}
go func() {
for item := range alive {
fmt.Printf("leaseID :%x , TTl :%v\n", item.ID, item.TTL)
}
}()
return nil
}调用 GetEtcdClient() 函数获取 etcd 客户端对象。根据服务名构建注册到 etcd 中的 key。创建一个租约,设置租约的过期时间为 10 秒,并获取租约 ID。调用 client.Put() 函数将服务地址注册到 etcd 中,同时指定该记录的租约 ID。调用 client.KeepAlive() 函数续租,以确保服务在 etcd 中的记录不会被删除。在一个新的 goroutine 中,打印租约 ID 和 TTL,以便观察租约的状态。如果函数执行过程中出现错误,直接返回错误信息。
client.Grant 是 etcd 用于创建一个新的租约。它接受一个 context.Context 参数和一个过期时间(以秒为单位)参数。
client.Put 方法将键值对存储到 etcd 中,其中 key 是键名,value 是键值。
clientv3.WithLease 选项指定了租约 ID,以关联该键值对与特定的租约。
client.KeepAlive() 函数续租,以确保服务在 etcd 中的记录不会被删除。
在server.go的main函数加入注册代码
func main() {
//tcp监听9090
listen, err := net.Listen("tcp", "localhost:9090")
if err != nil {
log.Fatal("出现错误", err)
return
}
newServer := grpc.NewServer()
proto.RegisterSayServiceServer(newServer, &server{})
log.Printf("server listen : %d", listen.Addr())
//服务注册进etcd
etcd.GrpcRegister("hello-server", "localhost:9090")
err = newServer.Serve(listen)
if err != nil {
log.Fatal(err)
}
}
运行

同时etcd也成功注册
docker exec etcd-server etcdctl get "hello" --prefix

如果将server.go关闭
etcd因为没有续约,所以会自动删除key

为了不干扰显示,这里将协程打印id代码注释
在etcd下修改grpc_etcd.go
package etcd
import (
"context"
"fmt"
clientv3 "go.etcd.io/etcd/client/v3"
)
func GrpcRegister(serviceName, addr string) error {
client, err := GetEtcdClient()
//可以对serviceName做处理
key := serviceName
ctx := context.Background()
//创建租约
leaseRes, err := client.Grant(ctx, 10)
if err != nil {
return err
}
//向etcd注册
_, err = client.Put(ctx, key, addr, clientv3.WithLease(leaseRes.ID))
if err != nil {
return err
}
_, err = client.KeepAlive(ctx, leaseRes.ID)
if err != nil {
return err
}
//go func() {
//for item := range alive {
//fmt.Printf("leaseID :%x , TTl :%v\n", item.ID, item.TTL)
//}
//}()
return nil
}
etcd发现
在grpc_etcd.go添加发现函数
func GrpcDiscover(serviceName string) string {
client, err := GetEtcdClient()
//可以对serviceName做处理
key := serviceName
// 获取 etcd client.Get 方法获取指定键名的值
getKey, err := client.Get(context.Background(), key)
if err != nil {
log.Fatal(err)
}
//
for _, item := range getKey.Kvs {
if string(item.Key) == serviceName {
return string(item.Value)
}
}
return ""
}
再在client发现
修改client.go的main函数,从etcd获取地址
func main() {
addr := etcd.GrpcDiscover("hello-server")
if addr == "" {
log.Fatal("不存在该地址")
}
//建立与服务器的连接
connect, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatal(err)
return
}
defer connect.Close()
//创建客户端对象 client
client := proto.NewSayServiceClient(connect)
sayHello(client)
}
成功




