gRPC简介

grpc简介

grpc介绍可以参考官网。无论是rpc还是grpc,可以这样理解,都知道过去使用的单单体架构,而在2011年5月威尼斯的一个软件架构会议上提出了微服务架构,围绕业务功能进行组织(organized around business capability),不再是以前的纵向切分,而改为按业务功能横向划分,一个微服务最好由一个小团队针对一个业务单元来构建。
gRPC简介_第1张图片
所以服务和服务之间存在调用关系,服务中之间使用http调用,发现性能太差,使用了一种新的通信方式,这个通信方式称为rpc客户端和服务端沟通的过程如下:

  1. 客户端发送数据(以字节流的方式)编码
  2. 服务端接受并解析,根据约定执行什么然后把结果返回给客户,解码
    那RPC的发展,RPC远程调用总体而言:
  3. rpc就是上述的过程封装下,时期操作更加优化
  4. 使用一些大家都认可的协议,使其规范化
  5. 做成一些框架,直接或间接的产生利益,这个就是grpc

grpc是基于“服务定义”的思想,简单的来说,就是通过某种方式描述一个服务,这种描述与语言无关,在这个“服务定义”的过程中,描述了提供的服务名是什么,有哪些方法可以被调用,这些方法有哪些参数,返回参数是什么

grpc调用方称之为client,服务方为server。也就是说定义好了这些方法和服务之后,grpc会屏蔽底层的细节,client只需要调用定义好的防范,就能返回预期的结果,对server段来说,还需要实现定义的方法。

proto创建

这里结合grpc-study实例介绍,按照如下目录创建工程和文件,仅有hello.proto中存在代码
gRPC简介_第2张图片

proto是一种语法,类似go中约定变量定义var name string一样,python中variable=1一样的语法规定。proto中也有自己的语法,比如使用message,使用service关键字, hello.proto代码如下:

syntax = "proto3";


// 这部分内容是关于最后生成的go文件的是放在哪个目录哪个包中的, "."代表在当前目录生成,service代表了生成go文件的包名是service
option go_package=".;service";


// 这里定义一个服务,这个份服务需要有一个方法,这个方法可以接受客户端的参数请求,再返回服务端的响应,这个方法是以rpc,就像func标记一个函数一行,这个rpc标记的是一个rpc的方法
// 其实可以看出,定义了一个service,称之为SayHello,这个服务有一个rpc的方法,名为SayHello
// 这个方法会发送一个HelloRequest,然后返回一个 HelloResponse
service SayHello {
  rpc SayHello(HelloRequest) returns (HelloResponse) {}
}

// 既然是约定的一些规则,这里需要使用message关键字顶一个结构体,这个message类似go中的struct
// 这里比较特殊的是变量后的赋值,这里不是赋值,而是在定义这个变量在这个message中的位置,可以理解为显示的定义了该参数在message数组或者结构体的索引位置
// 最终生成代码的时候,就会根据标识号来生成对应的位置,如requestName在第一个位置就会生成在第一个
message HelloRequest {
  string requestName = 1;
}

message HelloResponse {
  string response = 1;
}

// 以上是一个约束规则,就是一个语法规定

terminal切换到 grpc-service下

grpc-study % cd hello-server\proto

切换该目录后,在和*.proto同级目录执行如下命令

protoc --go_out=. hello.proto  // 使用go_out生成需要的go语言的文件
protoc --go-grpc_out=. hello.proto // 使用go_out生成需要的rpc语言的文件

执行完成后,在proto下新增两个文件如下所示
gRPC简介_第3张图片
对于远程过程调用,就使用的grpc中的代码。gRPC简介_第4张图片
所以关于proto的总结如下:

  1. 创建工程项目,设置文件分层
  2. 编写proto文件
  3. (切换目录)使用go-out生成pb.go,使用go-grpc_out命令生成gprc.pb.go文件

proto文件介绍

message

message:protobul定义了一个消息类型是通过关键字message字段指定,消息就是要传输的数据格式的定义,message类似go中的struct,在消息中承载的数据分别对应的是一个字段,每个字段都有一个名字和一种类型。每个字段有一些规则:

  1. required:消息体中的必填字段,不设置时会导致编码异常,在protobf2中使用,在protobuf3中删除
  2. optional:消息体中的可选字段,protobuf3中取消了required和optional等说明关键字,默认都是optional
  3. repeated:消息中的可重复字段,重复的值的顺序会被保留在go中重复的会被定义为切片,使用repeadted可以理解为类似数组
    每个字段都必须有一个唯一的标识好,一般不建议一个消息号中超过15,当然消息也可以嵌套定义
