这是我参与「第三届青训营 -后端场」笔记创作活动的的第3篇笔记
golang——gRPC学习
特性
基于HTTP/2
http/2相对于http/1.1的改动:
-
头部压缩:若发送多个请求,且头部一样或相似,则会消除重复(HPACK算法:客户端和服务器同时维护一张头部信心表,所有字段在表中生成索引,因此只需发送索引号即可)。
-
二进制格式:头部信息和数据体都是用二进制,且统称为帧。
-
数据流:通过数据包标记确定属于哪个回应,且客户端可以指定数据流的优先级。
-
多路复用:可以在一个连接中发送多个请求或响应,不用按照顺序一一对应,解决队头阻塞问题。
-
服务器推送:服务器可以主动向客户端发送消息。
-
HTTP/2仍有缺陷:
- 多个HTTP请求复用一个TCP连接,若发送丢包现象,TCP会触发重传机制,导致所有HTTP请求必须等待被丢失的包重传。即一旦发生丢包,会阻塞所有的HTTP请求。
IDL使用ProtoBuf
- protobuf是由Google开发的一套对数据结构进行序列化的方法,可用做通信协议,数据存储格式,等等。其特点是不限语言、不限平台、扩展性强,就像XML一样。
- 序列化后生成的代码体积更小;
- 解析速度更快
- 不易读
多语言支持
四种模式
- 简单模式:简单模式只是使用参数和返回值作为服务器与客户端传递数据的方式,最简单。
- 客户端流模式:即从客户端往服务器端发送数据使用的是流,即服务器端的参数为流类型,然而在服务器相应后返还数据给客户端,使用的也是流的send方法。一般在服务器端的代码,需要先recv再send,而客户端与此相反。但是在后面的双向模式中可以使用go的协程协作。
- 服务器端流模式:即服务器端返回结果的时候使用的是流模式,即传入的数据是通过参数形式传入的。但是在往客户端发送数据时使用send方法,与客户端返回数据的方式大同小异。
- 双向模式:客户端如果不适用协程,那么发送必须在接收之前。如果使用协程,发送与接收并没有先后顺序。为了保证协程的同步,可以使用互斥量进行约束。
gRPC优缺点:
优点:
- protobuf二进制消息,性能好/效率高(空间和时间效率都很不错)
- proto文件生成目标代码,简单易用
- 序列化反序列化直接对应程序中的数据类,不需要解析后在进行映射(XML,JSON都是这种方式)
- 支持向前兼容(新加字段采用默认值)和向后兼容(忽略新加字段),简化升级
- 支持多种语言(可以把proto文件看做IDL文件)
- Netty等一些框架集成
缺点:
- GRPC尚未提供连接池,需要自行实现
- 尚未提供“服务发现”、“负载均衡”机制
- 因为基于HTTP2,绝大部多数HTTP Server、Nginx都尚不支持,即Nginx不能将GRPC请求作为HTTP请求来负载均衡,而是作为普通的TCP请求。(nginx1.9版本已支持)
- Protobuf二进制可读性差(貌似提供了Text_Fromat功能)
- 默认不具备动态特性(可以通过动态定义生成消息类型或者动态编译支持)
快速入门
安装
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1
go get -u google.golang.org/grpc
protobuf文件编写
// 定义proto版本
syntax = "proto3";
// 定义包名
package pb;
// 定义生成的go文件的包名"路径;别名"
option go_package = "./pb";
// 类似于go的结构体
// 请求数据格式
message Req {
string message = 1;
}
// 响应数据格式
message Res {
string message = 1;
}
// 定义服务器
// 这里定义的服务会生成同名的接口,在业务代码中要实现该接口,然后注册该服务
service SayHi{
// 定义rpc服务
rpc Hello (Req) returns (Res);
}
编译命令:
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative ./hello.proto
server端
package main
import (
"context"
"log"
"net"
"new/grpc/pb"
"google.golang.org/grpc"
)
//定义一个服务结构体
type Server struct {
pb.UnimplementedSayHiServer
}
//定义服务方法
func (s *Server) Hello(c context.Context, in *pb.Req) (*pb.Res, error) {
log.Println(in.Message)
return &pb.Res{Message: "服务器发送的信息"}, nil
}
func main() {
//监听端口
listen, err := net.Listen("tcp", ":9999")
if err != nil {
log.Println(err)
return
}
//注册grpc服务
s := grpc.NewServer()
//注册服务方法
pb.RegisterSayHiServer(s, &Server{})
//开启grpc服务
s.Serve(listen)
}
client端
package main
import (
"context"
"log"
"new/grpc/pb"
"google.golang.org/grpc"
)
func main() {
//建立连接
conn, err := grpc.Dial("127.0.0.1:9999", grpc.WithInsecure())
if err != nil {
log.Println(err)
return
}
defer conn.Close()
//定义相应方法的客户端
c := pb.NewSayHiClient(conn)
//调用相应方法
res, err := c.Hello(context.Background(), &pb.Req{Message: "客户端发送的信息"})
if err != nil {
log.Println(err)
return
}
log.Println(res)
}
\