gRPC

1. gRPC讲解

微服务云架构示意图

我们回顾一下微服务,微服务架构将应用构建为一系列小型,松散耦合且可独立部署的服务,每项服务均旨在执行特定业务功能,可通过定义明确的API与其他服务进行通信

  • 微服务之间通信的方式为RPC
  • 在Golang的体系中,gRPC是使用最广的RPC框架。
  • gRPC基于HTTP/2.0,采用Protobuf作为数据序列化协议。
  • gRPC具有语言中立的特点
  • go-zero框架对GRPC已经支持,可以轻松的开发gRPC应用

1.1 proto文件

在使用Protobuf之前,我们需要编写proto格式的文件

//指定正在使用proto3语法
syntax = "proto3";
//消息类型 用于传输的数据格式的定义
//每个字段必须指定字段编号: 1到536870911之间的一个数字,需要唯一,19000到19999是保留编号不能使用
message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 results_per_page = 3;
}
message SearchResponse{}
//可以导入其他proto文件 使用其他proto文件定义的消息
import "myproject/other_protos.proto";
//包定义 防止消息类型之间的名称冲突
package foo.bar
//定义RPC服务接口 
service SearchService {
  rpc Search(SearchRequest) returns (SearchResponse);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

2. gRPC案例

rpc分为server端client端

  • server端提供rpc服务,为服务提供方
  • client端为服务消费方
  • RPC框架的目的是让远程调用变得和本地调用一样方便

2.1 server端

  1. 先生成proto文件

    goctl rpc -o greet.proto
    
    1
    //语法版本
    syntax = "proto3";
    //包定义 对go无效
    package greet;
    //生成go文件的包名
    option go_package="./greet";
    //消息体 需要传输的数据结构
    message Request {
      string ping = 1;
    }
    //消息体 需要传输的数据结构
    message Response {
      string pong = 1;
    }
    //服务名称以及服务定义
    service Greet {
      rpc Ping(Request) returns(Response);
    }
    
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
  2. 生成gRPC服务端

    $ goctl rpc protoc greet.proto --go_out=./grpc-server  --go-grpc_out=./grpc-server  --zrpc_out=./grpc-server
    
    1
    • go_out: proto生成的go代码所在的目录,proto本身的命令参数
    • go-grpc_out:proto生成的grpc代码所在的目录,proto本身的命令参数,和go_out必须同一个目录
    • zrpc_out: goctl rpc自带的命令,go-zero生成的代码所在的目录
  3. 拉取依赖

    # cd grpc-server
    # go mod tidy
    
    1
    2
  4. 编写代码逻辑

    func (l *PingLogic) Ping(in *greet.Request) (*greet.Response, error) {
    	// todo: add your logic here and delete this line
    
    	return &greet.Response{
    		Pong: "pong",
    	}, nil
    }
    
    1
    2
    3
    4
    5
    6
    7
  5. 修改配置文件,去掉etcd配置,采用直连模式

    Name: greet.rpc
    ListenOn: 0.0.0.0:8080
    #Etcd:
    #  Hosts:
    #  - 127.0.0.1:2379
    #  Key: greet.rpc
    
    1
    2
    3
    4
    5
    6
  6. 启动服务

观察生成的代码:

s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
      // 向grpc注册服务
		greet.RegisterGreetServer(grpcServer, server.NewGreetServer(ctx))

		if c.Mode == service.DevMode || c.Mode == service.TestMode {
            //注册反射服务,在gRPC中,反射是一种机制,允许客户端在不知道服务定义(即.proto文件)的情况下查询服务端上的gRPC服务信息。
			reflection.Register(grpcServer)
		}
	})
	defer s.Stop()
1
2
3
4
5
6
7
8
9
10

2.2 grpcurl

上述我们注册了反射服务,可以使用grpcurl来进行测试

2.2.1 安装

https://github.com/fullstorydev/grpcurl/releases 下载。

下载完成后,配置环境变量或者直接放在GOPATH的bin目录下即可。

image-20240926191922091

2.2.2 基本使用

Name: greet.rpc
ListenOn: 0.0.0.0:8080
Mode: dev #开发模式 用于反射服务可用
1
2
3
# 获取grpc服务列表 -plaintext选项代表使用http连接
# grpcurl -plaintext localhost:8080 list
greet.Greet
grpc.health.v1.Health
grpc.reflection.v1.ServerReflection
grpc.reflection.v1alpha.ServerReflection
1
2
3
4
5
6
# 获取grpc服务的方法
# grpcurl -plaintext localhost:8080 list greet.Greet
greet.Greet.Ping
1
2
3
# 获取服务细节 或者 方法细节
# grpcurl -plaintext localhost:8080 describe
greet.Greet is a service:
service Greet {
  rpc Ping ( .greet.Request ) returns ( .greet.Response );
}
# grpcurl -plaintext localhost:8080 describe greet.Greet
greet.Greet is a service:
service Greet {
  rpc Ping ( .greet.Request ) returns ( .greet.Response );
}

