Go进阶 - GRPC使用 | 青训营

168 阅读5分钟

RPC

(Remootee Procceduure Calls) 远程过程调用

是一种协议,用来屏蔽分布式计算机中的各种调用细节,使得可以像是本地调用一样直接调用一个远程函数

为什么需要RPC?

一般的:

客户端 与 服务端 沟通的过程

  1. 客户端发送数据(以字节流的方式)
  2. 服务端接受并解析。把结果返回给客户

RPC:

  1. RPC就是将上述过程封装,使其操作更加优化
  2. 使用一些大家都认可的协议,使其规范化
  3. 做成一些框架,直接或间接产生利益

GRPC

GRPC就是一个高性能的、开源的、通用的RPC框架

GRPC官网

中文文档

grpc使用了Protocol Buffss.这是谷歌开源的一套成熟的数据结构序列化机制。

  • 序列化:把数据结构或对象转成二进制串的过程
  • 反序列化:将在序列化过程中所产生的二进制串转成数据结构或对象的过程

安装Protobuf

1. 安装protocol buffers

下载地址

下载win安装包、解压到本地、添加环境变量。

测试:cmd输入protoc

2. 安装gRPC核心库

go get -u google.goland.org/grpc

3. 安装go protocol buffers的插件protoc-gen-go

go install google.golang.org/protobuf/cmd/protoc-gen-go@latest`
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest`

Proto文件

运行

protoc --go_out=. hello.proto
protoc --go-grpc_out=. hello.proto

文件编写

syntax = "proto3";

option go_package = ".;service";

service SayHello{
  rpc SayHello(HelloRequest) returns (HelloResponse) {}
}

message HelloRequest{
  string requestName = 1;
}
message HelloResponse{
  string responseMsg = 1;
}
  1. message 数据格式的定义,类似结构体
  2. 字段规则
    • required 必填字段 不设置会导致编码异常 protobuf3被删去
    • optional 可选字段 默认
    • repeated 可重复字段 go中会被定义为切片
  3. 消息号 在消息体的定义中,每个字段都必须有一个唯一的标识号

消息体是可以嵌套使用的

message PersonInfo{
    message Person{
        string name = 1;
        int32 height = 2;
        repeated int32 weight = 3;
    }
    repeated Person info = 1;
}
message PersonMessage{
    PersonInfo.Person info = 1;
}
  1. 服务定义 相当于函数
service SercjService{
    # rpc 服务函数名(参数) 返回 (返回参数){}
    rpc Search(SearchResquest) return (SearchResponse) {}
}

服务端编写

package main

import (
	"context"
	pb "douyin/server/proto"
	"google.golang.org/grpc"
	"net"
)

type server struct {
	pb.UnimplementedSayHelloServer
}

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

func main() {
	//	开启端口
	listen, _ := net.Listen("tcp", ":9090")
	//创建GRPC服务
	grpcServer := grpc.NewServer()
	//	再grpc服务端注册自己编写的服务
	pb.RegisterSayHelloServer(grpcServer, &server{})

	//	启动服务
	_ = grpcServer.Serve(listen)
}

客户端编写

package main

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

