ETCD源分析之启动与GRPC

111 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

 分析源前必须要做的事

    1. 理论学习,阅读相关书藉和官方文档,了解ETCD的主要功能,架构和实现原理
    1. 学习源码使用的框架,ETCD是用的gRPC框架,基于GO语言。
    1. 了解设计模式,一般软件都会分模块设计并使用分层架构,MVC, 再结合设计模式。 理清楚层次。
    1. 带着问题阅读源码,分析源码,得到问题的答案。或者验证在理论学习中学到的知识点。

几个问题

  • ETCD初始化流程是什么?
  • ETCD核心数据结构是什么?
  • gRPC提供了哪些服务
  • RAFT如何工作?
  • 数据如何从master同步到follower,如何保证一致性?
  • KV数据结构如何保存到磁盘?即持久化方式?
  • 磁盘数据结构是什么样子?
  • 重新启动后如何加载?
  • 新结点加入集群如何同步数据?
  • master崩溃后如何选举出新master?

gRPC使用方式

核心思路:

1.定义一个proto文件,文件里有写request, response的结构定义,和一个rpc服务,函数的定义

2.调用protoc和对应语言(GO)插件,生成服务端和客户端代码。

3.服务端:自己定义一个type Implement Struct实现rpc服务需要的接口,并注册到rpc框架中

4.客户端:可以使用生成的代码直接调用服务端的函数

syntax = "proto3";
option go_package = "github.com/grpc/example/helloworld";
package helloworld;
//grpc 服务
service Greeter {
  rpc LotsOfReplies (HelloRequest) returns (stream HelloReply){}
}
//grpc请求
message HelloRequest {
  string name = 1;
}
// grpc响应
message HelloReply {
  string message = 1;
}

生成grpc服务器和客户端stub代码

$ protoc helloworld.proto --go_out=output
$ tree .
.
├── helloworld.proto
└── output
    └── github.com
        └── grpc
            └── example
                └── helloworld
                    └── helloworld.pb.go
5 directories, 2 files

简单服务器实现

package main
import (
    "context"
    "fmt"
    "log"
    "net"
    "google.golang.org/grpc"
    pb "./output/github.com/grpc/example/helloworld"
    "google.golang.org/grpc/reflection"
)
const (
    port = ":50051"
)
// server is used to implement helloworld.GreeterServer.
//这个结构实现proto里定义的接口
type server struct{}
func (s *server) LotsOfReplies(in *pb.HelloRequest, stream pb.Greeter_LotsOfRepliesServer) error {
    for idx := 0; idx < 10; idx ++ {
        stream.Send(&pb.HelloReply{Message: fmt.Sprintf("Hello %s %d", in.Name, idx)})
    }
    return nil
}
func main() {
    lis, err := net.Listen("tcp", port)
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    //这里注册实现的接口
    s := grpc.NewServer()
    pb.RegisterGreeterServer(s, &server{})
    // Register reflection service on gRPC server.
    reflection.Register(s)
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

简单客户端实现

package main
import (
    "context"
    "io"
    "log"
    "os"
    "time"
    "google.golang.org/grpc"
    pb "./output/github.com/grpc/example/helloworld"
)
const (
    address     = "localhost:50051"
    defaultName = "world"
)
func main() {
    // Set up a connection to the server.
    conn, err := grpc.Dial(address, grpc.WithInsecure())
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    c := pb.NewGreeterClient(conn)
    // Contact the server and print out its response.
    name := defaultName
    if len(os.Args) > 1 {
        name = os.Args[1]
    }
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    //调用服务端函数,发请求
    stream, err := c.LotsOfReplies(ctx, &pb.HelloRequest{Name: name})
    if err != nil {
        log.Fatalf("could not greet: %v", err)
    }
    //收响应
    for {
        reply, err := stream.Recv()
        if err == io.EOF {
            break
        }
        if err != nil {
            log.Fatalf("%v.LotsOfReplies() = _, %v", c, err)
        }
        log.Printf("Greeting: %s\n", reply.Message)
    }
}

ETCD也用grpc,也是这个套路。同时gRPC又能很容易转为HTTP. 虽然客户端是用http与服务端通信,但是我们看懂了RPC也就懂了http.