grpc 入门


title: grpc 入门
date: 2020-07-21 19:26:19


0. 前言

实习期间,公司项目使用 gRPC 框架。因此,简单入门一下 grpc。

1. 微服务概述

在提起 GRPC 之前,不得不提起微服务。

long long ago,互联网还未繁荣起来,当时的网站应用功能较为简单,用户数量较少,应用代码只需部署在一台服务器上,便足矣。这种把应用整体打包部署的方式,也被称为单体架构

但好景不长,随着互联网的迅猛发展,网站人数暴增,应用也开始变得复杂。这时,便出现了许多问题,比如:一台服务器容易出现性能瓶颈、应用代码稍微修改后便要重新部署、一个小服务的错误便会导致整个应用崩溃等等。

于是,应用开始分为各种小应用,分布式进行部署,同时采用负载均衡分摊各自的压力。

随着这种方式的不断发展,微服务架构(Microservices Architecture) 出现了。微服务架构的核心思想是:一个应用可分割为多个小的、相互独立的微服务,这些服务可以运行在各自的服务器、各自的进程中,开发和发布都没有依赖。不同服务通过一些轻量级交互机制来通信,服务可独立扩展伸缩,每个服务定义了明确的边界,不同的服务甚至可以采用不同的编程语言来实现,由独立的团队来维护。

微服务架构的优点已经不言而喻了:高耦合低内聚,甚至连开发语言都可以不同。

当然,微服务架构也存在其缺点:运维开销及成本增加、测试难度增大、提高了系统复杂性等等。

参考博客:

2. RPC 概述

有了微服务架构,那么我们就要考虑各微服务之间如何进行通信了。HTTP 肯定不失为一种有效的方法,但此处要提起的却是另一种方法:RPC

RPC 的英文全称是 Remote Procedure Call,即远程过程调用,简单点说,就是服务器A可以像函数调用一样调用服务器B上的方法。

其原理大致如下:

3.jpeg

其组成结构大致如下:

4.png

可以看出,RPC 底层依然使用 TCP 等运输层协议进行网络通信,它跟 HTTP 较为相似,但比 HTTP 更便捷的是,它还封装了序列化与反序列化的功能。

或许,说到此处,还会有人疑惑:其实 HTTP 外加一层序列化与反序列化的封装后,也能实现 RPC 的功能,为何还需要使用 RPC 呢?

其实,HTTPRPC 各有千秋。甚至在 gRPC 中,其底层就是 HTTP2 协议。所以,并没有孰优孰劣,只是可能 RPC 更加适用于服务与服务之间的通信,不需要 HTTP1 协议中多余的头部,序列化与反序列化的功能也更加完善,而且封装程度高、即拿即用,毕竟谁不喜欢方便呢?

注意:另外,换一种说法,RPCHTTP 并不在同一层面上,并不能直接比较。毕竟 HTTP 是一个应用层协议,而 RPC 更像一种通信机制、一种框架,它内部可能使用自己编写的应用层协议,也可能就是 HTTP 协议。文中的 HTTPRPC 比较,可以看成是 Restful APIRPC 的比较。

参考博客:

3. gRPC 概述

gRPC 是谷歌开发的一个开源、高性能 RPC 框架,它底层使用 HTTP2 协议,并支持多种语言。

gRPC is a modern open source high performance RPC framework that can run in any environment.

以下是 gRPC 的官方概述

In gRPC, a client application can directly call a method on a server application on a different machine as if it were a local object, making it easier for you to create distributed applications and services. As in many RPC systems, gRPC is based around the idea of defining a service, specifying the methods that can be called remotely with their parameters and return types. On the server side, the server implements this interface and runs a gRPC server to handle client calls. On the client side, the client has a stub (referred to as just a client in some languages) that provides the same methods as the server.

1.png

同时,gRPC 使用 Protocol Buffers(一个谷歌出品的成熟的序列化框架)定义接口。

那么,gRPC 有何优点呢?如下所示:

  1. 性能。gRPC具有较好的性能,其原因包含如下几点:

    • 应用层通信使用了 HTTP/2 协议。HTTP/2 协议具有:头部采用二进制编码,并进行压缩;服务器推送;多路复用等优势,极大地提高了传输性能。
    • 数据结构序列化使用了谷歌的 Protocol Buffers 协议。其最后生成的数据体积、序列化性能都优于 json、xml 等。
  2. 。可以流式地发送和返回数据,比如:客户端想周期性地从服务端获取数据,以 HTTP/1.1 的方式则是轮询,而在 gRPC 中只需定义流式地返回数据即可。这点得益于 HTTP/2

  3. 清晰的API规范。相比于 Swagger API 的接口定义规范,.proto 文件所的定义更加清晰。

参考博客:

4. gRPC-go 尝试

首先,需要安装 Protocol buffer 编译器 protoc

然后,安装用于生成 go 代码的 protoc-gen-go 插件:

go get github.com/golang/protobuf/protoc-gen-go

