Go开发gRPC服务

Posted DevOps苏楷

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go开发gRPC服务相关的知识,希望对你有一定的参考价值。

gRPC是一个由 google 推出的、高性能、开源、通用的 rpc 框架。它是基于 HTTP2 协议标准设计开发,默认采用 Protocol Buffers 数据序列化协议,支持多种开发语言。

基本概念

gRPC Name Resolution

gRPC使用DNS作为默认的命名系统。gRPC可以使用基于DNS域名的服务发现和负载均衡,也可以通过ETCD来做服务发现,获取服务终端节点,etcd官方提供了gRPC解析器作为一个命名系统的可选方案。

URI Schemes:

dns:[//authority/]host[:port] -- DNS (default)

unix:pathunix://absolute_path -- Unix domain sockets (Unix systems only)

unix-abstract:abstract_path -- Unix domain socket in abstract namespace (Unix systems only)

gRPC-Gateway,gRPC服务的反向代理,将RESTful HTTP API转换为gRPC格式。

OpenAPI

OpenAPI规范(OAS)为HTTP APIs定义了一个标准的、与编程语言无关的接口描述说明。

代码示例

下面一起来看开源项目bpfd和datadog-agent如何使用gRPC进行服务通信。

Proto

首先导入google api annotations proto和openapi proto,通过option定义http API信息,以及openapi 描述信息。

syntax = "proto3";option go_package = "./grpc";package grpc;import "google/api/annotations.proto";import "protoc-gen-openapiv2/options/annotations.proto";// These annotations are used when generating the OpenAPI file.option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { info: { version: "1.0"; }; external_docs: { url: "https://github.com/ycsk02/grpc-example"; description: "gRPC-gateway repository"; } schemes: HTTP;};service API { rpc ListRules(ListRulesRequest) returns (ListRulesResponse) { option (google.api.http) = { // Route to this method from GET requests to /api/v1/rules get: "/api/v1/rules" }; option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { summary: "List rules" description: "List all rules on the server." tags: "Rules" }; }}message ListRulesRequest {}message Rule { string name = 1; // Name of rule string tracer = 2; // Name of tracer}message ListRulesResponse { repeated Rule rules = 1; // List of rules}

定义buf构建工具配置

buf.yaml

version: v1beta1name: buf.build/sukai/grpc-examplebuild: roots: - api/grpcdeps: - buf.build/beta/googleapis - buf.build/grpc-ecosystem/grpc-gateway

buf.gen.yaml

version: v1beta1plugins: - name: go out: api/grpc opt: paths=source_relative - name: go-grpc out: api/grpc opt: paths=source_relative,require_unimplemented_servers=false - name: grpc-gateway out: api/grpc opt: paths=source_relative - name: openapiv2 out: third_party/OpenAPI

生成Protobuf APIs

sudo cp buf-Linux-x86_64 /usr/local/bin/bufbuf beta mod updatebuf generate

执行完成,在api/grpc目录下生成三个文件:api.pb.go, api_grpc.pb.go, api.pb.gw.go,在third_party/OpenAPI目录下生成api.swagger.json文件。

gRPC Server端

UnimplementedAPIServer嵌入在APIServer中,保持向前兼容。

package apiimport ( "context" "github.com/ycsk02/grpc-example/api/grpc")type apiServer struct { rules map[string]map[string]grpc.Rule grpc.UnimplementedAPIServer}type Opts struct { Rules map[string]map[string]grpc.Rule}func NewServer(ctx context.Context, opt Opts) (grpc.APIServer, error) { server := &apiServer{ rules: opt.Rules, } return server, nil}func (s *apiServer) ListRules(ctx context.Context, r *grpc.ListRulesRequest) (*grpc.ListRulesResponse, error) { var rs []*grpc.Rule for _, prs := range s.rules { for _, rule := range prs { rs = append(rs, &rule) } } return &grpc.ListRulesResponse{Rules: rs}, nil}

注释代码为之前使用unix socket的URI Scheme。RegisterAPIServer注册服务到gRPC Server。

 // grpcAddress := "/home/sukai/grpc-example/grpc.sock" grpcAddress := "127.0.0.1:5000" // Create the directory if it doesn't exist. // if err := os.MkdirAll(filepath.Dir(grpcAddress), 0755); err != nil { // fmt.Errorf("creating directory %s failed: %v", filepath.Dir(grpcAddress), err) // } // // // Remove the old socket. // if err := os.RemoveAll(grpcAddress); err != nil { // logrus.Warnf("attempt to remove old sock %s failed: %v", grpcAddress, err) // } // l, err := net.Listen("unix", grpcAddress) l, err := net.Listen("tcp", grpcAddress) if err != nil { fmt.Printf("starting listener at %s failed: %v \n", grpcAddress, err) } s := grpc.NewServer() opt := api.Opts{ Rules: rls, } srv, err := api.NewServer(context.TODO(), opt) if err != nil { fmt.Printf("creating new api server failed: %v \n", err) } types.RegisterAPIServer(s, srv) go func() { s.Serve(l) }()

gRPC-Gateway

grpc.DialContext 创建gRPC连接

runtime.NewServeMux 创建gRPC-Gateway路由转接器,它将根据http请求,调用对应匹配的Handler。

types.RegisterAPIHandler(context.Background(), gwmux, conn) 将Service API对应的http handlers注册到mux。

func gatewayRun(dialAddr string) error { conn, err := grpc.DialContext( context.Background(), dialAddr, grpc.WithInsecure(), grpc.WithBlock(), ) if err != nil { return fmt.Errorf("failed to dial server: %w", err) } gwmux := runtime.NewServeMux() err = types.RegisterAPIHandler(context.Background(), gwmux, conn) if err != nil { return fmt.Errorf("failed to register gateway: %w", err) } gatewayAddr := "0.0.0.0:11000" gwServer := &http.Server{ Addr: gatewayAddr, Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if strings.HasPrefix(r.URL.Path, "/") { gwmux.ServeHTTP(w, r) return } }), } return fmt.Errorf("serving gRPC-Gateway server: %w", gwServer.ListenAndServe())}

