使用目前最新的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 接口面临哪些问题需要解决:
- 当grpc服务器只有一个的时候,如何处理?
- 当grpc服务器有多个的时候,如何处理?
- 当grpc服务器负载不同的时候如何处理? 或者说: 当多个链接都处于ready状态的时候,应该如何选择?
- 当链接失败的时候,如何处理? 是否启动重试等等
grpc为了解决这些问题, 把链路分为不同的阶段:
balancer构建的阶段
子链接具体的连接阶段: 一个grpc服务器地址对应一个连接, 多个地址的时候就会有多个子连接
子连接的选择问题(picker接口完成)
balancer状态
链路创建, 删除,更新
负载均衡相关的接口:
- Builder接口: 用于构建一个balancer接口实例 - 重点!!
- SubConn接口: 主要负责具体的连接
- Picker接口: 主要负责从众多的连接李,按照负载均衡算法选择一个连接供客户端使用 - 最重点!!
- Balancer接口: 主要负责更新clientConn状态, 更新subConn状态 - 重点!!
- 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对这一点上做了优化。
-
信息压缩:头信息使用gzip或compress压缩后再发送
-
双方信息缓存:客户端和服务端同时维护一张头信息表,当需要更改时发送对应信的索引号即可
多路复用
在连接和传输上http2也做了优化,在目前浏览器中打开一个页面总会加载很多的js/css/img等文件信息,因http1是短连接,因此每一个信息都可以视为是一个新的请求,需要与服务端建立多次tcp的连接才能获取到所有信息。
并且在收到数据信息后,还需要按照顺序一一对应,就形成了队头阻塞的情况。
而http2则只需要建立一次tcp链接,通过多条流,基于帧中的标识就可以知道信息是属于哪个请求,大大的提升了http传输的性能。
Links
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://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
protobuf编码 go语言编程之旅,grpc章节