之后,可参考官方示例,运行 grpc-helloworld 程序:go quickstart

5. gRPC-Gateway 概述

有了 gRPC 框架之后,服务之间的通信不成问题了。但是,人们又想,能不能通过 protocol 接口文件直接生成 RESTful HTTP API 呢?这样,微服务的接口不仅能通过 gRPC 被其它服务所调用,也能通过 HTTP 被浏览器端用户调用了。

基于这样的愿景,gRPC-Gateway 出道了。

官方文档如是说道:

The grpc-gateway is a plugin of the Google protocol buffers compiler protoc. It reads protobuf service definitions and generates a reverse-proxy server which translates a RESTful HTTP API into gRPC.

This helps you provide your APIs in both gRPC and RESTful style at the same time.

5.png

可以看出,grpc-gateway 能够根据 .proto 文件生成 HTTP 服务器代码 Reverse Proxy,并将 gRPC 接口转换成对应的 RESTful API。当 HTTP 客户端访问 API 时,Reverse Proxy 会将 HTTP 请求转换成对应的 gRPC 请求,并交给 gRPC service

6. gRPC-Gateway 尝试

OS:Windows10 pro

首先,需要安装 grpc-gateway 对应的 protoc 插件:

go get github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
go get github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger

然后,在 grpc-helloworld 程序的基础上,我们稍加改动。

项目目录如下:

6.png

修改 helloworld.proto 接口定义文件如下(google/api/annotations.protogrpc-gateway/third_party/googleapis/):

syntax = "proto3";

package api;

option go_package = "grpcDemo/api";

// grpc-gateway api
import "google/api/annotations.proto";

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

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

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

生成 grpc 接口代码文件:

protoc -I%PROTOC_INCLUDE% -I. -I%GOPATH%\src -Ithird_party\googleapis --go_out=plugins=grpc:. api\helloworld.proto

生成 gateway 代码文件:

protoc -I%PROTOC_INCLUDE% -I. -I%GOPATH%\src -Ithird_party\googleapis --grpc-gateway_out=logtostderr=true:. api\helloworld.proto

生成 swagger 文档:

protoc -I%PROTOC_INCLUDE% -I. -I%GOPATH%\src -Ithird_party\googleapis --swagger_out=logtostderr=true:. api\helloworld.proto

定义 gRPC server 代码如下:

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

import (
    "context"
    "log"
    "net"
    "os"

    "grpcDemo/api"

    "google.golang.org/grpc"
)

const (
    port = ":8081"
)

var (
    logger = log.New(os.Stdout, "[server] ", log.Lshortfile|log.Ldate|log.Ltime)
)

// server is used to implement api.HelloServiceServer.
type server struct {
    api.UnimplementedHelloServiceServer
}

// SayHello implements api.SayHello
func (s *server) SayHello(ctx context.Context, req *api.HelloRequest) (*api.HelloReply, error) {
    logger.Print("Received: ", req.String())
    return &api.HelloReply{Message: "Hello " + req.GetName() + ", your age is " + req.GetAge()}, nil
}

func main() {
    // listening the tcp port
    listen, err := net.Listen("tcp", port)
    if err != nil {
        logger.Fatalf("failed to listen: %v", err)
    }

    // register the server
    s := grpc.NewServer()
    api.RegisterHelloServiceServer(s, &server{})

    // open the serve
    logger.Print("server starting in ", port)
    err = s.Serve(listen)
    if err != nil {
        logger.Fatalf("failed to serve: %v", err)
    }
}

定义 grpc gateway 代码如下:

// package main provide http gateway according to rpc-server
package main

import (
    "flag"
    "log"
    "net/http"
    "os"

    "github.com/grpc-ecosystem/grpc-gateway/runtime"
    "golang.org/x/net/context"
    "google.golang.org/grpc"

    gw "grpcDemo/api"
)

const (
    port = ":8080"
)

var (
    // serverAddr gRPC server addr
    serverAddr = flag.String("server_addr", "localhost:8081", "address of YourServer")
    logger     = log.New(os.Stdout, "[gateway] ", log.Lshortfile|log.Ldate|log.Ltime)
)

func run() error {
    ctx := context.Background()
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()

    // Register HelloService handler.
    mux := runtime.NewServeMux()
    opts := []grpc.DialOption{grpc.WithInsecure()}
    err := gw.RegisterHelloServiceHandlerFromEndpoint(ctx, mux, *serverAddr, opts)
    if err != nil {
        return err
    }

    // Listening the port and open the serve.
    logger.Print("gateway is running in ", port)
    return http.ListenAndServe(port, mux)
}

func main() {
    flag.Parse()

    err := run()
    if err != nil {
        logger.Fatal(err)
    }
}

运行 grpc 服务端:

go run server\server.go

打开另一个命令行,运行 HTTP 网关:

go run gateway\gateway.go

最后,使用 postman 进行 HTTP API 接口测试:

2.png

项目代码地址:gitee

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343