【go语言】grpc 快速入门

一、什么是 grpc 和 protobuf

1.1 grpc

gRPC 是由 Google 开发的一个高效、开源的远程过程调用(RPC)框架,用于在分布式系统中进行通信。它是基于 HTTP/2 协议,支持多种语言,能够让不同的系统或应用程序(即使使用不同的编程语言)进行高效的通信。

1.1.1 主要特点

  • 高性能:gRPC 基于 HTTP/2 协议,支持流式传输、请求多路复用、头部压缩等特性,这些使得 gRPC 比传统的 HTTP/1.x 更加高效。
  • 跨语言支持:gRPC 支持多种编程语言(如 C++, Java, Go, Python, Ruby, Node.js 等),使得不同语言开发的系统能够无缝通信。
  • 支持同步和异步调用:gRPC 提供了同步和异步的调用方式,可以根据需求选择使用。
  • 内置支持流式传输:gRPC 支持单向流和双向流,可以非常方便地实现实时通信。
  • 自动生成代码:通过定义服务接口,gRPC 自动生成客户端和服务端的代码,减少手动编写的工作量。

1.1.2 典型应用场景

  • 微服务架构中的服务之间的高效通信。
  • 跨平台、跨语言的分布式系统通信。
  • 实时数据传输或流处理。

1.2 protobuf

Protocol Buffers(简称 Protobuf)是 Google 开发的一种高效的序列化结构数据的机制,类似于 XML 或 JSON,但比它们更紧凑、更高效。Protobuf 用于数据的定义、传输和持久化,通常与 gRPC 一起使用。

1.2.1 主要特点

  • 紧凑高效:Protobuf 使用二进制格式来存储数据,相比于 JSON 或 XML,它占用更少的空间,传输更快,解析也更高效。
  • 平台和语言无关:Protobuf 支持多种语言(如 C++, Java, Python, Go 等)并能在不同平台间进行无缝传输。
  • 结构化数据 :数据的定义通过 .proto 文件进行,可以清晰地指定数据的类型、字段以及数据结构。
  • 向后兼容与向前兼容:Protobuf 支持字段的增删和修改时,不影响已经存在的消息结构。新版本和旧版本可以兼容地进行通信。

1.2.2 工作原理

  1. 定义数据结构 :你需要创建一个 .proto 文件来定义数据结构和服务接口。例如:

    复制代码
    syntax = "proto3";
    
    message Person {
      string name = 1;
      int32 id = 2;
      string email = 3;
    }
    
    service Greeter {
      rpc SayHello (Person) returns (Person);
    }
  2. 编译 .proto 文件 :使用 protoc 编译器,将 .proto 文件转化为目标语言的代码。

  3. 生成代码 :Protobuf 会根据 .proto 文件生成语言特定的类和方法,可以通过这些类和方法来序列化、反序列化数据。

  4. 使用 gRPC :如果你定义了服务接口,gRPC 会根据 .proto 文件生成客户端和服务器端代码,帮助你构建高效的通信。

1.2.3 为什么结合使用 gRPC 和 Protobuf?

gRPC 通常与 Protobuf 一起使用,因为它们可以提供以下优势:

  • 性能:Protobuf 的二进制格式非常高效,适合需要高性能的场景,尤其是微服务通信时。
  • 简洁性:gRPC 和 Protobuf 的结合可以让你轻松地实现分布式服务,而无需担心数据格式的兼容性、传输效率等问题。
  • 自动化:通过 Protobuf 定义的数据结构和 gRPC 服务接口,所有的代码生成和数据序列化/反序列化都自动化处理,极大地减少了开发工作量。
  • gRPC 是一个高效的远程过程调用框架,用于不同系统之间的通信,支持多种语言,并且支持高效的流式传输。
  • Protobuf 是一种高效的序列化数据格式,通常用于 gRPC 中,能够减少数据的存储空间,提高传输效率。

这两者结合使用,可以构建出高效、可靠且跨语言的分布式系统。

二、RPC 有四种需求

2.1 简单模式

这种模式最为传统,即客户端发起一次请求,服务端响应一个数据,这个大家平时熟悉的RPC没有大的区别,所以不再详细介绍。

2.1.1 proto 文件

Go 复制代码
syntax = "proto3";

package helloworld;

