一個基於 gin+ grpc + etcd 等框架開發的小栗子

2023-02-27 15:01:20

一、標準的專案結構

首先我們看一個標準的專案結構是什麼樣子的,github 上給出的一個範例:golang-standards/project-layout

 

 

二、服務註冊與發現流程

 三、範例程式碼

專案地址:gRPC-GoWeb

1、服務註冊

服務註冊和發現都可參考 etcd官網 的註冊和發現流程,以下是專案的幾個步驟的程式碼,詳細的可下載專案檢視。

 1、連線etcd註冊中心

func New(etcdAddr []string) (*EtcdRegister, error) {
    log.Printf("開始連線etcd註冊中心... \n")
    cli, err := clientv3.New(clientv3.Config{
        Endpoints:   etcdAddr,
        DialTimeout: 5 * time.Second,
    })
    if err != nil {
        log.Printf("連線etcd註冊中心失敗,失敗原因是: %v \n", err)
        return nil, err
    }
    log.Printf("連線etcd註冊中心成功! \n")
    return &EtcdRegister{
        cli: cli,
    }, nil
}

2、服務註冊

func (etcdRegister *EtcdRegister) Register(srvName string, srvAddr string, ttl int64) error {
    etcdRegister.srvName = srvName
    etcdRegister.srvAddr = srvAddr
    log.Printf("開始建立etcd端點管理器... \n")
    em, err := endpoints.NewManager(etcdRegister.cli, srvName)
    if err != nil {
        log.Printf("建立etcd端點管理器失敗,失敗原因是: %v \n", err)
        return err
    }
    etcdRegister.em = em
    log.Printf("建立etcd端點管理器成功! \n")
    log.Printf("開始建立服務租期... \n")
    lease, err := etcdRegister.cli.Grant(context.TODO(), ttl)
    if err != nil {
        log.Printf("建立服務租期失敗,失敗原因是: %v", err)
    }
    etcdRegister.ttl = ttl
    etcdRegister.leaseID = lease.ID
    log.Printf("建立服務租期成功! \n")
    log.Printf("開始註冊服務,服務名: %s,服務地址: %s \n", srvName, srvAddr)
    em.AddEndpoint(context.TODO(), fmt.Sprintf("%v/%v", srvName, srvAddr), endpoints.Endpoint{Addr: srvAddr}, clientv3.WithLease(lease.ID))
    if err != nil {
        log.Printf("註冊服務失敗! \n")
        return err
    }
    log.Printf("註冊服務成功! \n")
    log.Printf("開始服務定期續租! \n")
    leaseChan, err := etcdRegister.cli.KeepAlive(context.TODO(), etcdRegister.leaseID)
    if err != nil {
        log.Printf("服務定期續租失敗! \n")
        return err
    }
    etcdRegister.leaseChan = leaseChan
    log.Printf("服務定期續租成功! \n")
    return nil
}

3、提供服務(需要用到Protobuf)

func init() {
    pb.RegisterUserServiceServer(server.GrpcServer, &UserService{})
}

2、服務發現

1、服務發現

func (etcdDiscovery *etcdDiscovery) Discovery(srvName string) (*grpc.ClientConn, error) {
    target := fmt.Sprintf("etcd:///%s", srvName)
    conn, err := grpc.Dial(target, grpc.WithResolvers(etcdDiscovery.resolver), grpc.WithInsecure())
    if err != nil {
        log.Printf("%s服務發現失敗,原因是: %v \n", srvName, err)
        return nil, err
    }
    return conn, nil
}

2、呼叫服務方法

func UserRegister(ginCtx *gin.Context) {
    etcdDiscovery := serviceDiscovery.EtcdCenter
    //獲取使用者服務
    conn, _ := etcdDiscovery.Discovery("study-user-service")
    userService := pb.NewUserServiceClient(conn)
    data, err := userService.SayHello(ginCtx, &pb.Request{})
    if err != nil {
        log.Printf("呼叫使用者服務出錯了,原因是: %v \n", err)
    }
    ginCtx.JSON(200, gin.H{
        "message": data,
    })
}

備註:api-gateway 和 study-user-service 都使用了空匯入的方式,所以主要看空匯入的幾個 init 函數,main 方法都是空的。

如果對 Go 的程式碼執行順序不熟悉的,可以瞭解一下!

參考文獻:

https://etcd.io/docs/v3.5/dev-guide/grpc_naming/

https://gin-gonic.com/zh-cn/docs/quickstart/

https://grpc.io/docs/languages/go/basics/

https://doc.oschina.net/grpc?t=60133

https://devpress.csdn.net/cloud/62f627bac6770329307fc0c9.html