Golang gRPC笔记01 gRPC简介与基本使用

一、 关于RPC

  1. 为什么需要 RPC

    使用RPC,目的主要是 像调用本地服务一样远程调用另一台服务器上的服务 来完成需求;使用http的接口也能完成这种需求,但是rpc有这些优势:简单、通用、安全、高效

  2. RPC简介

    • rpc只是一种概念,一种设计,就是为了解决不同服务之间的调用问题,它一般会包含 传输协议序列化协议 这两个协议。
    • rpc与http:RPC是一种思想,Http是一种协议,这是本质区别
    • rpc的传输协议和序列化协议:
      1. 传输协议: 著名的 grpc 使用的 http2 协议,也有如dubbo一类的自定义报文的tcp协议
      2. 序列化协议: 如基于文本编码的 xml json,也有二进制编码的 protobuf hessian等
      • web services: 传输协议=soap 序列化协议=xml
      • restful API: 传输协议=http 序列化协议=text
      • gRPC 传输协议=http/2 序列化协议=protocol buffer
  3. RPC 可以基于 HTTP 吗 ?

    • rpc大多是基于tcp实现的,但是也可以基于http实现,grpc的传输协议就是http2协议。
    • 为什么大部分RPC通常会采用自定义TCP协议来进行服务调用: http1.1协议的TCP报文包含太多在传输过程中可能无用的信息,自定义TCP协议进行传输就会避免上面这个问题,极大的减轻了传输数据的开销。

二、 gRPC

2.1 gRPC简介

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

2.2 Protocol Buffers v3

Protocol Buffers 是 Google 开源的,一种与语言无关,平台无关,可扩展的,用来将结构化数据序列化成 二进制数据编码格式 的方法,用于通信协议,数据存储等。
RPC 调用过程中客户端和服务端必须基于同一种 基础消息交换格式,才能让双方明白需要交换的数据到底代表什么意思! 不同的 RPC 框架可能选用的编解码协议各不相同,比如 gob、JSON、messagepack 等

gRPC 目前使用 Protocol Buffers V3(简称 proto3),它是 proto2 的升级版,性能更优,并增加了对 iOS 和 Android 等移动设备的支持

语法:新建文件 .proto 后缀文件,如 prod.proto

syntax = "proto3";

package pb;
option go_package=".;pb";

message ProdRequest {
  int64 prod_id = 1;
}

message ProdResponse {
  int64 prod_stock = 1;
}

service ProdService {
  rpc GetProdStock (ProdRequest) returns (ProdResponse);
}

编译: proto文件的编译,需要

  1. 安装编译器: 例 wget https://github.com/google/protobuf/releases/download/v3.12.3/protobuf-all-3.12.3.zip ...
  2. 安装编译器插件:例 golang 插件 go get -u github.com/golang/protobuf/protoc-gen-go

编译命令:

protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR --go_out=DST_DIR --ruby_out=DST_DIR --javanano_out=DST_DIR --objc_out=DST_DIR --csharp_out=DST_DIR path/to/file.proto
  • golang中的编译参数:
    • -I 参数:指定import路径,可以指定多个-I参数,编译时按顺序查找,不指定时默认查找当前目录
    • --go_out :golang编译支持,支持以下参数
      • plugins=plugin1+plugin2 - 指定插件,目前只支持grpc,即:plugins=grpc
        • 这里定义的 proto 文件是涉及了 RPC 服务的,而默认是不会生成 RPC 代码的,因此需要给出 plugins 参数传递给 protoc-gen-go,告诉它,请支持 RPC(这里指定了 gRPC)
      • M 参数 - 指定导入的.proto文件路径编译后对应的golang包名(不指定本参数默认就是.proto文件中import语句的路径)
      • import_prefix=xxx - 为所有import路径添加前缀,主要用于编译子目录内的多个proto文件,这个参数按理说很有用,尤其适用替代一些情况时的M参数,但是实际使用并不能达到我们预想的效果...
      • import_path=foo/bar - 用于指定未声明package或go_package的文件的包名,最右面的斜线前的字符会被忽略
      • 末尾 :编译文件路径 .proto文件路径(支持通配符)

完整示例:

