day 6:grpc(下) | 青训营笔记

183 阅读8分钟

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

4 gRPC实例

4.1 RPC和gRPC介绍

RPC(Remote Procedure Call)远程过程调用协议,一种通过网络从远程计算机上请求服务,而不需要了解底层网络技术的协议。RPC它假定某些协议的存在,例如TCP/UDP等,为通信程序之间携带信息数据。在OSI网络七层模型中,RPC跨越了传输层和应用层,RPC使得开发包括网络分布式多程序在内的应用程序更加容易。

过程是什么? 过程就是业务处理、计算任务,更直白的说,就是程序,就是像调用本地方法一样调用远程的过程

RPC采用客户端/服务端的模式,通过request-response消息模式实现。

gRPC 里客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法,使得您能够更容易地创建分布式应用和服务。与许多 RPC 系统类似,gRPC 也是基于以下理念:定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个 gRPC 服务器来处理客户端调用。在客户端拥有一个存根能够像服务端一样的方法。

官方网站:grpc.io/

底层协议:

4.1.1 HTTP2

  • HTTP/1里的header对应HTTP/2里的 HEADERS frame
  • HTTP/1里的payload对应HTTP/2里的 DATA frame

gGRPC把元数据放到HTTP/2 Headers里,请求参数序列化之后放到 DATA frame里

基于HTTP/2 协议的优点

  1. 公开标准

  2. HTTP/2的前身是Google的SPDY ,有经过实践检验

  3. HTTP/2 天然支持物联网、手机、浏览器

  4. 基于HTTP/2 多语言客户端实现容易

    1. 每个流行的编程语言都会有成熟的HTTP/2 Client
    2. HTTP/2 Client是经过充分测试,可靠的
    3. 用Client发送HTTP/2请求的难度远低于用socket发送数据包/解析数据包
  5. HTTP/2支持Stream和流控

  6. 基于HTTP/2 在Gateway/Proxy很容易支持

    1. nginx和envoy都有支持
  7. HTTP/2 安全性有保证

    1. HTTP/2 天然支持SSL,当然gRPC可以跑在clear text协议(即不加密)上。
    2. 很多私有协议的rpc可能自己包装了一层TLS支持,使用起来也非常复杂。开发者是否有足够的安全知识?使用者是否配置对了?运维者是否能正确理解?
    3. HTTP/2 在公有网络上的传输上有保证。比如这个CRIME攻击,私有协议很难保证没有这样子的漏洞。
  8. HTTP/2 鉴权成熟

    1. 从HTTP/1发展起来的鉴权系统已经很成熟了,可以无缝用在HTTP/2上
    2. 可以从前端到后端完全打通的鉴权,不需要做任何转换适配

基于HTTP/2 协议的缺点

  • rpc的元数据的传输不够高效
  • 尽管HPAC可以压缩HTTP Header,但是对于rpc来说,确定一个函数调用,可以简化为一个int,只要两端去协商过一次,后面直接查表就可以了,不需要像HPAC那样编码解码。 可以考虑专门对gRPC做一个优化过的HTTP/2解析器,减少一些通用的处理,感觉可以提升性能。
  • HTTP/2 里一次gRPC调用需要解码两次
  • 一次是HEADERS frame,一次是DATA frame。
  • HTTP/2 标准本身是只有一个TCP连接,但是实际在gRPC里是会有多个TCP连接,使用时需要注意。

gRPC选择基于HTTP/2,那么它的性能肯定不会是最顶尖的。但是对于rpc来说中庸的qps可以接受,通用和兼容性才是最重要的事情。

gRPC目前是k8s生态里的事实标准,而Kubernetes又是容器编排的事实标准。gRPC已经广泛应用于Istio体系,包括:

  • Envoy与Pilot(现在叫istiod)间的XDS协议
  • mixer的handler扩展协议
  • MCP(控制面的配置分发协议)

在Cloud Native的潮流下,开放互通的需求必然会产生基于HTTP/2的RPC。

4.2 实例

一个简单的grpc远程调用

4.2.1 服务端

// 这个就是protobuf的中间文件

// 指定的当前proto语法的版本,有2和3
syntax = "proto3";
option go_package="../service";

// 指定等会文件生成出来的package
package service;

// 定义request model
message ProductRequest{
        int32 prod_id = 1; // 1代表顺序
}

// 定义response model
message ProductResponse{
        int32 prod_stock = 1; // 1代表顺序
}

// 定义服务主体
service ProdService{
    // 定义方法
    rpc GetProductStock(ProductRequest) returns(ProductResponse);
}

生成:

protoc  --go_out=./ --go-grpc_out==plugins=grpc:./  .\product.proto

服务端代码

  1. 启动一个rpc服务器
  2. 将该服务注册到该rpc服务器中
  3. 开启一个网络监听器
  4. 使rpc服务器服务该监听接口
func main() {
   rpcServer := grpc.NewServer()

   service.RegisterProdServiceServer(rpcServer, service.ProductServiece)

   // 监听
   listener, err := net.Listen("tcp", ":8002")
   if err != nil {
      log.Fatal("启动监听失败!", err)
   }
   err = rpcServer.Serve(listener)
   if err != nil {
      log.Fatal("启动服务失败!", err)
   }
   fmt.Println("启动服务成功!")
}

