使用目前最新的protoc,protoc-gen-go,protoc-gen-go-grpc。 总结下grpc,protobuf 的使用。以前公司的社交项目都是用的grpc server和客户端 app,ios做通信的。 也有内部的后台是python写的,python作为客户端去调go 的grpc 服务。

版本:

  • protoc-gen-go(v1.28.1)
  • protoc(v3.21.9)

ps:和老的版本还是生成命令 和 流式grpc server有一定的改动

相关术语:

  • unary 和streaming rpc。 一次元rpc,服务端流式rpc,客户端流式rpc,双向流式rpc
  • grpc的错误处理,错误码
  • grpc拦截器
  • meta和rpc自定义认证
  • 客户端请求超时,重试(go-grpc-middleware)
  • 负载均衡

protoc

  • protobuf文件目录规范
  • package hashtag.gateway; //包名, hashtag/gateway目录
  • option go_package = “biz/activity”; //生成到相对路径 biz目录下activity目录下
  • option go_package=".;proto"; // . 生成路径, proto 包名; 不推荐用这个方式,等于把上面2个一起搞定了。
  • 字段编号 和字段是映射的, 如果错了,那么取到的值 也是取错的字段了
  • 一个proto文件可以导入另外一个proto文件 公共的message
  • 自带 empty,timestamp类型

工具安装

1brew install protobuf # libprotoc 3.21.9
2go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
3go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2

protobuf使用指南

目录结构

 1project-name
 2|-----api   //服务api定义
 3   |------protobuf
 4      |-----internel     //内部rpc服务 proto定义
 5      |-----common       //公共的proto
 6      |-----web         //用于web
 7      |-----gateway      //用户定义项目核心的proto 
 8         |----user   
 9             |-----v1       //版本号  
10      |-----third_party //第三方服务的proto

参考了kratos开源社区

包名 package

包名为应用的标识(APP_ID),用于生成gRPC请求路径,或者Proto之间进行引用Message;

my.package.v1,为API目录,定义service相关接口,用于提供业务使用

1package <package_name>.<version>
2
3// RequestUrl: /<package_name>.<version>.<service_name>/<method>

Version

该版本号为标注不兼容版本,并且会在<package_name>中进行区分,当接口需要重构时一般会更新不兼容结构;这个和我们的普通http接口,使用v1,v2版本思路一致

Import

业务proto依赖,以根目录进行引入对应依赖的proto; third_party,主要为依赖的第三方proto,比如protobuf、google rpc、google apis、gogo定义;

命名规范

目录结构

包名为小写,并且同目录结构一致,例如:my/package/v1/

1package my.package.v1

文件结构

1文件应该命名为:lower_snake_case.proto 所有Proto应按下列方式排列:
2License header (if applicable)
3File overview
4Syntax
5Package
6Imports (sorted)
7File options
8Everything else

Message 和 字段命名

使用驼峰命名法(首字母大写)命名 message,例子:SongServerRequest 使用下划线命名字段,例子:song_name

1   message SongServerRequest {
2       required string song_name = 1;
3   }

枚举 Enums

使用驼峰命名法(首字母大写)命名枚举类型,使用 “大写下划线大写” 的方式命名枚举值:

1enum Foo {
2  FIRST_VALUE = 0;
3  SECOND_VALUE = 1;
4}

服务 Services

如果你在 .proto 文件中定义 RPC 服务,你应该使用驼峰命名法(首字母大写)命名 RPC 服务以及其中的 RPC 方法:

1service Greeter {
2  rpc PutStream(HelloRequest) returns (HelloReponse); //客户端流模式
3}

protobuf 比json 传输体积更小,更高效

目录结构

 1➜  micro-test git:(main) ✗ tree .               
 2.
 3├── go.mod
 4├── go.sum
 5├── grpc_helloworld
 6│   ├── main.go
 7│   └── protoc
 8│       ├── hello.pb.go 
 9│       └── hello.proto
10└── readme.md

