使用consul和Fabio做RPC的服务发现

前言

Consul 是一个服务网格解决方案,提供具有服务发现、配置和分段功能的全功能控制平面。这些功能中的每一个都可以根据需要单独使用,也可以一起使用来构建完整的service mesh

安转Consul和Fabio

Install Consul

Fabio Quickstart

我这里采用本地运行的方式

  • 运行consul

    1
    
    ./consul.exe agent -dev
    

    web页面地址localhost:8500

  • 运行Fabio

    注意此处的端口号及协议类型

    1
    
    ./fabio-1.6.0-windows_amd64.exe -proxy.addr ':10000;proto=grpc'
    

    web 页面地址http://localhost:9998/

gRPC服务端

  1. golang代码如下,其中要注意的点:1、 registrationInfo.ID 不能重复;2、registrationInfo.Tags 需要有前缀才能被Fabio代理,以及协议要与前面启动Fabio处要对应;3、registrationInfo.Check 需要添加,因为Fabio只对健康检测通过的服务进行监控

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    
    package main
    
    import (
        "context"
        "encoding/json"
        "flag"
        "fmt"
        "log"
        "net"
    
        pb "google.golang.org/grpc/examples/helloworld/helloworld"
    
        "github.com/hashicorp/consul/api"
    
        "google.golang.org/grpc"
    )
    
    var (
        port     *int
        client   *api.Client
        serverId string
    )
    
    // server is used to implement helloworld.GreeterServer.
    type server struct {
        pb.UnimplementedGreeterServer
        port int
    }
    
    type Resp struct {
        Port int    `json:"port"`
        Name string `json:"name"`
    }
    
    // SayHello implements helloworld.GreeterServer
    func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
        resp, _ := json.Marshal(
            Resp{
                Port: s.port,
                Name: in.GetName(),
            })
        return &pb.HelloReply{Message: string(resp)}, nil
    }
    func main() {
        defer client.Agent().ServiceDeregister(serverId)
        lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
        if err != nil {
            log.Fatalf("failed to listen: %v", err)
        }
        s := grpc.NewServer()
        pb.RegisterGreeterServer(s, &server{port: *port})
        log.Printf("server listening at %v", lis.Addr())
        if err := s.Serve(lis); err != nil {
            log.Fatalf("failed to serve: %v", err)
        }
    }
    
    func init() {
        port = flag.Int("port", 50051, "The server port")
        flag.Parse()
        serverId = fmt.Sprintf("gRPC_id_%d", *port)
        Registration(*port)
    }
    
    func Registration(port int) {
        var err error
        client, err = api.NewClient(api.DefaultConfig())
        if err != nil {
            panic(err)
        }
        //注册信息
        registrationInfo := api.AgentServiceRegistration{
            ID:      serverId,
            Name:    "gRPCServer",
            Port:    port,
            Tags:    []string{"urlprefix-/", "proto=grpc"},
            Address: "192.168.1.103",
            Check: &api.AgentServiceCheck{
                CheckID:  fmt.Sprintf("gRPCServerCheck:%d", port),
                TCP:      fmt.Sprintf("192.168.1.103:%d", port),
                Interval: "2s",
                Timeout:  "5s",
            },
        }
        if err = client.Agent().ServiceRegister(&registrationInfo); err != nil {
            log.Fatalf("ServiceRegister error:%s", err.Error())
        }
    
    }
    
  2. 在三个终端运行服务

    1
    2
    3
    
    go run main.go -port=50001
    go run main.go -port=50002
    go run main.go -port=50003
    
  3. 运行效果 在consul中,可以看到有一个名为gRPCServer的服务,该服务下有三个示例 consul servers 在Fabio中的Routing Table如图: Routing Table

gRPC客户端

  1. golang代码如下,请求的端口为启动Fabio中处的端口,(即10000)
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    
     package main
    
     import (
         "context"
         "encoding/json"
         "fmt"
         "log"
    
         "google.golang.org/grpc"
         "google.golang.org/grpc/credentials/insecure"
         pb "google.golang.org/grpc/examples/helloworld/helloworld"
     )
    
     func main() {
         numMap := map[int]int{
             50001: 0,
             50002: 0,
             50003: 0,
         }
         for i := 0; i < 1000; i++ {
             respPort := Request(10000)
             numMap[respPort]++
         }
         log.Println("numMap: ", numMap)
     }
    
     func Request(port int) int {
         addr := fmt.Sprintf("192.168.1.103:%d", port)
         conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
         if err != nil {
             log.Fatalf("did not connect: %v", err)
         }
    
         //resolver.SetDefaultScheme()
         defer conn.Close()
         c := pb.NewGreeterClient(conn)
         r, err := c.SayHello(context.Background(), &pb.HelloRequest{Name: "myName"})
         if err != nil {
             log.Fatalf("could not greet: %s", err.Error())
         }
         //log.Printf("resp: %s", r.GetMessage())
         resp := new(Resp)
         _ = json.Unmarshal([]byte(r.GetMessage()), resp)
         return resp.Port
    
     }
    
     type Resp struct {
         Port int    `json:"port"`
         Name string `json:"name"`
     }
    
  2. 运行及结果
    1
    2
    
     go run main.go 
     2022/06/30 01:38:56 numMap:  map[50001:339 50002:326 50003:335]
    
    这里统计了各示例被请求的次数,结果与Fabio中各实例的权重相呼应

个人总结

consul相对于之前使用过的zookeeper来说,在服务发现这部分的配置会相对简单,且包含了健康检查。对于客户端来说,不需要拉取服务端列表到本地并进行故障处理和负载均衡,请求的是Fabio代理