4.2.2 客户端

新建client目录,把上述生成的product.pb.go 以及product_rpc.pb.go 复制到service目录下。

func main() {
   // 1. 新建连接,端口是服务端开放的8002端口
   // 没有证书会报错
   conn, err := grpc.Dial(":8002", grpc.WithTransportCredentials(insecure.NewCredentials()))
   if err != nil {
      log.Fatal(err)
   }

   // 退出时关闭链接
   defer func(conn *grpc.ClientConn) {
      err := conn.Close()
      if err != nil {

      }
   }(conn)

   // 2. 调用Product.pb.go中的NewProdServiceClient方法
   productServiceClient := service.NewProdServiceClient(conn)

   // 3. 直接像调用本地方法一样调用GetProductStock方法
   resp, err := productServiceClient.GetProductStock(context.Background(), &service.ProductRequest{ProdId: 233})
   if err != nil {
      log.Fatal("调用gRPC方法错误: ", err)
   }

   fmt.Println("调用gRPC方法成功,ProdStock = ", resp.ProdStock)
}

4.2.3 小结

对于服务端:

  1. 首先要编写protobuf,请求参数以及返回值因为都要远程传输,因此都需要加密,需要编写protobuf,同时在protobuf中要定义好需要的服务,以及每个服务中的函数。
  2. 生成对应的go文件,不必多说
  3. 实现服务中的接口,并且实例化服务(注意:示例化服务对象是在实现接口的文件中)
  4. 在服务端注册绑定服务。

对于客户端:

  1. 由于protobuf生成的go文件既包含客户端又包括服务端的接口,因此客户端的接口定义直接用服务端的接口文件就可以。

5 认证

5.1 认证介绍

具体说明:

  • Client 能互联网下载CA的公钥,用于验证Server身份
  • Server 通过加密算法生成一对密钥,将公钥发给CA认证机构,做数字证书
  • CA 通过自己的私钥加密 Server 公钥并加上自己的数字签名后,将生成的数字证书发给Server
  • Client 与 Server 通TCP的三次握手建立连接
  • Client 发出HTTP的报文请求,并和Server协商使用哪种算法加密
  • Server 响应报文并将公钥(CA证书)发送给客户端
  • Client 能通过CA的公钥解密CA证书,得到Server的公钥和数字签名,Client 通过散列函数算出Server公钥的数字签名与得到的数字签名对比,来验证Server身份,验证成功并发送数据请求
  • Server 处理请求,并根据Client请求的资源类型,来访问相关资源做出响应报文,并记录日志
  • Client 与 Server 通过TCP的四次挥手断开连接,通信完成

1. 生成自签证书

生产环境可以购买证书或者使用一些平台发放的免费证书

  • 安装openssl

网站下载:slproweb.com/products/Wi…

(mac电脑 自行搜索安装)

  • 生成私钥文件


## 需要输入密码

openssl genrsa -des3 -out ca.key 2048

  • 创建证书请求


openssl req -new -key ca.key -out ca.csr

  • 生成ca.crt


openssl x509 -req -days 365 -in ca.csr -signkey ca.key -out ca.crt

找到openssl.cnf 文件

5.2 生成自签

1. 生成自签证书

生产环境可以购买证书或者使用一些平台发放的免费证书

  • 安装openssl

网站下载:slproweb.com/products/Wi…

(mac电脑 自行搜索安装)

  • 生成私钥文件
  ## 需要输入密码
  openssl genrsa -des3 -out ca.key 2048
  • 创建证书请求
  openssl req -new -key ca.key -out ca.csr
  • 生成ca.crt
  openssl x509 -req -days 365 -in ca.csr -signkey ca.key -out ca.crt

找到openssl.cnf 文件

  1. 打开copy_extensions = copy
  2. 打开 req_extensions = v3_req
  3. 找到[ v3_req ],添加 subjectAltName = @alt_names
  4. 添加新的标签 [ alt_names ] , 和标签字段
   [ alt_names ]
   DNS.1 = *.mszlu.com
  1. 生成证书私钥server.key

openssl genpkey -algorithm RSA -out server.key

  1. 通过私钥server.key生成证书请求文件server.csr

openssl req -new -nodes -key server.key -out server.csr -days 3650 -config ./openssl.cnf -extensions v3_req

  1. 生成SAN证书

openssl x509 -req -days 365 -in server.csr -out server.pem -CA ca.crt -CAkey ca.key -CAcreateserial -extfile ./openssl.cnf -extensions v3_req

  • key: 服务器上的私钥文件,用于对发送给客户端数据的加密,以及对从客户端接收到数据的解密。
  • csr: 证书签名请求文件,用于提交给证书颁发机构(CA)对证书签名。
  • crt: 由证书颁发机构(CA)签名后的证书,或者是开发者自签名的证书,包含证书持有人的信息,持有人的公钥,以及签署者的签名等信息。
  • pem: 是基于Base64编码的证书格式,扩展名包括PEM、CRT和CER。

什么是 SAN?

SAN(Subject Alternative Name)是 SSL 标准 x509 中定义的一个扩展。使用了 SAN 字段的 SSL 证书,可以扩展此证书支持的域名,使得一个证书可以支持多个不同域名的解析。