// 指定 Go 包的路径
option go_package = ".;proto";

// 这是一个rpc服务
service Hello {
    rpc Hello(HelloRequest) returns (HelloResponse);
}

message HelloRequest {
    string name = 1; // 1 是编号
}

message HelloResponse {
    string reply = 1;
}

我们在编译 proto 文件的时候,我们需要先将安装了最新版本的 protoc-gen-goprotoc-gen-go-grpc 插件。如果你还没有安装或需要更新它们,可以使用以下命令:

Go 复制代码
# 安装最新的 protoc-gen-go 插件
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest

# 安装最新的 protoc-gen-go-grpc 插件
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

我们需要生成普通Go代码的时候,使用 protoc 命令时,命令格式如下,我们会根据 helloworld.proto 文件生成普通的 Go 代码文件。

cpp 复制代码
protoc -I . helloworld.proto --go_out=.

我们需要生成gRPC代码的时候,使用 protoc 命令时,命令格式如下,我们会使用 --go-grpc_out 来生成 gRPC 相关的代码(包括客户端和服务端的 gRPC 代码):

cpp 复制代码
protoc -I . helloworld.proto --go-grpc_out=.

我们可以使用完整的命令行,来一次性生成所有的代码(普通的 Go 代码和 gRPC 相关的代码),你可以将两个命令合并成一个:

cpp 复制代码
protoc -I . helloworld.proto --go_out=. --go-grpc_out=.

2.1.2 server端

Go 复制代码
package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"log"
	"net"
	pb "xuego/protobuf/helloworld/proto"
)

type Server struct {
	pb.UnimplementedHelloServer // 继承自动生成的 Unimplemented 方法,可以确保将来不破坏接口兼容性
}

// SayHello 实现了 Greeter 服务的 SayHello 方法
func (s *Server) Hello(ctx context.Context, request *pb.HelloRequest) (*pb.HelloResponse, error) {
	// 构造并返回响应
	return &pb.HelloResponse{
		Reply: "Hello " + request.Name,
	}, nil
}

func main() {
	// 创建一个新的 gRPC 服务器
	g := grpc.NewServer()

	// 注册 Greeter 服务到 gRPC 服务器
	pb.RegisterHelloServer(g, &Server{})

	// 定义服务器监听地址和端口
	listen, err := net.Listen("tcp", ":8080")
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}

	// 输出日志,表明服务器开始监听
	fmt.Println("Server is listening on port :50051")

	// 启动 gRPC 服务器
	if err := g.Serve(listen); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}

2.1.3 client端

Go 复制代码
package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"xuego/protobuf/helloworld/proto"
)

func main() {
	conn, err := grpc.Dial("localhost:8080", grpc.WithInsecure())
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	c := proto.NewHelloClient(conn)
	r, err := c.Hello(context.Background(), &proto.HelloRequest{Name: "xuego"})
	if err != nil {
		panic(err)
	}
	fmt.Errorf(r.Reply)
}

2.2 服务端数据流模式

这种模式是客户端发起一次请求,服务端返回一段连续的数据流。典型的例子是客户端向服务端发送一个股票代码,服务端就把该股票的实时数据源源不断的返回给客户端。

2.3 客户端数据流模式

与服务端数据流模式相反,这次是客户端源源不断的向服务端发送数据流,而在发送结束后,由服务端返回一个响应。典型的例子就是物联网终端向服务器报发数据。

2.4 双向数据流模式

顾名思义,这是客户端和服务端都可以向对方发送数据流,这个时候双方的数据可以同时互相发送,也就是可以实现实时交互。典型的例子就是聊天机器人。

2.5 服务端数据流模式

&& 客户端数据流模式

&& 双向数据流模式

2.5.1 proto 文件

Go 复制代码
syntax = "proto3";

option go_package = ".;proto";

service Greeter {
    rpc GetStream(StreamReqData) returns (stream StreamResData); // 服务端流模式
    rpc PutStream(stream StreamReqData) returns (StreamResData); // 客户端流模式
    rpc AllStream(stream StreamReqData) returns (stream StreamResData); // 双向流模式
}

message StreamReqData {
    string data = 1;
}

message StreamResData {
    string data = 1;
}

在这些模式下,他们在服务端中写的函数的参数与简单模式的不同,需要注意一下!!!!

