grpc介绍可以参考官网。无论是rpc还是grpc,可以这样理解,都知道过去使用的单单体架构,而在2011年5月威尼斯的一个软件架构会议上提出了微服务架构,围绕业务功能进行组织(organized around business capability),不再是以前的纵向切分,而改为按业务功能横向划分,一个微服务最好由一个小团队针对一个业务单元来构建。
所以服务和服务之间存在调用关系,服务中之间使用http调用,发现性能太差,使用了一种新的通信方式,这个通信方式称为rpc客户端和服务端沟通的过程如下:
grpc是基于“服务定义”的思想,简单的来说,就是通过某种方式描述一个服务,这种描述与语言无关,在这个“服务定义”的过程中,描述了提供的服务名是什么,有哪些方法可以被调用,这些方法有哪些参数,返回参数是什么
grpc调用方称之为client
,服务方为server
。也就是说定义好了这些方法和服务之后,grpc会屏蔽底层的细节,client只需要调用定义好的防范,就能返回预期的结果,对server段来说,还需要实现定义的方法。
这里结合grpc-study实例介绍,按照如下目录创建工程和文件,仅有hello.proto
中存在代码
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
中的代码。
所以关于proto的总结如下:
message
message:protobul定义了一个消息类型是通过关键字message
字段指定,消息就是要传输的数据格式的定义,message
类似go中的struct,在消息中承载的数据分别对应的是一个字段,每个字段都有一个名字和一种类型。每个字段有一些规则:
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
的约束之后,就可以遵循该声明实现服务端的接口调用,服务端实现如下:
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
的约束之后,就可以遵循该声明实现服务端的接口调用,服务端实现如下:
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函数
这里注意到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认证。
此处说到的认证,不是用户的身份认证,而是指多个server和多个client之间,如何识别对方是谁,并且可以安全的进行数据传输,可以通过如下的几种方式:
首先通过openssl生成证书和私钥,关于认证方式参考:https://www.kuangstudy.com/bbs/1604044550878703617
生成证书步骤如下,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这个域名去访问才能同意,相当于是一个安全性的校验,客户端发起请求的时候需要携带该域名,这就是安全机制校验