zoukankan      html  css  js  c++  java
  • GO-GRPC实践(一) 完成第一个GRPC接口并使用etcd作为服务注册和发现

    https://me1onrind.github.io/2021/06/06/grpc_one/

    demo代码地址

    https://github.com/Me1onRind/go-demo

    环境搭建

    go

    go 1.13 以上

    需安装的二进制文件

    可执行文件名 安装方式 作用
    protoc https://github.com/protocolbuffers/protobuf/releases 下载安装 将.proto文件编译为具体编程语言的文件
    protoc-gen-go go get github.com/golang/protobuf/protoc-gen-go@v1.3.3 将.proto文件编译为go文件时需要的插件

    使用etcd作为服务注册中心

    https://github.com/etcd-io/etcd

    非生产环境在本地单机部署或者使用docker运行即可

    docker-compose.yml

    https://github.com/Me1onRind/my_docker/blob/master/etcd/docker-compose.yml

    编写go服务端代码

    项目目录

    ./
    ├── cmd
    │   └── grpc
    │       ├── client_test.go # 测试文件
    │       └── main.go # main文件
    ├── go.mod
    ├── go.sum
    ├── internal
    │   ├── controller # grpc接口实现
    │   │   └── foo_controller.go
    │   ├── core
    │   │   └── register # 服务注册实现
    │   │       └── etcd.go
    ├── protobuf # proto原型文件和编译后的文件
    │   ├── build.sh
    │   ├── foo.proto
    │   └── pb
    │       └── foo.pb.go
    └── README.md
    

    代码

    greet.proto

    syntax = "proto3";
    
    package pb;
    
    service Foo {  
        rpc Greet(GreetReq) returns (GreetResp);
    }
    
    message GreetReq {
        string my_name = 1;
        string msg = 2;
    }
    
    message GreetResp {
        string msg = 1;
    }
    
    编译proto文件生成go代码
    [root@debian go-demo]# protoc --proto_path=./:  --go_out=plugins=grpc:./pb/. ./*.proto
    

    为了避免每次都要输一串命令(还有其他用处), 将编译命令写在shell脚本里

    build.sh
    #!/bin/bash
    set -ue
    cd `dirname $0`
    protoc --proto_path=./:  --go_out=plugins=grpc:./pb/. ./*.proto
    

    之后更新proto文件后只需执行

    [root@debian go-demo]# sh protobuf/build.sh 
    

    foo_controller.go

    实现定义的Foo接口

    package controller
    
    import (
        "context"
        "fmt"
    
        "github.com/Me1onRind/go-demo/protobuf/pb"
    )
    
    type FooController struct {
    }
    
    func NewFooController() *FooController {
        f := &FooController{}
        return f
    }
    
    func (f *FooController) Greet(ctx context.Context, in *pb.GreetReq) (*pb.GreetResp, error) {
        reply := fmt.Sprintf("Hello %s, I got your msg:%s", in.GetMyName(), in.GetMsg())
        out := &pb.GreetResp{}
        out.Msg = reply
        return out, nil
    }
    

    etcd.go

    服务注册功能

    package register
    
    import (
        "context"
        "fmt"
        "log"
        "time"
    
        uuid "github.com/satori/go.uuid"
        clientv3 "go.etcd.io/etcd/client/v3"
        "go.etcd.io/etcd/client/v3/naming/endpoints"
    )
    
    var client *clientv3.Client
    
    const (
        prefix = "service"
    )
    
    func init() {
        var err error 
        client, err = clientv3.New(clientv3.Config{
            Endpoints:   []string{"localhost:2379"},
            DialTimeout: 5 * time.Second,
        }) 
        if err != nil {
            panic(err)
        }
    }
    
    func Register(ctx context.Context, serviceName, addr string) error {
        lease := clientv3.NewLease(client)
        cancelCtx, cancel := context.WithTimeout(ctx, time.Second*3	)
        defer cancel()
        leaseResp, err := lease.Grant(cancelCtx, 3)
        if err != nil {
            return err
        }   
    
    
        leaseChannel, err := lease.KeepAlive(context.Background(), leaseResp.ID) // 长链接, 不用设置超时时间
        if err != nil {
            return err 
        }   
    
        em, err := endpoints.NewManager(client, prefix)
        if err != nil {
            return err
        }
    
        cancelCtx, cancel = context.WithTimeout(ctx, time.Second*3)
        defer cancel()
        if err := em.AddEndpoint(cancelCtx, fmt.Sprintf("%s/%s/%s", prefix, serviceName, uuid.NewV4().String()), endpoints.Endpoint{
            Addr: addr,
        }, clientv3.WithLease(leaseResp.ID)); err != nil {
            return err
        }
    
        go func() {
            for {
                select {
                case resp := <-leaseChannel:
                    if resp != nil {
                        //log.Println("keep alive success.")
                    } else {
                        time.Sleep(time.Second)
                        log.Println("keep alive failed.")
                    }
                case <-ctx.Done():
                    log.Println("close service register")
    
                    cancelCtx, cancel = context.WithTimeout(ctx, time.Second*3)
                    defer cancel()
                    em.DeleteEndpoint(cancelCtx, serviceName)
    
                    lease.Close()
                    client.Close()
                    return
                }
            }
        }()
    
        return nil
    }
    

    main.go

    package main
        
    import (
        "context"
        "fmt"
        "log"
        "net"
    
        "github.com/Me1onRind/go-demo/internal/controller"
        "github.com/Me1onRind/go-demo/internal/core/register"
        "github.com/Me1onRind/go-demo/protobuf/pb"
    
        "google.golang.org/grpc"
    )
    
    func registerService(s *grpc.Server) {
        pb.RegisterFooServer(s, controller.NewFooController())
    }   
        
    func main() {
        addr := "127.0.0.1:8080"
        ctx := context.Background()
    
        lis, err := net.Listen("tcp", addr)
        if err != nil {
            log.Fatalf("failed to listen: %v", err)
        }
        s := grpc.NewServer()
        registerService(s)
    
        if err := register.Register(ctx, "go-demo", addr); err != nil { // 服务注册名: go-demo
            log.Fatalf("register %s failed:%v", "go-demo", err)
        }
    
        fmt.Printf("start grpc server:%s", addr)
        if err := s.Serve(lis); err != nil {
            log.Fatalf("failed to serve: %v", err)
        }
    }
    

    通过服务发现调用Foo.Greet方法

    client_test.go

    package main
                                   
    import (
        "context"
        "testing"                  
        "time"
        
        "github.com/Me1onRind/go-demo/protobuf/pb"
        "go.etcd.io/etcd/client/v3"
        "go.etcd.io/etcd/client/v3/naming/resolver"
        "google.golang.org/grpc"   
    ) 
    
        
    func Test_Greet(t *testing.T) {
      
        cli, err := clientv3.NewFromURL("http://localhost:2379")
        if err != nil {            
            t.Fatal(err)           
        }
        builder, err := resolver.NewBuilder(cli) 
        if err != nil {
            t.Fatal(err)
        }
        conn, err := grpc.Dial("etcd:///service/go-demo",
            grpc.WithResolvers(builder),    
            grpc.WithBalancerName("round_robin"),
            grpc.WithInsecure(), grpc.WithTimeout(time.Second))
        if err != nil {
            t.Fatal(err)
        }
    
        fooClient := pb.NewFooClient(conn)
        ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
        defer cancel()
        resp, err := fooClient.Greet(ctx, &pb.GreetReq{
            MyName: "Bar",
            Msg:    "Hello, World",
        })
        if err != nil {
            t.Fatal(err)
        }
    
        t.Log(resp.Msg)
    }
    

    验证

    启动server

    [root@debian go-demo]# go run cmd/grpc/main.go 
    start grpc server:127.0.0.1:8080	
    

    可以使用etcd命令行客户端/UI客户端看到, 服务已经注册上去

    客户端调用

    === RUN   Test_Greet
        client_test.go:43: Hello Bar, I got your msg:Hello, World
    -- PASS: Test_Greet (0.00s)
    PASS
    ok      github.com/Me1onRind/go-demo/cmd/grpc   0.010s
    
  • 相关阅读:
    C#开发BIMFACE系列44 服务端API之计算图纸对比差异项来源自哪个图框
    C#开发BIMFACE系列43 服务端API之图纸拆分
    C#开发BIMFACE系列42 服务端API之图纸对比
    利用 OpenVINO 进行推理加速(一)
    虚拟化技术概述(一)
    利用目标跟踪来提高实时人脸识别处理速度
    目标追踪(Object Tracking)概念的简要介绍
    Python3 使用IMAP接收邮件
    Git使用
    Git基础
  • 原文地址:https://www.cnblogs.com/Me1onRind/p/14878802.html
Copyright © 2011-2022 走看看