1
2
3
4
5
6
7
8
9
10
11
12
# 获取类型信息
# grpcurl -plaintext localhost:8080 describe greet.Request
greet.Request is a message:
message Request {
  string ping = 1;
}
1
2
3
4
5
6
# 调用方法
# grpcurl -d {\"ping\":\"ping\"} -plaintext  localhost:8080 greet.Greet/Ping
# 或者使用 -d @  这种是流式输入,windows用先回车 crtl+z结束输入 其他一般用 先回车 crtl+d结束输入
# grpcurl -d @ -plaintext  localhost:8080 greet.Greet/Ping
{"ping":"ping"}
1
2
3
4
5

经过测试,我们发现grpc服务可以正常使用

2.4 client端

  1. 创建grpc-client目录,在目录下,新建etc目录和client.go

  2. client.go内容

    package main
    
    import (
    	"context"
    	"github.com/zeromicro/go-zero/core/conf"
    	"github.com/zeromicro/go-zero/zrpc"
    	"gozero-learn/grpc-server/greet"
    	"log"
    )
    
    func main() {
    	var clientConf zrpc.RpcClientConf
    	conf.MustLoad("grpc-client/etc/client.yaml", &clientConf)
    	conn := zrpc.MustNewClient(clientConf)
    	client := greet.NewGreetClient(conn.Conn())
    	resp, err := client.Ping(context.Background(), &greet.Request{Ping: "ping"})
    	if err != nil {
    		log.Fatal(err)
    		return
    	}
    
    	log.Println(resp)
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
  3. client.yaml配置文件内容:

    Target: 127.0.0.1:8080
    
    1
  4. 运行进行测试

2.4.1 原生支持

func main() {
	var clientConf zrpc.RpcClientConf
	conf.MustLoad("grpc-client/etc/client.yaml", &clientConf)
	//conn := zrpc.MustNewClient(clientConf)
	conn, err := grpc.NewClient("127.0.0.1:8080", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatal(err)
	}
	client := greet.NewGreetClient(conn)
	resp, err := client.Ping(context.Background(), &greet.Request{Ping: "ping"})
	if err != nil {
		log.Fatal(err)
		return
	}
	log.Println(resp)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

3. 配置文件说明

在使用gRPC服务时,我们会用到go-zero内置的RpcClientConf配置和RpcServerConf配置

  • RpcSeverConf
    • ServiceConf: 基础服务配置
    • ListenOn: 监听地址
    • Etcd: etcd 配置项
    • Auth: 是否开启 Auth
    • Redis: rpc 认证,仅当 Auth 为 true 生效
    • Timeout: 超时时间
    • Middlewares: 启用中间件
    • Health: 是否开启健康检查
  • RpcClientConf
    • Etcd: 服务发现配置,当需要使用 etcd 做服务发现时配置
    • Endpoints: RPC Server 地址列表,用于直连,当需要直连 rpc server 集群时配置
    • Target: 域名解析地址,名称规则请参考
    • App: rpc 认证的 app 名称,仅当 rpc server 开启认证时配置
    • Token: rpc 认证的 token,仅当 rpc server 开启认证时配置
    • NonBlock: 是否阻塞模式,当值为 true 时,不会阻塞 rpc 链接
    • Timeout: 超时时间
    • KeepaliveTime: 保活时间
    • Middlewares: 是否启用中间件

4. 使用etcd

4.1 服务发现

服务发现概述

注册中心是服务发现的核心,是包含服务实例数据(例如ip地址,端口等)的数据库。所以注册中心需要一个高可用的分布式键/值存储,例如Etcd,Zookeeper,Consul等。

  • Etcd更加稳定可靠
  • 在服务发现的实现上,Etcd使用的是节点租约(Lease)
  • Etcd支持稳定的watch
  • Etcd支持mvcc
  • Etcd支持更大的数据规模,支持存储百万到千万级别的key
  • Etcd性能更好

4.2 docker-compose配置

version: "3.5"
services:
  Etcd:
    container_name: etcd3-go-zero
    image: bitnami/etcd:3.5.6
    deploy:
      replicas: 1
      restart_policy:
        condition: on-failure
    environment:
      - ALLOW_NONE_AUTHENTICATION=yes
      - ETCD_SNAPSHOT_COUNT=10000
      - ETCD_QUOTA_BACKEND_BYTES=6442450944
    privileged: true
    volumes:
      - ./volumes/etcd/data:/bitnami/etcd/data
    ports:
      - 2379:2379
      - 2380:2380
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

4.3 使用etcd做为注册中心

  • server端:

    Etcd:
      Hosts:
      - 127.0.0.1:2379
      Key: greet.rpc
    
    1
    2
    3
    4
  • client端:

    #Target: 127.0.0.1:8080
    Etcd:
      Hosts:
      - 127.0.0.1:2379
      Key: greet.rpc
    
    1
    2
    3
    4
    5

如果使用grpc原生支持:

func main() {
	var clientConf zrpc.RpcClientConf
	conf.MustLoad("grpc-client/etc/client.yaml", &clientConf)
	//conn := zrpc.MustNewClient(clientConf)
	conn, err := grpc.NewClient("etcd://127.0.0.1:2379/greet.rpc", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatal(err)
	}
	client := greet.NewGreetClient(conn)
	resp, err := client.Ping(context.Background(), &greet.Request{Ping: "ping"})
	if err != nil {
		log.Fatal(err)
		return
	}
	log.Println(resp)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16