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:path
,unix://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: v1beta1
name: buf.build/sukai/grpc-example
build:
roots:
- api/grpc
deps:
- buf.build/beta/googleapis
- buf.build/grpc-ecosystem/grpc-gateway
buf.gen.yaml
version: v1beta1
plugins:
- 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/buf
buf beta mod update
buf 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 api
import (
"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 main
import (
"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$ ./daemon
INFO[0000] Loaded rules: bashreadline, password_files, setuid_binaries
sukai@SuKai:/mnt/e/projects/grpc-example$ ./client
NAME TRACER
bashreadline bashreadline
password_files open
setuid_binaries exec
sukai@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 Speed
100 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@latest
go: downloading github.com/grpc-ecosystem/grpc-gateway v1.16.0
go: downloading github.com/grpc-ecosystem/grpc-gateway/v2 v2.4.0
go: downloading google.golang.org/grpc v1.37.0
go: downloading github.com/golang/protobuf v1.5.2
go: downloading google.golang.org/genproto v0.0.0-20210426193834-eac7f76ac494
go: downloading github.com/ghodss/yaml v1.0.0
go: downloading google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.0
go 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.proto
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative grpc/api.proto
protoc -I . --grpc-gateway_out . --grpc-gateway_opt logtostderr=true --grpc-gateway_opt paths=source_relative --grpc-gateway_opt generate_unbound_methods=true grpc/api.proto
protoc -I . --openapiv2_out ./third_party/OpenAPI --openapiv2_opt logtostderr=true grpc/api.proto
sudo cp buf-Linux-x86_64 /usr/local/bin/buf
buf beta mod update
buf generate
以上是关于Go开发gRPC服务的主要内容,如果未能解决你的问题,请参考以下文章