hello.proto

 1syntax = "proto3";
 2
 3//包名
 4package grpc_hellworld.protoc; 
 5// 生成路径
 6option go_package = "grpc_helloworld/protoc";
 7
 8message HelloRequest {
 9  string username = 1;
10}
1# https://grpc.io/docs/languages/go/quickstart/#regenerate-grpc-code
2# 新的 protoc 可能使用起来有点不一样,不支持以前的插件参数了。要单独指定 --go-grpc_opt
3# 项目根目录
4protoc --go_out=. --go_opt=paths=source_relative \
5    --go-grpc_out=. --go-grpc_opt=paths=source_relative \
6    grpc_helloworld/protoc/hello.proto 
7# 生成的hello.pb.go    在 grpc_helloworld/protoc 下 和 hello.proto同目录
 1package main
 2
 3import (
 4	"encoding/json"
 5	"fmt"
 6
 7	"github.com/golang/protobuf/proto"
 8	"microgo/grpc_helloworld/protoc"
 9)
10
11type Hello struct {
12	Username string
13}
14
15func main() {
16	p := protoc.HelloRequest{
17		Username: "cr-mao毛",
18	}
19	data, _ := proto.Marshal(&p)
20	fmt.Println("------")
21	fmt.Println(string(data))
22	fmt.Println(len(data)) //11
23	j := Hello{
24		Username: "cr-mao毛",
25	}
26	jsonData, _ := json.Marshal(&j)
27	fmt.Println("------")
28	fmt.Println(string(jsonData))
29	fmt.Println(len(jsonData)) //24
30}
31
32/*
33结果
34------
35
36        cr-mao毛
3711
38------
39{"Username":"cr-mao毛"}
4024
41
42*/
 1{
 2  "id":1,
 3  "name":"cr-mao"
 4}
 5
 6
 7message user {
 8  string id = 1
 9  string name= 2
10}
11
12数值比字符串省空间
13{
14  1: 1,
15  2: "cr-mao"
16}

序列化的时候对int类型运用varint格式存储

序列化数据运用tlv格式存储

流式grpc

stream.proto

 1syntax = "proto3";
 2
 3package protoc;
 4
 5option go_package="grpc_stream/protoc";
 6
 7service Greeter {
 8  rpc GetStream(StreamReqData) returns (stream StreamResData); //服务端流模式
 9  rpc PutStream(stream StreamReqData) returns (StreamResData); //客户端流模式
10  rpc AllStream(stream StreamReqData) returns (stream StreamResData); //双向流模式
11}
12
13message  StreamReqData {
14  string data =1 ;
15}
16
17message StreamResData {
18  string data = 1;
19}

生成pb命令

1protoc --go_out=. --go_opt=paths=source_relative \
2    --go-grpc_out=. --go-grpc_opt=paths=source_relative \
3    grpc_stream/protoc/steam.proto

项目目录结构

1├── grpc_stream
2│   └── protoc
3│       ├── steam.pb.go
4│       ├── steam.proto
5│       └── steam_grpc.pb.go
6stream_server.go
7stream_client.go

