1. RPC
1.1 什么是RPC
RPC: Remote Procedure Call,远程过程调用。调用过程包括传输协议和对象编码(序列化)。
1.2 RPC框架
- 负载均衡
- 服务注册和发现
- 服务治理
1.3 为什么使用RPC
简单、通用、安全、效率
2. Protobuf
Protocol Buffers 是一种与语言、平台无关,可扩展的序列化结构化数据的方法,常用于通信协议、数据存储等。相较于JSON、XML,它更小、更快、更简单。
1 | syntax = "proto3"; |
3. gRPC
gRPC 是一个高性能、开源和通用的RPC框架,面向移动和 HTTP/2 设计
特点:
- HTTP/2
- Protobuf
- 客户端、服务端基于同一份IDL
- 移动网络支持良好
- 支持多语言
3.1 安装
gRPC:
1 | go get -u google.golang.org/grpc |
Protocol Buffers v3:
1 | brew search protobuf |
Protoc Plugin:
1 | # 会自动编译安装protoc-gen-go可执行插件文件 |
3.2 入门
3.2.1 编写 IDL
1 | syntax = "proto3"; |
3.2.2 生成 pb.go文件
1 | protoc --go_out=. *.proto |
3.2.3 服务端
1 | type SearchService struct{} |
3.2.4 客户端
1 | func main() { |
4. gRPC 流
gRPC 的流式,有三种类型:
- Server-side Streaming
- Client-side Streaming
- Bidirectional Streaming
适合用 Streaming RPC 的场景:
- 大规模数据包
- 实时场景
4.1 IDL 和 基础模板
1 | syntax = "proto3"; |
服务器:
1 | func main() { |
客户端:
1 | func main() { |
4.2 服务器端流式 RPC
- 单向流
- Server 为 Stream,多次向客户端发送数据
- Client 为普通 RPC 请求
4.2.1 服务端
1 | func (s *StreamService) List(r *pb.StreamRequest, stream pb.StreamService_ListServer) error { |
stream.Send()
方法:
1 | type StreamService_ListServer interface { |
SendMsg()
方法:
- 消息体(对象)序列化
- 压缩序列化后的消息体
- 对正在传输的消息体增加5个字节的header
- 判断消息体总长度是否大于预设的maxSendMessageSize (默认math.MaxInt32),超过则报错
- 写入给流的数据集
4.2.2 客户端
1 | func printList(client pb.StreamServiceClient, r *pb.StreamRequest) error { |
stream.Recv()
方法:
1 | type StreamService_ListClient interface { |
RecvMsg()
方法:
- 阻塞等待
- 流结束 (Close)时,返回 io.EOF
- 可能的错误
- io.EOF
- io.ErrUnexpectedEOF
- transport.ConnectionError
- google.golang.org/grpc/codes
4.3 客户端流式RPC
- 单向流
- 客户端多次RPC请求服务端
- 服务端发起一次响应给客户端
4.3.1 服务端
1 | func (s *StreamService) Record(stream pb.StreamService_RecordServer) error { |
4.3.2 客户端
1 | func printRecord(client pb.StreamServiceClient, r *pb.StreamRequest) error { |
4.4 双向流RPC
4.4.1 服务端
1 | func (s *StreamService) Route(stream pb.StreamService_RouteServer) error { |
4.4.2 客户端
1 | func printRoute(client pb.StreamServiceClient, r *pb.StreamRequest) error { |
5. TLS 证书认证
5.1 生成证书
5.1.1 私钥
1 | openssl ecparam -genkey -name secp384r1 -out server.key |
5.1.2 自签公钥
1 | openssl req -new -x509 -sha256 -key server.key -out server.pem -days 3650 |
5.2 服务端
1 | type SearchService struct{} |
5.3 客户端
1 | const HOST = ":9001" |
6. 基于 CA 的 TLS 证书认证
6.1 CA
6.1.1 生成CA证书
根证书(root certificate)是属于根证书颁发机构(CA)的公钥证书。可以通过验证CA的签名从而信任CA,任何人都可以得到CA的证书(含公钥),用以验证它所签发的证书。
1 | # 生成Key |
6.1.2 服务端证书
CSR: Cerificate Signing Request,证书请求文件。主要作用是 CA 会利用 CSR 文件进行签名使得攻击者无法伪装或篡改原有证书。
1 | # 生成CSR |
6.1.3 客户端证书
1 | # 生成Key |
6.2 TLS认证代码
6.2.1 服务端认证
1 | type Server struct { |
6.2.2 客户端认证
1 | type Client struct { |
6.3 实现代码
6.3.1 服务端
1 | type SearchService struct{} |
6.3.2 客户端
1 | func main() { |
大致流程:
- Client 通过请求得到 Server 端的证书
- 使用 CA 认证的根证书对 Server 端证书进行可靠性、有效性等校验
- 校验 ServerName 是否有效
- 同样,在设置了
tls.RequireAndVerifyClientCert
模式下,Server 也会使用 CA 认证的根证书对Client的证书进行可靠性、有效性校验。
6.4 补充知识点:ssl/tls 单向认证双向认证
- 单向认证:只有一个对象校验对端的证书合法性。通常client来校验服务器的合法性。那么client需要一个ca.crt,服务器需要server.crt,server.key。
- 双向认证:相互校验,服务器需要校验每个client,client也需要校验服务器。server 需要 server.key、server.crt、ca.crt,client 需要 client.key、client.crt、ca.crt。
7. 拦截器
7.1 Unary and Stream interceptor
- 普通方法:一元拦截器
grpc.UnaryInterceptor
- 流方法:流拦截器
grpc.StreamInterceptor
7.1.1 grpc.UnaryInterceptor
1 | func UnaryInterceptor(i UnaryServerInterceptor) ServerOption { |
7.1.2 grpc.StreamInterceptor
1 | func StreamInterceptor(i StreamServerInterceptor) ServerOptions |
7.2 实现多个拦截器
gRPC本身只能设置一个拦截器,但可以采用go-grpc-middleware
项目来解决问题
1 | import "github.com/grpc-ecosystem/go-grpc-middleware" |
7.3 实现 logging 和 recover 拦截器
7.3.1 logging
1 | func LoggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { |
7.3.2 recover
1 | func RecoveryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { |
7.3.3 完整代码
1 | type SearchService struct{} |
8. 同时提供 HTTP 服务
8.1 服务端
1 | type SearchService struct{} |
8.2 gRPC 客户端
1 | func main() { |
8.3 http/1.1 直接访问
1 | curl -k --cert client.pem --key client.key https://127.0.0.1:9003 |
9. 自定义认证
9.1 自定义认证接口
1 | type PerRPCCredentials interface { |
9.2 服务端
1 | type SearchService struct { |
9.3 客户端
1 | type Auth struct { |
10. gRPC Deadline
10.1 为什么要设置Deadline?
- 未设置 Deadlines 时,将采用默认的 DEADLINE_EXCEEDED(该时间非常大)
- 产生阻塞等待时,会造成大量正在进行的请求被保留,直到这些请求都达到最大超时
- 会导致资源耗尽的风险,也会增加服务的延迟,最坏时可能导致整个进出崩溃
10.2 服务端
1 | type SearchService struct{} |
10.3 客户端
1 | func main() { |