GRPC一点通

Posted Yison小站

tags:

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

gRPC是一个高性能、开源和通用的 RPC 框架,面向移动和 HTTP/2 设计,带来诸如双向流、流控、头部压缩、单 TCP 连接上的多复用请求等特性,更省电和节省空间占用。
本文主要讲述如何快速搭建一个grpc服务,附带支持http接口以及swagger文档。

定义服务

示例使用项目名为grpc-demo,在 helloworld/simple.proto 文件内定义服务。

syntax = "proto3";

import "google/api/annotations.proto";

option go_package = "grpc-demo/helloworld";

package helloworld;

// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {
option (google.api.http) ={
post:"/v1/helloworld/sayHello"
body:"*"
};
}
}

// The request message containing the user's name.
message HelloRequest {
string name = 1;
}

// The response message containing the greetings
message HelloReply {
string message = 1;
}

环境准备

没安装protobuf的需自行安装,mac下安装比较方便:brew install protobuf,也可下载源码后编译安装。

准备grpc工具:

go get -u google.golang.org/grpc
go get -u google.golang.org/protobuf/cmd/protoc-gen-go
go get -u google.golang.org/grpc/cmd/protoc-gen-go-grpc
go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger

go install google.golang.org/protobuf/cmd/protoc-gen-go
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc
go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger

转换HTTP接口需要依赖 googleapis 库 ,先把库准备好:
go get github.com/googleapis/googleapis
或者
git clone https://github.com/googleapis/googleapis.git $GOPATH/src/github.com/googleapis

然后找到googleapis库在本地存放的位置,可能是$GOPATH/src/github.com/googleapis/googleapis

google.golang.org 若无法下载的解决方法:

// 非go mod模式:
git clone https://github.com/grpc/grpc-go.git $GOPATH/src/google.golang.org/grpc

// go mod模式:
go mod edit -replace=google.golang.org/grpc=github.com/grpc/grpc-go@latest

自动生成代码

用 protobuf 编译器生成服务器和客户端代码:

protoc -I . \
-I $GOPATH/src/github.com/googleapis/googleapis \
--go_out . --go_opt paths=source_relative \
--go-grpc_out . --go-grpc_opt paths=source_relative \
--grpc-gateway_out . --grpc-gateway_opt logtostderr=true --grpc-gateway_opt paths=source_relative \
--swagger_out . --swagger_opt logtostderr=true \
simple.proto

注意 $GOPATH/bin 要提前加到PATH环境变量中去,否则工具会找不到。
这里已经一次性把GRPC结构代码、HTTP的Restful接口、Swagger格式文件都生成好了,后面会补充使用说明。

服务实现

使用 gRPC 的 Go API 为你的服务实现一个简单的客户端和服务器。

client.go

// Package main implements a client for Greeter service.
package main

import (
"context"
pb "grpc-demo/helloworld"
"log"
"os"
"time"

"google.golang.org/grpc"
)

const (
address = "localhost:50051"
defaultName = "world"
)

func main() {
// Set up a connection to the server.
conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)

// Contact the server and print out its response.
name := defaultName
if len(os.Args) > 1 {
name = os.Args[1]
}
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
for i := 0; i < 10; i++ {
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.GetMessage())
time.Sleep(1 * time.Second)
}
}

server.go

// Package main implements a server for Greeter service.
package main

import (
"context"
"grpc-demo/gateway"
pb "grpc-demo/helloworld"
"log"
"net"

"google.golang.org/grpc"
)

const (
port = ":50051"
)

// server is used to implement helloworld.GreeterServer.
type server struct {
pb.UnimplementedGreeterServer
}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Received: %v", in.GetName())
return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}

func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
log.Printf("GRPC server listening at %v", lis.Addr())
go func() {
listener, err := net.Listen("tcp", "127.0.0.1:8000")
if err != nil {
log.Fatalf("net.Listen err: %v", err)
}
//使用gateway把grpcServer转成httpServer
httpServer := gateway.ProvideHTTP("127.0.0.1:8000", s)
if err = httpServer.Serve(listener); err != nil {
log.Fatal("HTTP ListenAndServe: ", err)
}
}()
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}

gateway/gateway.go

package gateway

import (
"context"
"log"
"net/http"
"strings"

pb "grpc-demo/helloworld"

"github.com/grpc-ecosystem/grpc-gateway/runtime"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
"google.golang.org/grpc"
)

