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的
相关推荐
欧尼焦4 天前
[k8s生产系列]:k8s集群故障恢复,etcd数据不一致,kubernetes集群异常
容器·kubernetes·etcd
尾巴尖上的阳光4 天前
ETCD概述--使用/特性/架构/原理
数据库·架构·etcd
hy1997079 天前
【云原生】Kubernetes----ETCD数据的备份与恢复
云原生·kubernetes·etcd
开心星人14 天前
【etcd】etcd单机安装及简单操作
数据库·etcd
画个一样的我17 天前
Python连接Etcd集群基础教程
python·etcd
peihexian22 天前
k8s及etcd的每日自动备份及故障时的还原脚本
容器·kubernetes·etcd
daemon36523 天前
boltdb 原理
linux·数据库·kubernetes·etcd
山河已无恙24 天前
K8s 集群高可用master节点ETCD全部挂掉如何恢复?
容器·kubernetes·etcd
大叶子不小1 个月前
Etcd Raft架构设计和源码剖析2:数据流
数据库·etcd
大叶子不小1 个月前
Etcd Raft架构设计和源码剖析1:宏观架构
数据库·架构·etcd