etcd学习笔记

博客参考:K8s组件:etcd安装、使用及原理(Linux)

bash 复制代码
分布式系统架构中对一致性要求很高,etcd就满足了分布式系统中的一致性要求,实现了分布式一致性键值对存储的中间件。

etcd完整的集群至少需要3台,这样才能选出一个master和两个node,具有强一致性,常用于注册中心(配置共享和服务发现)。现目前是分布式和云原生下的基础组件,如:k8s。

内部使用的raft算法,算法具体内容见我的另外一篇博客,在此描述几个关键点:
	Leader、Candidate、Follower、任期、心跳探测。
	脑裂:当网络恢复之后,旧的Leader发现集群中的新Leader的Term比自己大,则自动降级为Follower,并从新Leader处同步数据达成集群数据一致。
	数据同步流程:当Client发起数据更新请求,请求会先到领袖节点C,节点C会更新日志数据,然后通知群众节点也更新日志,当群众节点更新日志成功后,会返回成功通知给领袖C;当领袖C收到通知后,会更新本地数据,并通知群众也更新本地数据,同时会返回成功通知给Client。
	
结构定义:从raftLog结构体可以看到,里面有两个存储位置,一个是storage,是保存已经持久化过的日志条目,unstable是保存的尚未持久化的日志条目。storage结构中字段的定义,它实际上就是包含一个WAL来保存日志条目,一个Snapshotter负责保存日志快照的。

简易操作:
etcdctl put myKey "this is etcd"
etcdctl get myKey
etcdctl del myKey

etcd实现服务注册与发现:
1) docker pull bitnami/etcd

2) docker run

3)编写proto文件:

message HelloResponse {
    string hello = 1;
}
message RegisterRequest {
    string name = 1;
    string password = 2;
}
message RegisterResponse {
    string uid = 1;
}
service Server {
    rpc Hello(Empty) returns(HelloResponse);
    rpc Register(RegisterRequest) returns(RegisterResponse);
}

4)通过这个文件和一个.sh脚本生成server和client代码。

生成服务端的etcd.go:

func etcdRegister(addr string) error {
	log.Printf("etcdRegister %s\b", addr)
	etcdClient, err := clientv3.NewFromURL(etcdUrl)
	if err != nil {
		return err
	}
	em, err := endpoints.NewManager(etcdClient, serviceName)
	if err != nil {
		return err
	}
	
	err = em.AddEndpoint(context.TODO(), fmt.Sprintf("%s/%s", serviceName, addr), endpoints.Endpoint{Addr: addr})
	if err != nil {
		return err
	}

	return nil
}

func etcdUnRegister(addr string) error {
	log.Printf("etcdUnRegister %s\b", addr)
	if etcdClient != nil {
		em, err := endpoints.NewManager(etcdClient, serviceName)
		if err != nil {
			return err
		}
		err = em.DeleteEndpoint(context.TODO(), fmt.Sprintf("%s/%s", serviceName, addr))
		if err != nil {
			return err
		}
		return err
	}

	return nil
}

生成的服务端的server.go:

func (s Server) Hello(ctx context.Context, request *rpc.Empty) (*rpc.HelloResponse, error) {
	resp := rpc.HelloResponse{Hello: "hello client."}
	return &resp, nil
}

func (s Server) Register(ctx context.Context, request *rpc.RegisterRequest) (*rpc.RegisterResponse, error) {
	resp := rpc.RegisterResponse{}
	resp.Uid = fmt.Sprintf("%s.%s", request.GetName(), request.GetPassword())
	return &resp, nil
}

生成的服务端的main.go:

func main() {
	var port int
	flag.IntVar(&port, "port", 8001, "port")
	flag.Parse()
	addr := fmt.Sprintf("localhost:%d", port)

	ch := make(chan os.Signal, 1)
	go func() {
		//开启协程从ch管道中读取,如果有服务停止,则注销etcd中的服务
		s := <-ch
		etcdUnRegister(addr)
		os.Exit(0)
	}()

	//注册服务
	err := etcdRegister(addr)

	if err != nil {
		panic(err)

	}
	
	lis, err := net.Listen("tcp", addr)

	if err != nil {
		panic(err)
	}

	grpcServer := grpc.NewServer(grpc.UnaryInterceptor(UnaryInterceptor()))

	rpc.RegisterServerServer(grpcServer, Server{})

	log.Printf("service start port %d\n", port)
	
	if err := grpcServer.Serve(lis); err != nil {
		panic(err)
	}
}

func UnaryInterceptor() grpc.UnaryServerInterceptor {
	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
		log.Printf("call %s\n", info.FullMethod)
		resp, err = handler(ctx, req)
		return resp, err
	}
}

生成的客户端的代码client.go:

func main() {
	//获取etcd客户端
	etcdClient, err := clientv3.NewFromURL(etcdUrl)
	if err != nil {
		panic(err)
	}
	etcdResolver, err := resolver.NewBuilder(etcdClient)

	//通过grpc与服务建立连接
	conn, err := grpc.Dial(fmt.Sprintf("etcd:///%s", serviceName), grpc.WithResolvers(etcdResolver), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"LoadBalancingPolicy": "%s"}`, roundrobin.Name)))

	if err != nil {
		fmt.Printf("err: %v", err)
		return
	}

	ServerClient := rpc2.NewServerClient(conn)

	for {
		helloRespone, err := ServerClient.Hello(context.Background(), &rpc2.Empty{})
		if err != nil {
			fmt.Printf("err: %v", err)
			return
		}

		log.Println(helloRespone, err)
		time.Sleep(500 * time.Millisecond)
	}

}

5)启动三个server,并向etcd注册服务

// 进入server/main.go所在目录
go run . --port 8081
go run . --port 8082
go run . --port 8083

6)启动一个client端,通过etcd拉取服务
go run .\main.go

观察三个server的打印,可以发现,client端的请求时负载均衡的,每个server都有可能被访问到

7)我们停止server3,发现client的请求被均衡的分发到server1、server2

8)进入docker部署的etcd内部,查询所有的key:发现只有8081和8082的
相关推荐
beifengtz7 小时前
推荐一款ETCD桌面客户端——Etcd Workbench
etcd·etcd客户端
ZHOU西口7 小时前
微服务实战系列之玩转Docker(十八)
分布式·docker·云原生·架构·数据安全·etcd·rbac
ZHOU西口5 天前
微服务实战系列之玩转Docker(十六)
分布式·docker·云原生·架构·etcd·配置中心
forestqq9 天前
构建后端为etcd的CoreDNS的容器集群(七)、编写适合阅读的域名管理脚本
运维·数据库·etcd
华东设计之美9 天前
etcd多实例配置
linux·服务器·etcd
24203011 天前
etcd之etcd分布式锁及事务(四)
golang·etcd
alden_ygq14 天前
docker 部署单节点的etcd以及 常用使用命令
docker·容器·etcd
forestqq14 天前
构建后端为etcd的CoreDNS的容器集群(六)、编写自动维护域名记录的代码脚本
数据库·etcd·coredns
观测云14 天前
Etcd 可观测最佳实践
etcd
suoyue_zhan14 天前
ETCD未授权访问风险基于角色认证和启用https的ca证书修复方案
数据库·postgresql·etcd·1024程序员节·patroni