message PersonInfomation {
    message Person {
        string name = 1;
        int32 height = 2;
        repeated int32 age = 3;
    }
   repeated Person info = 1;
}

如果要在父消息类型的外部重用这个消息类型,需要PersonInformation.Person的形式使用它,如:

message PersonMessage {
    PersonInformation.Person info  = 1;
}

既然是用于微服务,如果想要将消息类型用在RPC的系统中,可以在**.proto文件中定义一个RPC服务接口,protocol buffer编译器将会更具所选择的不同语言生成服务接口代码和存根

service SearchService {
    // rpc 服务函数名 (参数名)返回 (返回参数)
    rpc Search(SearchRquest) returns (SearchResponse)
}

这样就定义了了RCP的服务,该方法接受SearchRequest请求和SearchResponse响应.所以这个*.proto文件中的格式只是一个约定规定,就像go的函数使用func声明是一个函数,*.proto中也有自己的一些约束。有了这些约束之后,客户端和服务端都是去调用这些接口的,有了这些约束之后,grpc可以帮助我们去寻路,用什么方法写的,就调用什么方法就可以。

服务端实现

有了上面的*.proto的约束之后,就可以遵循该声明实现服务端的接口调用,服务端实现如下:

  • 创建gRPC Server对象,我们可以理解他是Server端的抽象对象
  • 将Server(包含需要被调用的服务端接口)注册到gRPC Server的内部注册中心,这样可以在服务端接受请求时,通过内部的服务发现,发现该服务器接口并转接进行逻辑处理
  • 创建Listen,监听TCP端口
  • gRPC Server开始lis.Accept,直到Stop
    上面定义了server中的方法,这里来具体实现,在serverice下的main.go中
package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	pb "grpc-study/hello-server/proto"
	"net"
)

// hello server
type server struct {
	pb.UnimplementedSayHelloServer
}

func (s *server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) {
    fmt.Printf("hello" + req.RequestName)
	return &pb.HelloResponse{Response: "hello" + req.RequestName}, nil
}

func main() {
	//	开启端口监听
	listen, _ := net.Listen("tcp", "9090")

	//	把这个服务暴露其他服务使用
	grpcServer := grpc.NewServer()
	//在grpc服务端中去注册自己编写的服务
	pb.RegisterSayHelloServer(grpcServer, &server{})

	//创建服务完成后,启动该服务
	err := grpcServer.Serve(listen)
	if err != nil {
		fmt.Printf("failed to server: %", err)
		return
	}
}

// 如何实现具体逻辑呢,在grpc.pb.go中找到源码,实现对应的方法逻辑就行了

客户端实现

有了上面的*.proto的约束之后,就可以遵循该声明实现服务端的接口调用,服务端实现如下:

  • 创建于给定目标(服务端)的连接交互
  • 创建server的客户端对象
  • 发送RPC对象,等待同步响应,得到回调后返回响应结果
  • 输出响应结果
package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	pb "grpc-study/hello-server/proto"
	"log"
)

func main() {
	// 连接打server端,次数禁用安全传输,没有加密和验证
	conn, err := grpc.Dial("127.0.0.1:9090", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}


	defer conn.Close()

	//建立连接
	client := pb.NewSayHelloClient(conn)

	// 执行rpc调用(这个方法时在服务器端来实现并返回结果)
	resp, _ := client.SayHello(context.Background(), &pb.HelloRequest{RequestName: "zhiyu"})
	fmt.Println(resp.GetResponse())
}

先执行server中的main.go,再执行执行client中的main.go,检查执行结果,未来只要能找到"127.0.0.:9090"这个地址就可以调用到服务并可以拿到返回结果。goland IDE界面分屏执行两个main.go函数
gRPC简介_第5张图片

这里注意到client/main.go中