stream_server.go

 1package main
 2
 3import (
 4	"fmt"
 5	"io"
 6	"log"
 7	"net"
 8	"sync"
 9	"time"
10
11	"microgo/grpc_stream/protoc"
12
13	"google.golang.org/grpc"
14)
15
16const PORT = ":50052"
17
18type binding struct {
19	protoc.UnimplementedGreeterServer
20}
21
22// 服务端流式
23func (s *binding) GetStream(req *protoc.StreamReqData, res protoc.Greeter_GetStreamServer) error {
24
25	i := 0
26	for {
27		i++
28		err := res.Send(&protoc.StreamResData{
29			Data: fmt.Sprintf("%v - %v", time.Now().Unix(), req.Data),
30		})
31		if err != nil {
32			return err
33		}
34		if i > 10 {
35			break
36		}
37	}
38
39	return nil
40}
41
42// 客户端流式
43func (s *binding) PutStream(cliStr protoc.Greeter_PutStreamServer) error {
44	for {
45		resp, err := cliStr.Recv()
46		if err == io.EOF {
47			// 接受完毕,返回客户端数据
48			return cliStr.SendAndClose(&protoc.StreamResData{
49				Data: "some response",
50			})
51		}
52		if err != nil {
53			return err
54		}
55		log.Printf("resp:%v", resp)
56	}
57	return nil
58}
59
60// 双向流式
61func (s *binding) AllStream(allStr protoc.Greeter_AllStreamServer) error {
62	var wg sync.WaitGroup = sync.WaitGroup{}
63	wg.Add(2)
64	go func() {
65		defer wg.Done()
66		for {
67			data, _ := allStr.Recv()
68			fmt.Println("收到客户端消息", data.Data)
69		}
70
71	}()
72	go func() {
73		defer wg.Done()
74		for {
75			_ = allStr.Send(&protoc.StreamResData{
76				Data: "我是服务器发送的数据",
77			})
78			time.Sleep(time.Second)
79
80		}
81	}()
82	wg.Wait()
83	return nil
84}
85
86func main() {
87	server := grpc.NewServer()
88	protoc.RegisterGreeterServer(server, &binding{})
89	lis, _ := net.Listen("tcp", "127.0.0.1"+PORT)
90	fmt.Println("listen at " + PORT)
91	err := server.Serve(lis)
92	if err != nil {
93		log.Fatal(err)
94	}
95}

stream_client.go

 1package main
 2
 3import (
 4	"context"
 5	"fmt"
 6	"io"
 7	"log"
 8	"sync"
 9	"time"
10
11	"google.golang.org/grpc"
12
13	"microgo/grpc_stream/protoc"
14)
15
16func main() {
17	conn, err := grpc.Dial(":50052", grpc.WithInsecure())
18	if err != nil {
19		panic(err)
20	}
21	defer conn.Close()
22	client := protoc.NewGreeterClient(conn)
23
24	//服务端流模式
25	stream, _ := client.GetStream(context.Background(), &protoc.StreamReqData{Data: "crmao"})
26	for {
27		resp, err := stream.Recv()
28		if err == io.EOF {
29			break
30		}
31		if err != nil {
32			panic(err)
33		}
34		log.Printf("resp:%v", resp)
35	}
36
37	//客户端流模式
38	putS, _ := client.PutStream(context.Background())
39	i := 0
40	for {
41		i++
42		_ = putS.Send(&protoc.StreamReqData{
43			Data: fmt.Sprintf("%d", i),
44		})
45		time.Sleep(time.Second)
46		if i > 6 {
47			break
48		}
49	}
50	// 关闭发送,接受服务的响应
51	resp, err := putS.CloseAndRecv()
52	if err != nil {
53		panic(err)
54	}
55	log.Printf("steam client resp err :%v", resp)
56
57	allStr, _ := client.AllStream(context.Background())
58	wg := sync.WaitGroup{}
59	wg.Add(2)
60
61	go func() {
62		defer wg.Done()
63		for {
64			data, err := allStr.Recv()
65			if err != nil {
66				fmt.Println(err)
67			}
68			fmt.Println("收到服务端消息", data.Data)
69		}
70
71	}()
72	go func() {
73		defer wg.Done()
74		for {
75			_ = allStr.Send(&protoc.StreamReqData{
76				Data: "我是客户端发送的数据",
77			})
78			time.Sleep(time.Second)
79		}
80	}()
81	wg.Wait()
82}

效果如下

 1➜  micro-test git:(main) ✗ go run stream_client.go 
 22022/11/06 23:22:20 resp:data:"1667748140 - crmao"
 32022/11/06 23:22:20 resp:data:"1667748140 - crmao"
 42022/11/06 23:22:20 resp:data:"1667748140 - crmao"
 52022/11/06 23:22:20 resp:data:"1667748140 - crmao"
 62022/11/06 23:22:20 resp:data:"1667748140 - crmao"
 72022/11/06 23:22:20 resp:data:"1667748140 - crmao"
 82022/11/06 23:22:20 resp:data:"1667748140 - crmao"
 92022/11/06 23:22:20 resp:data:"1667748140 - crmao"