func main() {
	//连接到server端
    // 此处禁用安全认证
	conn, err := grpc.Dial("127.0.0.1:9090", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()
	//建立连接
	client := pb.NewSayHelloClient(conn)
	//执行rpc调用(这个方法是在服务端实现并返回结果
	resp, _ := client.SayHello(context.Background(), &pb.HelloRequest{RequestName: "LXJ "})
	fmt.Println(resp.GetResponseMsg())
}

认证、安全传输

默认使用protobuf作为传输协议 当然也可以自定义其他协议。

SSL/TLS认证方式

环境配置

安装包下载

配置环境变量

测试openssl

生成证书
# 1、生成私钥
openssl genrsa -out server.key 2048

# 2、生成证书
openssl req -new -x509 -key server.key -out server.crt -days 36500

# 3、生成csr
openssl req -new -key server.key -out server.csr
1. 复制bin中的openssl.cnf到key
2. 找到[CA_default] 取消注释 copy_extensions = copy
3. 找到[req] 取消注释 req_extensions = v3_req
4. 找到[v3_req]  添加 subjectAltName = @alt_names
5. 添加新的标签[alt_names] 和 标签字段 DNS.1 = *.Gangnam.com
# 生成证书私钥test.key
openssl genpkey -algorithm RSA -out test.key

# 通过私钥test.key生成证书请求文件test.csr
openssl req -new -nodes -key test.key -out test.csr -days 3650 -subj "/C=cn/OU=myorg/O=mycomp/CN=myname" -config ./openssl.cfg -extensions v3_req

# 生成SAN证书 pem
openssl x509 -req -days 365 -in test.csr -out test.pem -CA server.crt -CAkey server.key -CAcreateserial -extfile ./openssl.cfg -extensions v3_req
server端代码
package main

import (
	"context"
	pb "douyin/server/proto"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"net"
)

type server struct {
	pb.UnimplementedSayHelloServer
}

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

func main() {
	//TLS认证
	creds, _ := credentials.NewServerTLSFromFile("D:\\GoProjects\\douyin\\key\\test.pem",
		"D:\\GoProjects\\douyin\\key\\test.key")
	//	开启端口
	listen, _ := net.Listen("tcp", ":9090")
	//创建GRPC服务
	grpcServer := grpc.NewServer(grpc.Creds(creds))
	//	再grpc服务端注册自己编写的服务
	pb.RegisterSayHelloServer(grpcServer, &server{})

	//	启动服务
	_ = grpcServer.Serve(listen)
}

client端代码
package main

import (
	"context"
	pb "douyin/server/proto"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"log"
)

func main() {
	file, _ := credentials.NewClientTLSFromFile("D:\\GoProjects\\douyin\\key\\test.pem",
		"*.Gangnam.com")
	//连接到server端
	conn, err := grpc.Dial("127.0.0.1:9090", grpc.WithTransportCredentials(file))
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()
	//建立连接
	client := pb.NewSayHelloClient(conn)
	//执行rpc调用(这个方法是在服务端实现并返回结果
	resp, _ := client.SayHello(context.Background(), &pb.HelloRequest{RequestName: "LXJ "})
	fmt.Println(resp.GetResponseMsg())
}

Token认证方式

type PerRPCCredentials interface {
	// GetRequestMetadata gets the current request metadata, refreshing tokens
	// if required. This should be called by the transport layer on each
	// request, and the data should be populated in headers or other
	// context. If a status code is returned, it will be used as the status for
	// the RPC (restricted to an allowable set of codes as defined by gRFC
	// A54). uri is the URI of the entry point for the request.  When supported
	// by the underlying implementation, ctx can be used for timeout and
	// cancellation. Additionally, RequestInfo data will be available via ctx
	// to this call.  TODO(zhaoq): Define the set of the qualified keys instead
	// of leaving it as an arbitrary string.
	GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error)
	// RequireTransportSecurity indicates whether the credentials requires
	// transport security.
	RequireTransportSecurity() bool
}

第一个方法的作用是获取元数据信息,也就是客户端提供(key valued)对, context用于控制超时和取消,uri是请求入口处的uri

第二个方法的作用是是否需要基于TLS认证进行安全传输,如果返回值是true,则必须加上TLS验证,false则不需要

Client端代码
package main

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

type ClientTokenAuth struct {
}

func (c ClientTokenAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
	return map[string]string{
		"appId":  "lxj",
		"appKey": "123",
	}, nil
}

func (c ClientTokenAuth) RequireTransportSecurity() bool {
	return false // 不开启安全认证
}

func main() {

	//file, _ := credentials.NewClientTLSFromFile("D:\\GoProjects\\douyin\\key\\test.pem",
	//	"*.Gangnam.com")
	//连接到server端
	var opts []grpc.DialOption
	opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) // 无安全认证 上面是false
	opts = append(opts, grpc.WithPerRPCCredentials(new(ClientTokenAuth)))

	conn, err := grpc.Dial("127.0.0.1:9090", opts...)
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()
	//建立连接
	client := pb.NewSayHelloClient(conn)
	//执行rpc调用(这个方法是在服务端实现并返回结果
	resp, _ := client.SayHello(context.Background(), &pb.HelloRequest{RequestName: "LXJ "})
	fmt.Println(resp.GetResponseMsg())
}

Server端代码
package main

import (
	"context"
	pb "douyin/server/proto"
	"errors"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"google.golang.org/grpc/metadata"
	"net"
)

type server struct {
	pb.UnimplementedSayHelloServer
}

// SayHello 业务
func (s *server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) {
	//获取元数据信息
	md, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		return nil, errors.New("未传输Token")
	}
	var appId string
	var appKey string
	if v, ok := md["appid"]; ok {
		appId = v[0]
	}
	if v, ok := md["appkey"]; ok {
		appKey = v[0]
	}

	//校验
	if appId != "lxj" || appKey != "123" {
		return nil, errors.New("Token 错误")
	}

	fmt.Println("hello " + req.RequestName)
	return &pb.HelloResponse{ResponseMsg: "Hello" + req.RequestName}, nil
}

func main() {
	//TLS认证
	//creds, _ := credentials.NewServerTLSFromFile("D:\\GoProjects\\douyin\\key\\test.pem",
	//	"D:\\GoProjects\\douyin\\key\\test.key")
	//	开启端口
	listen, _ := net.Listen("tcp", ":9090")
	//创建GRPC服务
	grpcServer := grpc.NewServer(grpc.Creds(insecure.NewCredentials()))
	//	再grpc服务端注册自己编写的服务
	pb.RegisterSayHelloServer(grpcServer, &server{})

	//	启动服务
	_ = grpcServer.Serve(listen)
}