1.安装

1.编译器

下载对应系统的文件解压并设置变量 protocolbuffers/protobuf/tags

2.go插件

go install google.golang.org/protobuf/cmd/protoc-gen-go@latest

2.hello world

创建一个空项目

2.新建文件 user.proto

//proto语法版本
syntax = "proto3";
//生成的文件夹
option go_package = "../service";
//生成的包名
package service;
//生成的对象
message User{
  string username = 1;
  int32  age = 2;
}

3.运行命令

protoc --go_out=./ .\user.proto

4.编写测试码

package main

import (
    "fmt"
    "google.golang.org/protobuf/proto"
    "my-proto/service"
)

func main() {
    user := &service.User{
        Username: "hideyoshi",
        Age:      18,
    }

    marshal, err := proto.Marshal(user)
    if err != nil {
        panic(err)
    }

    newUser := &service.User{}
    err = proto.Unmarshal(marshal, newUser)
    if err != nil {
        panic(err)
    }

    fmt.Println(newUser.String())
}

2.proto文件

1.字段规则

  • require:消息体中的必填字段,不色泽会导致编码异常
  • optional:消息体中的可选字段
  • repeated:消息体中的可重复字段,重复的顺序会被保留 在go中重复的会被定义为切片
message User{
    string username = 1;
    int32 age = 2;
    optional string password = 3;
    repeated string address = 4;
}

2.字段映射

.proto
Type
Notesc++
Type
Python
Type
Go
Type
double doublefloatfloat64
float floatfloatfloat32
int32使用变长编码,对于负值效率很低,如果你的域有可能有负值,请使用sint64代替int32intint32
uint32使用变长编码uint32int/longuint32
sint32使用变长编码,这些编码在负值时比int32高效很多int32intint32
uint64使用变长编码uint64int/longuint64
sint64使用变长编码,这些编码在负值时通常比int64高效int64int/longint64
fixed32总是4个字节,如果数值总比228打的话使用这个类型会比uint32高效uint32intuint32
fixed64总是4个字节,如果数值总比228打的话使用这个类型会比uint64高效uint64int/longuint64
sfixed32总是4字节int32intint32
sfixed64总是8字节int64int/longint64
boolboolboolboolbool
string一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本stringstr/unicodestring
bytes可能包含任意顺序的字节数据stringstr[]byte

3.默认值

protobuf3 删除了 protobuf2 中用来设置默认值的default关键字,取而代之的是protobuf3为各类型定义的默认值,也就是约定的默认值,如下表所示:

类型默认值
boolfalse
整型0
字符串""
枚举第一个枚举元素值,因为protobuf3强制要求第一个枚举元素的值必须是0,所以枚举默认值就是0;
message不是null,而是DEFAULT_INSTANCE

4.标识号

在消息体的定义中,每个字段都必须要有一个唯一标识号,标识号是[0,2^29-1]范围内的一个整数

message User{
    string username = 1; //位置1
    int32 age = 2;
    optional string password = 3;
    repeated string address = 4;//位置4
}

以User为例:username=1,age=2,password=3,address=4中1-4就是标识号

5.定义多个消息类型

一个proto文件可以定义多个消息类型

message UserRequest{
    string username = 1;
    int32 age = 2;
    optional string password = 3;
    repeated string address = 4;
}

message UserResponse{
    string username = 1;
    int32 age = 2;
    optional string password = 3;
    repeated string address = 4;
}

可以在其他消息类型中定义、使用消息类型:

message PersonList{
    message Person{
        string name = 1;
        int32 height = 2;
        repeated int32 weight = 3;
    }
    repeated Person info = 1;
}

如果你想在他的父消息类型中的外部重用这个消息类型,你需要以PersonList.Person的形式使用它,如:

message PersonMessage{
    PersonList.Person info = 1;
}

当然我们也可以将消息嵌套任意层

message Grandpa{
    message Father{
        message Son{
            string name = 1;
            int32 age = 2;
        }
    }
    message Uncle{
        message Son{
            string name = 1;
            int32 age = 2;
        }
    }
}

6.定义服务

如果想要将消息类型用在RPC系统中,可以在.proto文件中定义一个PRC接口,protocol buffer编译器将会根据所选择的不同语言生成服务接口代码及存根

service SearchService{
    rpc Search (SearchRequest) returns (SearchResponse);
}

上述表示,定义了一个PRC服务,该方法接收SearchRequest返回SearchResponse

3.grpc实例

3.1 PRC和gRPC介绍

RPC远程过程调用协议,一种通过网络从远程计算机上请求服务,而不需要了解底层网络技术的协议。PPC他假定某些协议纯在,例如TPC/UDP等,为通信程序之间携带信息数据。在OSI网络七层模型中,RPC跨越了传输层和应用层,PRC使得开发,包括网络分布式多线程在内的应用程序更容易。

过程是什么?过程就是业务处理、计算任务,更直白的说,就是程序,就像调用本地方法一样调用远程的过程

PRC采用客户端/服务端的模式,通过request-response消息模式实现

gRPC里客户端应用可以像调用本地对象一样直接调用另一台服务端应用的方法,使得你能够跟容易得创建分布式应用和服务。与许多RPC系统相似,gRPC也是基于以下理念:定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个gRPC服务器来处理客户端调用。在客户端拥有一个存根,像服务端一样的方法。