102022/11/06 23:22:20 resp:data:"1667748140 - crmao"
112022/11/06 23:22:20 resp:data:"1667748140 - crmao"
122022/11/06 23:22:20 resp:data:"1667748140 - crmao"
132022/11/06 23:22:27 steam client resp err :data:"some response"
14收到服务端消息 我是服务器发送的数据
15收到服务端消息 我是服务器发送的数据
16收到服务端消息 我是服务器发送的数据
17收到服务端消息 我是服务器发送的数据
18收到服务端消息 我是服务器发送的数据
19.
20.
21.
22
23➜  micro-test git:(main) ✗ go run stream_server.go
24listen at :50052
252022/11/06 23:22:20 resp:data:"1"
262022/11/06 23:22:21 resp:data:"2"
272022/11/06 23:22:22 resp:data:"3"
282022/11/06 23:22:23 resp:data:"4"
292022/11/06 23:22:24 resp:data:"5"
302022/11/06 23:22:25 resp:data:"6"
312022/11/06 23:22:26 resp:data:"7"
32收到客户端消息 我是客户端发送的数据
33收到客户端消息 我是客户端发送的数据
34收到客户端消息 我是客户端发送的数据
35收到客户端消息 我是客户端发送的数据
36收到客户端消息 我是客户端发送的数据
37.
38.
39.

unary 及 metadata 及rpc 自定义认证

hello.proto

 1syntax = "proto3";
 2
 3//导入公共的
 4import "basic.proto";
 5//内置空message
 6import "google/protobuf/empty.proto";
 7//内置timestamp
 8import "google/protobuf/timestamp.proto";
 9
10// . 生成路径     ;  proto为包名
11option go_package = ".;proto";
12
13service HelloServer {
14  rpc SayHello(HelloRequest) returns (HelloResponse);
15  //空 message
16  // Pong  共用
17  rpc SayHelloAgain(google.protobuf.Empty) returns (Pong);
18}
19
20//枚举类型
21enum Gender {
22  MALE = 0;
23  FEMALE = 2;
24}
25
26message HelloRequest {
27  string username = 1;
28  Gender g = 2;
29  map <string, string> mp = 3;
30  google.protobuf.Timestamp t = 4;
31}
32
33message HelloResponse {
34  string msg = 1;
35  // 嵌套message 的使用
36  message Result {
37    string name = 1;
38    string url = 2;
39  }
40  repeated  Result  res = 2;
41}

basic.proto

 1syntax = "proto3";
 2
 3option go_package = ".;proto";
 4
 5message Empty {
 6
 7}
 8message Pong {
 9  string id = 1;
10}

unary gRPC-Server编写

 1package main
 2
 3import (
 4	"context"
 5	"fmt"
 6	"google.golang.org/grpc/codes"
 7	"google.golang.org/grpc/metadata"
 8	"google.golang.org/grpc/status"
 9	"log"
10	"microgo/grpc_unary_test/proto"
11	"net"
12	"strconv"
13
14	"github.com/golang/protobuf/ptypes/empty"
15	"google.golang.org/grpc"
16)
17
18type Bingding struct {
19}
20
21//空 message
22// Pong  共用
23func (s *Bingding) SayHello(ctx context.Context, in *proto.HelloRequest) (*proto.HelloResponse, error) {
24	// https://github.com/grpc/grpc-go/tree/master/examples/features/metadata/server
25	//meta
26	//超时 测试
27	//time.Sleep(5 * time.Second)
28	return nil, status.Errorf(codes.InvalidArgument, "审核信息不能为空")
29
30	md, ok := metadata.FromIncomingContext(ctx)
31	if ok {
32		fmt.Printf("meta信息:%v\n",md)
33	}
34	mp := in.GetMp()
35	fmt.Println(mp)
36	fmt.Println(in.GetT().String())
37	return &proto.HelloResponse{
38		Msg: "hello" + in.GetUsername() + strconv.Itoa(int(in.GetG())),
39		//嵌套message
40		Res: []*proto.HelloResponse_Result{{
41			Name: "CRMAO",
42			Url:  "http://www.crblog.cc",
43		}},
44	}, nil
45}
46
47func (s *Bingding) SayHelloAgain(ctx context.Context, in *empty.Empty) (*proto.Pong, error) {
48	return &proto.Pong{
49		Id: "111",
50	}, nil
51}
52//拦截器
53func AuthInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
54	log.Println("auth interceptor start")
55	md, ok := metadata.FromIncomingContext(ctx)
56	if !ok {
57		//fmt.Printf("meta信息:%v\n",md)
58		return nil,status.Error(codes.Unauthenticated,"token认证失败")
59	}
60
61	var (
62		appid string
63		appkey string
64	)
65	if v1 ,ok :=md["appid"];ok {
66		appid = v1[0]
67	}
68	if v1 ,ok :=md["appkey"];ok {
69		appkey = v1[0]
70	}
71
72	if appid !="111111" || appkey !="appkey" {
73		return nil,status.Error(codes.Unauthenticated,"token认证失败")
74	}
75
76	resp, err := handler(ctx, req)
77	log.Println("auth interceptor end")
78	return resp, err
79}
80
81func main() {
82	var grpcOption = []grpc.ServerOption{
83		grpc.UnaryInterceptor(AuthInterceptor),
84	}
85	s := grpc.NewServer(grpcOption...)
86	proto.RegisterHelloServerServer(s, &Bingding{})
87	lis, err := net.Listen("tcp", ":8001")
88	if err != nil {
89		log.Fatalf("failed to listen: %v", err)
90	}
91	err = s.Serve(lis)
92	if err != nil {
93		log.Fatalf("failed to listen: %v", err)
94	}
95}