func main() {
	// 连接打server端,此处禁用安全传输,没有加密和验证
	conn, err := grpc.Dial("127.0.0.1:9090", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}

只要能访问到该地址信息,就可以拿到返回结果,因此这种是不安全的,所以需要进行加密+安全传输。
经过前文的学习,我们直到gRPC是一个C/S模型,需要开户客户端和服务端,客户端和服务端需要达成协议,使用某一个确认的传输协议来传输数据,gPRC通过默认使用protobuf作为传输协议,当然也可以使用其他的自定义协议创建,如你可以使用xml。接着再看grpc官网中的这个示意图,就可以如下表示:
gRPC简介_第6张图片

客户端和服务端通信之前,客户端如何知道自己的数据发送给哪个明确的服务端呢?反过来,服务端是如何知道将数据返回给谁呢,这个就需要gRPC认证。
此处说到的认证,不是用户的身份认证,而是指多个server和多个client之间,如何识别对方是谁,并且可以安全的进行数据传输,可以通过如下的几种方式:

  • SSL/TLS认证方式(采用http2协议)
  • 基于Token的认证方式(基于安全连接)
  • 不采用任何措施的连接,这是不安全的连接(默认采用http1)
  • 自定义的身份认证
    客户端和服务端之间调用,我们可以通过加入证书的方式,实现调用的安全性
    TLS(Transport Layer Security,安全传输层),TLS是建立在传输层TCP协议之上的协议,服务于应用层,它的前身是SSL(Secure Socket Layeer,
    安全套接字层),它实现了将应用层的报文进行加密后再交由TCP进行传输的功能。
    TLS协议主要解决如下三个网络安全问题。
  1. 保密(message privacy),保密通过加密encryption实现,所有信息都加密传输,第三方无法嗅探;
  2. 完整性(message integrity),通过MAC校验机制,一旦被篡改,通值双方会立刻发现;
  3. 认证(mutual authentication),双方认证,双方都可以配备证书,防止身份被冒充
    生存环境可以购买证书或者使用一些平台发放的证书
  • key:服务器上的私钥文件,用于对发送给客户端数据的加密,以及对从客户端接受到数据的解密
  • csr:证书签名请求文件,用于提交给证书颁发机构(CA)对证书签名。
  • crt:由证书颁发机构(CA)签名后的证书,或者是开发者自签名的证书,包含证书持有人的信息,持有人的公钥,以及签署者的签名等信息
  • pem:是基于Base64编码的证书格式,扩展名包括PEM、CRT和CER

SSL/TLS认证方式

首先通过openssl生成证书和私钥,关于认证方式参考:https://www.kuangstudy.com/bbs/1604044550878703617

  1. 官网下载:https://www.openssl.org/source/ ,便捷版安装包http://slproweb.com/products/Win320penSSL.html
  2. 我们使用便捷版安装包,一直下一步即可
  3. 配置环境变量 D:\Environment\OpenSSL-Win64\bin
  4. 命令行测试 openssl

生成证书步骤如下,mac安装ssl参考:https://www.freesion.com/article/7165613046/

#1、生成私钥
openssl genrsa -out server.key 2048
#2、生成证书全部回车即可,可以不填
openss1 req -new -x509 -key server.key -out server.crt -days 36500
#国家名称
Country Name (2 letter code) [AU] :CN
#省名称
State or Province Name (full name) [Some-State]:GuangDong
#城市名称
Locality Name (eg, city) []:Meizhou
#公司组织名称
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Xuexiangbar
#部门名称
Organizational Unit Name (eg, section) []:go
#服务器or网站名称
Common Name
# 邮件
(e.g. server FQDN or YOUR name) []:kuangstudy
Email Address []:24736743@qq.com

# 3、生存csr
openssl req -new -key server.key -out server.cs
#更改openssl.cnf (Linux 是openssl.cfg)
#1)复制一份你安装的openssl的bin目录里面的openssl.cnf 文件到你项目所在的目录
#2)找到[CA_default],打开 copy_extensions=copy(就是把前面的#去掉)
#3)找到[req],打开 req_extensions=v3_req # Thhe extensions to add to a certificate request
#4)找到[v3_req],添加 subjectAltName=@alt_namnes
#5)添加新的标签[alt_names],和标签字段
DNS.1 = *.zhiyu.com

这个就是对应未来通过zhiyu.com这个域名去访问才能同意,相当于是一个安全性的校验,客户端发起请求的时候需要携带该域名,这就是安全机制校验

你可能感兴趣的:(go,java,微服务,分布式)