// ProvideHTTP 把gRPC服务转成HTTP服务,让gRPC同时支持HTTP
func ProvideHTTP(endpoint string, grpcServer *grpc.Server) *http.Server {
ctx := context.Background()
dopts := []grpc.DialOption{grpc.WithInsecure()}
//新建gwmux,它是grpc-gateway的请求复用器。它将http请求与模式匹配,并调用相应的处理程序。
gwmux := runtime.NewServeMux()
//将服务的http处理程序注册到gwmux。处理程序通过endpoint转发请求到grpc端点
err := pb.RegisterGreeterHandlerFromEndpoint(ctx, gwmux, endpoint, dopts)
if err != nil {
log.Fatalf("Register Endpoint err: %v", err)
}
//新建mux,它是http的请求复用器
mux := http.NewServeMux()
//注册gwmux
mux.Handle("/", gwmux)
// 设置静态目录
fsh := http.FileServer(http.Dir("swagger"))
mux.Handle("/swagger/", http.StripPrefix("/swagger/", fsh))
log.Println(endpoint + " HTTP.Listing ...")
return &http.Server{
Addr: endpoint,
Handler: grpcHandlerFunc(grpcServer, mux),
}
}

// grpcHandlerFunc 根据不同的请求重定向到指定的Handler处理
func grpcHandlerFunc(grpcServer *grpc.Server, otherHandler http.Handler) http.Handler {
return h2c.NewHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.ProtoMajor == 2 && strings.Contains(r.Header.Get("Content-Type"), "application/grpc") {
grpcServer.ServeHTTP(w, r)
} else {
otherHandler.ServeHTTP(w, r)
}
}), &http2.Server{})
}

扩展HTTP接口

上面示例中已包含此功能,注意查看server.go的如下代码段,增加了8000作为http的服务端口

    go func() {
listener, err := net.Listen("tcp", "127.0.0.1:8000")
if err != nil {
log.Fatalf("net.Listen err: %v", err)
}
//使用gateway把grpcServer转成httpServer
httpServer := gateway.ProvideHTTP("127.0.0.1:8000", s)
if err = httpServer.Serve(listener); err != nil {
log.Fatal("HTTP ListenAndServe: ", err)
}
}()

扩展swagger文档

1.

下载swagger-ui
git clone https://github.com/swagger-api/swagger-ui,把dist目录下的所有文件拷贝我们项目的swagger/目录下。

2.

http设置静态目录
注意gateway/gateway.go如下代码段



    fsh := http.FileServer(http.Dir("swagger"))
mux.Handle("/swagger/", http.StripPrefix("/swagger/", fsh))

1.将自动生成的simple.swagger.json覆盖到swagger/swagger.json文件2.修改swagger/index.html文件,url换成 url: "swagger.json"

编译运行

编译客户端: go build -o c client.go
编译服务端: go build -o s server.go

启动服务端: ./s
启动客户端: ./c

运行效果

# ./s
2021/07/17 13:27:38 GRPC server listening at [::]:50051
2021/07/17 13:27:38 127.0.0.1:8000 HTTP.Listing ...
2021/07/17 13:30:58 Received: world
2021/07/17 13:30:59 Received: world
2021/07/17 13:31:00 Received: world
2021/07/17 13:31:01 Received: world
2021/07/17 13:31:02 Received: world
2021/07/17 13:31:03 Received: world
2021/07/17 13:31:04 Received: world
2021/07/17 13:31:05 Received: world
2021/07/17 13:31:06 Received: world
2021/07/17 13:31:07 Received: world
# ./c
2021/07/17 13:30:58 Greeting: Hello world
2021/07/17 13:30:59 Greeting: Hello world
2021/07/17 13:31:00 Greeting: Hello world
2021/07/17 13:31:01 Greeting: Hello world
2021/07/17 13:31:02 Greeting: Hello world
2021/07/17 13:31:03 Greeting: Hello world
2021/07/17 13:31:04 Greeting: Hello world
2021/07/17 13:31:05 Greeting: Hello world
2021/07/17 13:31:06 Greeting: Hello world
2021/07/17 13:31:07 Greeting: Hello world

浏览器访问: http://127.0.0.1:8000/swagger/


至此,你已经学会如何快速搭建一个grpc服务了,赶快动手试试吧。
还有,别忘了补习下 proto3 的配置语法喔。

关于我

name: yison.li
blog: http://yyeer.com
github: https://github.com/yisonli


以上是关于GRPC一点通的主要内容,如果未能解决你的问题,请参考以下文章

每天进步一点点:go基于grpc构建微服务框架-服务注册与发现

每天进步一点点:使用Go gRPC 和k8s开始你的微服务之旅

Dapr集成之GRPC 接口

TiDB与gRPC的那点事

源码解析Grpc拦截器(C#版本)

gRPC-web现状及测试