unary gRPC-Client编写

 1package main
 2
 3import (
 4	"context"
 5	"fmt"
 6	"google.golang.org/grpc/status"
 7	"log"
 8	"time"
 9
10	"google.golang.org/grpc"
11	timestamppb "google.golang.org/protobuf/types/known/timestamppb"
12	"microgo/grpc_unary_test/proto"
13)
14
15type CustomerCredential struct {
16}
17
18//
19func (s *CustomerCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
20	return map[string]string{
21		"appid":  "111111",
22		"appkey": "appkey",
23	}, nil
24}
25func (s *CustomerCredential) RequireTransportSecurity() bool {
26	return false
27}
28
29func main() {
30	//interceptor := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
31	//	start := time.Now()
32	//	md := metadata.New(map[string]string{
33	//		"appid":  "111112",
34	//		"appkey": "appkey",
35	//	})
36	//	ctx = metadata.NewOutgoingContext(ctx, md)
37	//	err := invoker(ctx, method, req, reply, cc, opts...)
38	//	fmt.Printf("耗时 %s\n", time.Since(start))
39	//	//fmt.Println(err)
40	//	return err
41	//}
42	//dailOption := grpc.WithUnaryInterceptor(interceptor)
43	//conn, err := grpc.Dial(":8001", grpc.WithInsecure(), dailOption)
44
45	//  身份认证
46	dailOption := grpc.WithPerRPCCredentials(&CustomerCredential{})
47	var opts []grpc.DialOption
48	opts = append(opts, grpc.WithInsecure())
49	opts = append(opts, dailOption)
50	conn, err := grpc.Dial(":8001", opts...)
51	if err != nil {
52		log.Fatalf("client conn err :%v", err)
53	}
54	defer conn.Close()
55	client := proto.NewHelloServerClient(conn)
56
57	//md := metadata.New(map[string]string{
58	//	"token": "xxx-token",
59	//})
60	//ctx :=metadata.NewOutgoingContext(context.Background(),md)
61	// meta header头传入
62	ctx, _ := context.WithTimeout(context.Background(), time.Second*3)
63	resp, err := client.SayHello(ctx, &proto.HelloRequest{
64		Username: "crmao",
65		//枚举类型 限定
66		G: proto.Gender_MALE,
67		Mp: map[string]string{
68			"nickname": "maodada",
69		},
70		T: timestamppb.New(time.Now()),
71	})
72	if err != nil {
73		st, ok := status.FromError(err)
74		if !ok {
75			panic("错误解析失败")
76		}
77		fmt.Println(st.Message())
78		fmt.Println(st.Code())
79	}
80
81	fmt.Println(resp)
82
83	//resp, err = client.SayHello(context.Background(), &proto.HelloRequest{
84	//	Username: "crmao",
85	//	//枚举类型 限定
86	//	G: proto.Gender_MALE,
87	//	Mp: map[string]string{
88	//		"nickname": "maodada",
89	//	},
90	//	T: timestamppb.New(time.Now()),
91	//})
92	//fmt.Println(resp)
93	//
94	//if err !=nil {
95	//
96	//
97	//}
98}

