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的
相关推荐
masx2008 天前
在 Docker 中部署 etcd 并解决权限问题实战指南,成功解决permission denied问题!
docker·etcd
有梦想的攻城狮8 天前
etcd详解
数据库·k8s·etcd
藥瓿亭9 天前
2024 CKA模拟系统制作 | Step-By-Step | 18、题目搭建-备份还原Etcd
linux·运维·服务器·ubuntu·kubernetes·etcd·cka
莱茵不哈哈12 天前
etcd:高可用,分布式的key-value存储系统
数据库·分布式·go·etcd·kv
24203012 天前
etcd之etcd curl命令(七)
golang·etcd
ErizJ12 天前
Golang|etcd服务注册与发现 & 策略模式
golang·策略模式·etcd
富士康质检员张全蛋15 天前
Kubernetes etcd 故障恢复(1)
容器·kubernetes·etcd
Spring_java_gg15 天前
Kubernetes 运维操作手册:从 etcd 快照进行精确恢复
运维·云原生·容器·kubernetes·etcd
y_f_617 天前
etcd基础
数据库·etcd
富士康质检员张全蛋23 天前
云原生|kubernetes|kubernetes的etcd集群备份策略
云原生·kubernetes·etcd