gRPC客户端

grpc.DialOption gRPC连接请求配置项

grpc.Dial 初始化连接

NewAPIClient 创建一个APIClient

package mainimport ( "context" "fmt" types "github.com/ycsk02/grpc-example/api/grpc" "google.golang.org/grpc" "net" "os" "text/tabwriter" "time")func getClient(ctx context.Context, address string) (types.APIClient, error) { // TODO: have more dial options dialOpts := []grpc.DialOption{grpc.WithInsecure()} dialOpts = append(dialOpts, grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) { timeout := 30 * time.Second // return net.DialTimeout("unix", addr, timeout) return net.DialTimeout("tcp", addr, timeout) }, )) conn, err := grpc.Dial(address, dialOpts...) if err != nil { return nil, fmt.Errorf("creating grpc connection to %s failed: %v", address, err) } return types.NewAPIClient(conn), nil}func main() { // grpcAddress := "/home/sukai/grpc-example/grpc.sock" grpcAddress := "127.0.0.1:5000" c, err := getClient(context.TODO(), grpcAddress) if err != nil { fmt.Printf("get client failed: %v \n", err) os.Exit(1) } // List the rules. resp, err := c.ListRules(context.Background(), &types.ListRulesRequest{}) if err != nil { fmt.Printf("sending ListRules request failed: %v \n", err) os.Exit(1) } w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0) fmt.Fprint(w, "NAME\tTRACER\n") for _, rule := range resp.Rules { fmt.Fprintf(w, "%s\t%s\n", rule.Name, rule.Tracer) } w.Flush()}

执行验证

sukai@SuKai:/mnt/e/projects/grpc-example$ ./daemonINFO[0000] Loaded rules: bashreadline, password_files, setuid_binariessukai@SuKai:/mnt/e/projects/grpc-example$ ./clientNAME TRACERbashreadline bashreadlinepassword_files opensetuid_binaries execsukai@SuKai:/mnt/e/projects/grpc-example$ curl http://127.0.0.1:11000/api/v1/rules{"rules":[{"name":"bashreadline","tracer":"bashreadline"},{"name":"password_files","tracer":"open"},{"name":"setuid_binaries","tracer":"exec"}]}sukai@SuKai:/mnt/e/projects/grpc-example$sukai@SuKai:/mnt/e/projects/grpc-example$ curl http://127.0.0.1:11000/api/v1/rules | jq . % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed100 144 100 144 0 0 72000 0 --:--:-- --:--:-- --:--:-- 72000{ "rules": [ { "name": "bashreadline", "tracer": "bashreadline" }, { "name": "password_files", "tracer": "open" }, { "name": "setuid_binaries", "tracer": "exec" } ]}sukai@SuKai:/mnt/e/projects/grpc-example$

安装代码生成器

sukai@sukai:~$ go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@latest google.golang.org/protobuf/cmd/protoc-gen-go@latest google.golang.org/grpc/cmd/protoc-gen-go-grpc@latestgo: downloading github.com/grpc-ecosystem/grpc-gateway v1.16.0go: downloading github.com/grpc-ecosystem/grpc-gateway/v2 v2.4.0go: downloading google.golang.org/grpc v1.37.0go: downloading github.com/golang/protobuf v1.5.2go: downloading google.golang.org/genproto v0.0.0-20210426193834-eac7f76ac494go: downloading github.com/ghodss/yaml v1.0.0go: downloading google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.0go install: package google.golang.org/protobuf/cmd/protoc-gen-go provided by module google.golang.org/protobuf@v1.26.0 All packages must be provided by the same module (github.com/grpc-ecosystem/grpc-gateway/v2@v2.4.0).go install: package google.golang.org/grpc/cmd/protoc-gen-go-grpc provided by module google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.0.0 All packages must be provided by the same module (github.com/grpc-ecosystem/grpc-gateway/v2@v2.4.0).

使用protoc编译,注意这里需要手工解决依赖关系,所以后来使用Buf来编译。

protoc -I . --openapiv2_out ./third_party/OpenAPI --openapiv2_opt logtostderr=true api/grpc/api.protoprotoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative grpc/api.protoprotoc -I . --grpc-gateway_out . --grpc-gateway_opt logtostderr=true --grpc-gateway_opt paths=source_relative --grpc-gateway_opt generate_unbound_methods=true grpc/api.protoprotoc -I . --openapiv2_out ./third_party/OpenAPI --openapiv2_opt logtostderr=true grpc/api.proto
sudo cp buf-Linux-x86_64 /usr/local/bin/bufbuf beta mod updatebuf generate


以上是关于Go开发gRPC服务的主要内容,如果未能解决你的问题,请参考以下文章

go微服务gRPC

go微服务gRPC

go微服务gRPC

[教程,Part 2]如何使用HTTP/REST端点,中间件,Kubernetes等开发Go gRPC微服务

gRPC服务开发和接口测试初探「Go」

Go使用grpc+http打造高性能微服务