grpc健康检测

https://github.com/grpc/grpc/blob/master/doc/health-checking.md

https://github.com/cr-mao/crgo/blob/main/grpc/serve.go

1import (
2    //... 
3    "google.golang.org/grpc/health/grpc_health_v1"
4)
5
6grpc_health_v1.RegisterHealthServer(s, health.NewServer())

grpc服务注册与发现、负载均衡原理

官方设计思路

  • 客户端根据服务名发起请求
  • 名称解析器(name resolver) 解析服务名并返回 (返回ip地址一个或者多个,并标记是服务端地址还是负载均衡器地址,及客户端负载均衡策略)
  • 客户端根据服务端类型选择相应的策略(未配置负载均衡策略,则客户端默认选择第一个可用的服务端地址)
  • 根据不同的策略进行调用

https://blog.csdn.net/qq_16399991/article/details/130744846

builder 接口面临哪些问题需要解决:

  1. 当grpc服务器只有一个的时候,如何处理?
  2. 当grpc服务器有多个的时候,如何处理?
  3. 当grpc服务器负载不同的时候如何处理? 或者说: 当多个链接都处于ready状态的时候,应该如何选择?
  4. 当链接失败的时候,如何处理? 是否启动重试等等

grpc为了解决这些问题, 把链路分为不同的阶段:

balancer构建的阶段

子链接具体的连接阶段: 一个grpc服务器地址对应一个连接, 多个地址的时候就会有多个子连接

子连接的选择问题(picker接口完成)

balancer状态

链路创建, 删除,更新

负载均衡相关的接口:

  1. Builder接口: 用于构建一个balancer接口实例 - 重点!!
  2. SubConn接口: 主要负责具体的连接
  3. Picker接口: 主要负责从众多的连接李,按照负载均衡算法选择一个连接供客户端使用 - 最重点!!
  4. Balancer接口: 主要负责更新clientConn状态, 更新subConn状态 - 重点!!
  5. ClientConn接口: 主要负责链路的维护, 包括创建一个子链路, 删除一个子链路, 更新ClientConn状态

protoc 自定义插件开发

https://github.com/grpc/grpc-go/blob/master/cmd/protoc-gen-go-grpc/main.go

protoc基本使用

https://github.com/cr-mao/crgo/blob/main/scripts/codegen.sh

–plugin=EXECUTABLE 可以指定插件的位置, 也可以将插件放到path中

插件执行原理

protoc加载流程 就是 根据标准输入的参数,解析, 对应的可执行插件文件,取读取参数进行想要的工作

https://github.com/grpc/grpc-go/blob/master/cmd/protoc-gen-go-grpc/main.go

grpcurl 工具

前提grpc server 要注册反射

1    import(
2"google.golang.org/grpc/reflection"
3)
4	// Register reflection service on gRPC server. ,给grpcurl工具可以用
5	reflection.Register(srv.Server)
1go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest

列出所有的服务

1game-server git:(master) ✗ grpcurl -plaintext localhost:9000 list
2game.gateway.Greeter # game.gateway 包名, Greeter 是service
3grpc.health.v1.Health
4grpc.reflection.v1alpha.ServerReflection

请求方法

-d json方式,它会帮我们转成body 。 响应结果也是json 的方式返回给我们

1  game-server git:(master) ✗ grpcurl -plaintext -d '{"token":"crmao"}' localhost:9000 game.gateway.Greeter.SayHello
2
3{
4  "Code": 100
5}

对grpc接口进行版本管理

可兼容性修改

  • 新增service
  • 在原来对service中新增rpc方法
  • 在原来对请求message中新增新的字段
  • 在原有的响应message中新增新的字段

