go实现gRPC服务

 来源:来源

本文介绍如何使用go语言搭建简单的gRPC服务,内容比较基础,记录自己的踩坑过程。

1. 背景知识

gRPC是一种高效的远程过程调用(RPC)框架,用于不同计算机之间的通信和远程服务调用。gRPC的目标是让客户端向调用本地方法一样调用其他机器上的服务端应用程序提供的方法,能够帮助开发者更方便高效的构建分布式应用程序和服务。

protocol buffers是实现gRPC服务的基础,gRPC使用protocol buffers提供了一种简洁而强大的接口定义语言(IDL),gRPC通过.proto文件定义服务和消息类型,由于protocol buffers是一种用于序列化结构化数据的与语言无关的格式,因此,使用.proto文件定义的服务可以使用任意语言编写业务逻辑,从而实现该服务。

2. gRPC服务实现过程

  1. 实现一个gRPC服务主要包括以下步骤:

  2. 定义服务接口:定义服务,服务的方法,接收和返回的消息类型

  3. 编译proto文件:使用protoc编译器编译生成服务相关的go文件

  4. 开发业务逻辑:实现proto中定义的方法

  5. 创建gRPC服务:创建gRPC服务器,并绑定业务逻辑

3. 基础环境配置

3.1 go get配置代理

golang默认的代理 https://proxy.golang.org 在国内下载速度很慢,使用以下命令设置golang代理为 https://goproxy.cn

go env -w GOPROXY=https://goproxy.cn,direct

参考:Go 国内加速:Go 国内加速镜像 | Go 技术论坛 (learnku.com)

3.2. protoc安装

protoc是一款命令行工具,用于自动根据proto后缀的protobuf文件生成相关的go文件,从以下github仓库中下载可执行文件

Releases · protocolbuffers/protobuf · GitHub

img

将下载完成的可执行文件放入%GOPATH%/bin目录下(也可以放入其他目录并添加环境变量)

img

在命令行中运行 protoc --version 成功显示版本号则说明安装成功

img

3.3 protocol compiler plugin安装

使用一下命令安装相关的插件,go install命令会下载依赖源码并进行编译,编译完成的可执行文件位于GOPATH下。

$ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

安装完成后,能够在%GOPATH%/bin目录下找到刚刚安装完成的插件可执行文件

img

后续的自动生成proto代码时,会调用到当前目录下的可执行文件。一般go安装时会自动将GOPATH添加到环境变量,如果没有,则需要手动添加。

img

参考:Quick start | Go | gRPC

2. 定义服务

2.1 编写proto代码

定义服务接口、接收和响应的消息类型均在.proto文件中定义,以下代码中,定义了一个服务Greeter,服务中定义了一个rpc方法SayHello,该方法接收一个HelloRequest类型的消息,并返回一个HelloReply类型的响应。

syntax = "proto3";    // 指定protoc版本

option go_package = "gRPC_demo/protos/helloworld";   // 指定自动生成go代码时的包名

package helloworld;   // 指定包名,防止命名冲突

// 定义Greeter服务
service Greeter {
  // 定义SayHello方法
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// 定义HelloRequest消息,消息中包括一个string类型的name字段
message HelloRequest {
  string name = 1;    // 1代表该字段的排序编号,如该消息还有第2个字段pass,则定义为 string pass = 2;
}

// 定义HelloReply消息,消息中包括一个string类型的message字段
message HelloReply {
  string message = 1;
}
}

2.2. 编译.proto文件

protocol buffers是一种与语言无关的接口定义语言,在使用不同的语言实现时,都可以通过对应的插件将.proto定义代码编译成对应语言的代码。为了适应开发过程中.proto文件的频繁变更,可以将编译命令写入gen.bat文件,方便后续发生变更时快速重新生成代码,注意将.proto文件和.bat文件放在同一层目录,并cd到bat文件所在目录执行bat文件,防止代码生成到其他目录造成目录结构混乱

// gen.bat
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go grpc_opt=paths=source_relative .helloworld.proto

执行bat文件后,将自动生成了两个go文件,**.grpc.pb.go**.pb.go,它们包含的内容如下:

  1. **.grpc.pb.go:该文件包含gRPC服务端和客户端的代码框架,后续将基于该框架编写服务的业务逻辑,实现.proto文件中定义的服务。

  2. **.pb.go: 该文件保险消息类型的定义和相关的序列化/反序列化方法,通信过程中,将调用这些方法对消息进行处理。

img

2.3 同步依赖

生成的代码由于依赖未同步,可能存在大范围飘红,在命令行执行go mod tidy自动同步依赖即可

img

3. 实现业务逻辑

打开helloworld_grpc.pb.go,可以看到SayHello方法定义如下,会返回一个SayHello方法没有实现的错误,我们可以在SayHello中编写业务逻辑,但是由于helloworld_grpc.pb.go是自动生成的代码,在后续发生变更时,重新编译会覆盖我们编写的业务逻辑代码,因此强烈不建议在helloworld_grpc.pb.go中直接编写业务逻辑代码。

func (UnimplementedGreeterServer) SayHello(context.Context, *HelloRequest) (*HelloReply, error) {
	return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented")
} nil
}

推荐的方法是在项目根目录下创建一个services/helloworld目录,在目录中创建一个helloworld.go,新建一个结构体Server,继承UnimplementedGreeterServer结构体中的所有方法,并重写UnimplementedGreeterServer.SayHello方法,实现业务逻辑。

package helloworld

import (
    "context"
    pb "gRPC_demo/protos/helloworld"
    "log"
)

// 创建Server结构体,将SayHello方法注册为它的成员函数
type Server struct {
    pb.UnimplementedGreeterServer    // Server结构体继承了pb.UnimplementedGreeterServer结构体的所有方法
}

// 重写pb.GreeterServer.SayHello方法,实现业务逻辑
func (s *Server) SayHello(ctx context.Context, hellorequest *pb.HelloRequest) (*pb.HelloReply, error) {
    log.Printf("Received message from %v", hellorequest.GetName())
    return &pb.HelloReply{Message: "Hello " + hellorequest.GetName()}, nil
}

项目目录结构如下:

img

4. 编写main函数

在项目根目录编写以下main函数,新建一个gRPC服务,将gRPC服务和我们实现的Server结构体中的业务逻辑绑定到Greeter服务上,并开启监听。

package main

import (
    "fmt"
    pb "gRPC_demo/protos/helloworld"
    "gRPC_demo/services/helloworld"
    "log"
    "net"
    "google.golang.org/grpc"
)

func main() {
    // 绑定地址和端口
    grpcAddress := "0.0.0.0"
    grpcPort := 8090
    lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", grpcAddress, grpcPort))
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    // 初始化gRPC服务
    s := grpc.NewServer()
    // 将gRPC服务和自定义的业务逻辑注册到Greeter服务中
    pb.RegisterGreeterServer(s, &helloworld.Server{})
    log.Printf("serving gRPC on %v", lis.Addr())
    // 将gRPC服务绑定在上面创建的tcp端口上,并开启监听
    err = s.Serve(lis)
    if err != nil {
        log.Fatalf("启动gRPC服务失败(%v)", err)
    }
}

5. 运行gRPC服务

使用一下命令启动gRPC服务

go run main.go

img

7. 调用测试

将proto文件导入apifox后调用SayHello方法

img

服务端也打印了调用的日志信息

img

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值