3.2 gRPC

官方网站: http://grpc.io/

底层协议:

3.3 HTTP2

  • HTTP1里的hearder对应HTTP2里的 HEADERS frame
  • HTTP2里的payload对应HTTP2里的DATA frame

gRPC把元数据放到HTTP2 Headers里,请求参数序列化后放到DATA frame里

基于HTTP2协议的优点

  1. 公开标准
  2. HTTP2的前身是Google的SPDY,有经过实践检验
  3. HTTP2天然致辞物联网、手机、浏览器
  4. 基于HTTP2多语言客户端实现更容易

    1. 每个流行变长语言都会有成熟的HTTP2 Client
    2. HTTP2 Client是经过充分测试,可靠的
    3. 用Client发送HTTP2请求远低于用socket发送数据包、解析数据包
  5. HTTP2支持Stream和流控
  6. 基于HTTP2在Gateway/Proxy很容易支持

    1. nginx和envoy都有支持
  7. HTTP2安全性有保证

    1. HTTP2天然支持SSL,当然gRPC可以跑在clear text协议(既不加密)上。
    2. 很多私有协议的rpc可能自己包装了一层TLS支持,使用起来也非常复杂。开发者是否有足够的安全知识?使用者是否配置对了?运维者是否能正确理解?
    3. HTTP2在公有网络上传输有保证。比如这个CRIME攻击,私有协议很难保证没有这样电费漏洞
  8. HTTP2鉴权成熟

    1. 从HTTP1发展起来的鉴权系统已经很成熟了,可以无缝用在HTTP2上
    2. 可以从前端到后端完全打通鉴权,不需要做任何转换适配

基于HTTP2协议的缺点

  • rpc的元数据传输不够高效

​ 尽管HPAC可以压缩HTTP Header,但是对于rpc来说,确定一个函数调用,可以简化为一个int,只要两端协商过一次,后面直接查表就可以了,不需要HAPC那样解码。

​ 可以考虑专门对gRPC做一个优化过的HTTP2解析器,减少一些通用的处理,感觉可以提升性能

  • HTTP2里gRPC调用需要解码两次

​ 一次是HEADERS frame,一次是DATA frame

  • HTTP2标准本身只有一个TCP链接,但是实际在gRPC里是会有多个TCP链接,使用时需要注意

gRPC选择基于HTTP2,那么他的性能肯定不会是最顶尖的。但是对于rpc来说中庸的qps可以接受,通用性和兼容性才是最重要的事情

gRPC目前是k8s生态里的事实标准,而Kubernetes又是容器编排的事实标准。gRPC已经广泛应用于Istio体系,包括

  • Envoy与Pilot(现在叫istiod)间的XDS协议
  • mixer的handler扩展协议
  • MCP(控制面的配置分发协议)

在CLoud Native的潮流下,开放互通的需求必然会产生基于HTTP2的RPC。

3.4实例

3.4.1 服务端

syntax = "proto3";
option go_package="../service";
package service;
message ProductRequest{
    int32 prod_id = 1;
}

message ProductResponse{
    int32 prod_stock = 1;
}

service ProService{
    rpc GetProductStock(ProductRequst) returns(ProductResponse);
}
package main

import (
    "fmt"
    "google.golang.org/grpc"
    "log"
    "my-proto/service"
    "net"
)

func main() {
    server := grpc.NewServer()
    service.RegisterProductServiceServer(server, service.ProductService)
    listen, err := net.Listen("tcp", ":8002")
    if err != nil {
        log.Fatal("启动监听失败", err)
    }
    err = server.Serve(listen)
    if err != nil {
        log.Fatal("启动监听失败", err)
    }
    fmt.Println("启动完成")
}

3.4.1 客户端

package main

import (
    "context"
    "fmt"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
    "log"
    "my-proto/service"
)

func main() {
    dial, err := grpc.Dial(":8002",
        grpc.WithTransportCredentials(insecure.NewCredentials()))
    if err != nil {
        log.Fatal("客户端出错", err)
    }
    defer dial.Close()
    client := service.NewProductServiceClient(dial)
    request := &service.ProductRequest{
        ProdId: 123,
    }
    stock, err := client.GetProductStock(context.Background(), request)
    if err != nil {
        log.Fatal("查询失败", err)
    }
    fmt.Println("查询成功", stock)
}

3.4 认证

上面代码没有证书

3.4.1 生成自签证书

生产环境可以购买证书或使用平台发放的免费证书

  • 安装openssl

网站下载:http://slproweb.com/products/Win32OpenSSL.html

(mac电脑自行搜索安装)

  • 生成私钥文件

    openssl genrsa -des3 -out server.key 2048
  • 创建证书请求

    openssl req -new -key server.key -out server.csr
  • 生成ca.crt

    openssl x509 -req -days 365 -in ca.csr -signkey ca.key -out ca.crt

找到openssl.cnf文件

  1. 打开copy_extensions = copy
  2. 打开req_etensions= v3_req
  3. 找到[ v3req ],添加subjectAltName = @alt_names
  4. 添加新的标签[ alt_names ],和标签字段

    DNS.1 = *.hideyoshi.top

...

Last modification:October 19, 2023
如果觉得我的文章对你有用,请收藏本站