破坏性修改

  • 改字段的类型
  • 调整原来字段的标识位(数字)
  • 修改原来的字段名
  • 修改message 原来的命名
  • 删除service或rpc方法
  • 删除原来的字段

解决方案

  • 可兼容性修改
  • 版本号管理
1syntax = "proto3";
2package proto.v1;
3service TagService {
4	rpc GetTagService (GetTagListRequest) returns (GetTagListResponse) {
5	        option (google.api.http) = {
6                get : "/api/v1/tags"          // 自定义url 
7            };	
8     }
9}

在package上指定版本号,格式为proto.v1.TagService或 proto.v2.TagService, 路由格式为/api/v1/tags 或 /api/v2/tags

http2协议

http1协议的问题

  • 低效的tcp利用
  • 臃肿的消息首部
  • 明文传输
  • 传输效率低

在目前的浏览器浏览器中http2的实现是基于ssl/tls,因此http2的连接建立过程和https是差不多的,而http2在http1的基础上做了如下改进。

  • 传输的改进:二进制传输
  • 请求头优化
  • 多路复用
  • 服务端可以推送
  • 基于SSL/TLS提高安全性

传输协议的改进

http1在传输中会遇到如下问题:

  • 一次只能处理一个请求或响应完成之前不能停止解析。
  • 无法预判解析需要多少内存
  • 还需考虑个别浏览器用LF做分隔符

http2在传输中的是采用帧进行解决,在设计思想上与分段传输及tcp包头有相似之处。

在应用层和传输层之间增加一个二进制分帧层,http/2会将所有传输的信息分割为更小的消息和帧,并采用二进制格式的编码。这时原本以固有的header+body的组合报文方式就改为了一个个碎片,这些碎片数据信息可以乱序发送,而浏览器会根据流id重新组合起来,以获得实际传输的数据。

简化的理解为,http2将原本的数据包拆分成为多个小的数据包,然后在每个数据包上编个序号,基于流发送客户端。客户端在接收到这些数据包后,就可以根据固定位获取编号再组装数据。

请求头的优化

http协议是不带状态的,每次请求都必须要附带上所有的信息,因此客户端在请求中会收到很多重复的内容,如cookie、user agent这样就会浪费很多的资源,也影响速度。

http/2对这一点上做了优化。

  1. 信息压缩:头信息使用gzip或compress压缩后再发送

  2. 双方信息缓存:客户端和服务端同时维护一张头信息表,当需要更改时发送对应信的索引号即可

多路复用

在连接和传输上http2也做了优化,在目前浏览器中打开一个页面总会加载很多的js/css/img等文件信息,因http1是短连接,因此每一个信息都可以视为是一个新的请求,需要与服务端建立多次tcp的连接才能获取到所有信息。

并且在收到数据信息后,还需要按照顺序一一对应,就形成了队头阻塞的情况。

而http2则只需要建立一次tcp链接,通过多条流,基于帧中的标识就可以知道信息是属于哪个请求,大大的提升了http传输的性能。

https://developers.google.com/protocol-buffers/docs/style

https://developers.google.com/protocol-buffers/docs/proto3

https://colobu.com/2017/03/16/Protobuf3-language-guide/

https://github.com/grpc/grpc

https://grpc.io/docs/

https://grpc.io/docs/languages/go/quickstart/#regenerate-grpc-code

https://developers.google.com/protocol-buffers/docs/reference/overview

https://github.com/grpc/grpc-go/tree/master/examples/features/metadata/server

https://github.com/grpc/grpc/blob/master/examples/python/metadata/metadata_client.py

https://github.com/grpc/grpc/blob/master/examples/python/metadata/metadata_server.py

https://github.com/grpc/grpc/blob/master/doc/statuscodes.md

https://github.com/grpc/grpc/blob/master/doc/load-balancing.md

https://github.com/mbobakov/grpc-consul-resolver

https://github.com/grpc-ecosystem/go-grpc-middleware

grpc连接池

protobuf编码 go语言编程之旅,grpc章节

grpc服务注册与发现原理