博客参考: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的