protoc -I . --go_out=plugins=grpc,Mfoo/bar.proto=bar,import_prefix=foo/,import_path=foo/bar:. ./*.proto

2.3 golang 中的 grpc 库

在 golang 中使用 gRPC,安装 grpc-go 包即可,命令:

go get -u google.golang.org/grpc

三、 gRPC的基本使用

gRPC使用流程:

  1. 编写.proto描述文件
  2. 编译生成.pb.go文件
  3. 服务端实现约定的接口并提供服务
  4. 客户端按照约定调用方法请求服务

项目目录:

grpc_demo/
|—— demo01/
    |—— client/
        |—— client.go   // 客户端
    |—— proto/
        |—— prod/
            |—— prod.proto
            |—— prod.pb.go
    |—— server/
        |—— server.go   // 服务端

.proto文件:prod.proto

syntax = "proto3";

package prodpb;
option go_package="./prod;prodpb";

message ProdRequest {
  int64 prod_id = 1;
}

message ProdResponse {
  int64 prod_stock = 1;
}

service ProdService {
  rpc GetProdStock (ProdRequest) returns (ProdResponse);
}

编译生成.pb.go文件:prod.pb.go

# 编译 prod.proto
# 注意 option go_package 参数,在此处全部以 proto/ 目录为相对路径进行编写
# 此时,只有切换到  demo01/proto 目录下执行命令, 生成的 .pb.go 文件才会与 .proto 文件同目录

protoc.exe -I . --go_out=plugins=grpc:. prod/*.proto

server端:server.go

package main

import (
	"context"
	prodpb "grpc_demo/demo01/proto/prod"
	"log"
	"net"

	"google.golang.org/grpc"
)

// 定义 ProdService 并实现约定的接口
type ProdService struct{}

func (p ProdService) GetProdStock(_ context.Context, prodReq *prodpb.ProdRequest) (*prodpb.ProdResponse, error) {
	log.Printf("ProdRequest.ProdId = %d", prodReq.ProdId)
	return &prodpb.ProdResponse{ProdStock: 1008}, nil
}

// 生成ProdService
func CreateProdService() ProdService {
	return ProdService{}
}

const (
	// Address gRPC服务地址
	Address = "127.0.0.1:8899"
)

func main() {
	// 1. 创建 gRPC Server 的实例对象
	rpcServer := grpc.NewServer()
	// 2.gRPC Server 内部服务和路由的注册
	prodpb.RegisterProdServiceServer(rpcServer, CreateProdService())

	// 3. 监听指定 TCP 端口,用于接受客户端请求
	listener, err := net.Listen("tcp", Address)
	if err != nil {
		panic("net.Listen err: " + err.Error())
	}

	log.Printf("Listening on %s\n", Address)
	// 4. Serve() 调用服务器以执行阻塞等待,直到进程被终止或被 Stop() 调用
	log.Fatal(rpcServer.Serve(listener))
}

client端: client.go

package main

import (
	"context"
	prodpb "grpc_demo/demo01/proto/prod"
	"log"

	"google.golang.org/grpc"
)

const (
	// Address gRPC服务地址
	Address = "127.0.0.1:8899"
)

func main() {
	// 连接 rpc 服务器
	conn, err := grpc.Dial(Address, grpc.WithInsecure())
	if err != nil {
		panic("grpc.Dial err: " + err.Error())
	}
	defer conn.Close()

	// 初始化客户端
	client := prodpb.NewProdServiceClient(conn)
	resp, err := client.GetProdStock(context.Background(), &prodpb.ProdRequest{ProdId: 1111111})
	if err != nil {
		log.Print("调用失败,err=", err)
		return
	}
	log.Printf("%+v \n", resp)
}

验证:

启动 Server:

cd demo01/server
go run server.go

Listening on 127.0.0.1:8899

启动 Client:

cd demo01/client
go run client.go

prod_stock:1008 

搞定! 成功连接服务端,并获取数据...

参考文档:

https://github.com/Jergoo/go-grpc-example

https://eddycjy.com/tags/grpc-gateway/

https://www.cnblogs.com/FireworksEasyCool/category/1693727.html

posted @ 2020-07-11 12:17  朱一一  阅读(528)  评论(0)    收藏  举报