day 5:grpc(上) | 青训营笔记

80 阅读6分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 5 天 gRPC

1 微服务架构

1.1 代码冗余问题

服务未拆分之前,公共的功能有统一的实现,比如认证,授权,限流等,但是服务拆分之后,每一个服务都要实现一遍。

解决方案:

  • 由于为了保持对外提供服务的一致性,引入了网关的概念。由于网关根据不同的需求将其转发到不同的服务。由于入口的一致性,可以在网关上实现公共的一些功能。不
  • 可以将公共的功能抽取出来,形成一个新的服务,比如统一认证中心。

1.2 服务之间调用

服务拆分之后,服务和服务之间发生的是进程和进程之间的调用。服务器和服务器之间的调用。

那么就需要发起网络调用,网络调用我们能力往想象到的就是HTTP但是在微服务架构中,HTTP虽然方便快捷,但是性能很低。这时候就需要引入RPC(远程过程调用)。通过自定义协议发起tcp调用,来加快传输。

问题以及解决方案:

2 gRPC简介

数据在网络传输。需要进行序列化。序列化的协议有很多。比如xml, json, Protobuf,G PC默认使用Protocol buffers。

序列化:将数据结构和独享转换成二进制的过程。

反序列化:将在序列化过程中所产生的二进制串转换成数据结构或对象的过程。

3 protobuf

优势:

  • 序列化后体积很小,适合网络传输。
  • 支持跨平台多语言。
  • 序列化和反序列化速度很快。

3.1 安装

  • 第一步:下载通用编译器
  • 地址:github.com/protocolbuf…
  • 根据不同的操作系统,下载不同的包,我是windows电脑,解压出来是protoc.exe
  • 第二步:配置环境变量
  • 第三步:安装go专用的protoc的生成器(最新)
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28

如何使用protobuf呢?

  1. 定义了一种源文件,扩展名为 .proto,使用这种源文件,可以定义存储类的内容(消息类型)
  2. protobuf有自己的编译器 protoc,可以将 .proto 编译成对应语言的文件,就可以进行使用了

3.2 hello world

假设,我们现在需要传输用户信息,其中有username和age两个字段

// profile/user.proto
// 指定的当前proto语法的版本,有23  syntax = "proto3" ; //option go_package = "path;name"; ath 表示生成的go文件的存放地址,会自动生成目录的  // name 表示生成的go文件所属的包名  option go_package= "../service" ; // 指定等会文件生成出来的package  package service;  message User {  string username = 1 ;  int32 age = 2 ; } 

运行protoc命令编译成go中间文件

# 编译user.proto之后输出到service文件夹
 protoc  --go_out=./ --go-grpc_out=./  .\user.proto

测试代码

package main

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

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

   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)
}

3.3 proto文件介绍

3.3.1 message介绍

messageprotobuf中定义一个消息类型是通过关键字message字段指定的。

消息就是需要传输的数据格式的定义。

message关键字类似于C++中的class,Java中的class,go中的struct

例如:

message User {
  string username = 1;
  int32 age = 2;
}

在消息中承载的数据分别对应于每一个字段。

其中每个字段都有一个名字和一种类型 。

3.3.2 字段规则

  • required:消息体中必填字段,不设置会导致编解码异常。(例如位置1)
  • optional: 消息体中可选字段。(例如位置2)
  • repeated: 消息体中可重复字段,重复的值的顺序会被保留(例如位置3)在go中重复的会被定义为切片。
message User {
  string username = 1;
  int32 age = 2;
  optional string password = 3;
  repeated string address = 4; //一个用户有多个地址
}

3.3.3 字段映射

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

3.3.4 默认值

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

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

3.3.5 标识号

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

message Person { 

  string name = 1;  // (位置1)
  int32 id = 2;  
  optional string email = 3;  
  repeated string phones = 4; // (位置4)
}

以Person为例,name=1,id=2, email=3, phones=4 中的1-4就是标识号。

3.3.6 定义多个消息类型

一个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;
}

3.3.7 嵌套消息

可以在其他消息类型中定义、使用消息类型,在下面的例子中,Person消息就定义在PersonInfo消息内,如 :

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

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

message PersonMessage {
        PersonInfo.Person info = 1;
}

当然,你也可以将消息嵌套任意多层,如 :

message Grandpa { // Level 0
    message Father { // Level 1
        message son { // Level 2
            string name = 1;
            int32 age = 2;
            }
        } 
    message Uncle { // Level 1
        message Son { // Level 2
            string name = 1;
            int32 age = 2;
        }
    }
}

3.3.8 定义服务(Service)

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

service SearchService {
        //rpc 服务的函数名 (传入参数)返回(返回参数)
        rpc Search (SearchRequest) returns (SearchResponse);
}

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