2.5.2 server端

Go 复制代码
package main

import (
	"fmt"
	"google.golang.org/grpc"
	"net"
	"sync"
	"time"
	pb "xuego/stream_grpc_test/proto"
)

const PORT = ":50052"

type Server struct {
	pb.UnimplementedGreeterServer
}

func (s *Server) GetStream(req *pb.StreamReqData, res pb.Greeter_GetStreamServer) error {
	i := 0
	for {
		i++
		_ = res.Send(&pb.StreamResData{
			Data: fmt.Sprintf("%v", time.Now().Unix()),
		})
		time.Sleep(time.Second)
		if i > 10 {
			break
		}
	}
	return nil
}

func (s *Server) PutStream(cliStr pb.Greeter_PutStreamServer) error {
	for {
		if a, err := cliStr.Recv(); err != nil {
			fmt.Println(err)
			break
		} else {
			fmt.Println(a.Data)
		}
	}
	return nil
}

func (s *Server) AllStream(allStr pb.Greeter_AllStreamServer) error {
	wg := sync.WaitGroup{}
	wg.Add(2)
	go func() {
		defer wg.Done()
		for {
			data, _ := allStr.Recv()
			fmt.Println("收到客户端下消息:" + data.Data)
		}
	}()
	go func() {
		defer wg.Done()
		for {
			_ = allStr.Send(&pb.StreamResData{Data: "我是服务器"})
			time.Sleep(time.Second)
		}
	}()
	wg.Wait()
	return nil
}

func main() {
	lis, err := net.Listen("tcp", PORT)
	if err != nil {
		panic(err)
	}
	s := grpc.NewServer()
	pb.RegisterGreeterServer(s, &Server{})
	err = s.Serve(lis)
	if err != nil {
		panic(err)
	}
}

2.5.3 client端

Go 复制代码
package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"sync"
	"time"
	pb "xuego/stream_grpc_test/proto"
)

func main() {
	conn, err := grpc.Dial("localhost:50052", grpc.WithInsecure())
	if err != nil {
		panic(err)
	}
	defer conn.Close()
	// 服务端流模式
	c := pb.NewGreeterClient(conn)
	res, _ := c.GetStream(context.Background(), &pb.StreamReqData{Data: "陌客网"})
	for {
		a, err := res.Recv()
		if err != nil {
			fmt.Println(err)
			break
		}
		fmt.Println(a.Data)
	}

	// 客户端流模式
	putS, _ := c.PutStream(context.Background())
	i := 0
	for {
		i++
		putS.Send(&pb.StreamReqData{
			Data: fmt.Sprintf("慕课网%d", i),
		})
		time.Sleep(time.Second)
		if i > 10 {
			break
		}
	}

	// 双向流模式
	allStr, _ := c.AllStream(context.Background())
	wg := sync.WaitGroup{}
	wg.Add(2)
	go func() {
		defer wg.Done()
		for {
			data, _ := allStr.Recv()
			fmt.Println("收到客户端下消息:" + data.Data)
		}
	}()
	go func() {
		defer wg.Done()
		for {
			_ = allStr.Send(&pb.StreamReqData{Data: "我是客户段"})
			time.Sleep(time.Second)
		}
	}()
	wg.Wait()
}
相关推荐
CaffeinePro1 分钟前
Pydantic深度使用:数据校验、枚举、ORM映射
后端·fastapi
Chenyiax32 分钟前
从 Chat 到 Responses:OpenAI API 抽象为什么变了?
后端
MariaH33 分钟前
Koa和Express的区别
后端
MariaH39 分钟前
Koa框架的使用
后端
luckdewei2 小时前
那个用 passlib 做认证的新同事,上线第一天就把用户密码写进了日志
后端
ping某3 小时前
为什么 Nginx 明明监听了 80,转发后端时却用了 4xxxx 端口?
后端·nginx
JustHappy3 小时前
我汇总了身边朋友的经历才发现,其实第一份实习是最难找的......
前端·后端·面试
uhakadotcom3 小时前
在python 的 工程化架构中 ,什么是 薄包装器层?
后端·面试·github
用户1474853079748 小时前
CodeX使用Skill生成游戏美术和音乐资源,一分钟入门
后端
Melody1238 小时前
用 abort 中断 AI 流式请求,我之前做错了
后端