Go 语言如何进行 RPC 调用

简介: 云栖号资讯:【点击查看更多行业资讯】在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来! 今天我们来了解一下 Go 语言是如何进行远程方法调用的,远程方法调用是服务间进行通信的基础方式之一,是 Go 语言实现微服务架构必须掌握的开发知识和原理。
云栖号资讯:【 点击查看更多行业资讯
在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来!

今天我们来了解一下 Go 语言是如何进行远程方法调用的,远程方法调用是服务间进行通信的基础方式之一,是 Go 语言实现微服务架构必须掌握的开发知识和原理。

gRPC

gRPC 是一个高性能、开源、通用的 RPC 框架,由 Google 推出,基于HTTP/2 协议标准设计开发,默认采用 Protocol Buffers 数据序列化协议,支持多种开发语言。gRPC 提供了一种简单的方法来精确的定义服务,并且为客户端和服务端自动生成可靠代码的功能库。

我们来详细了解一下 gRPC 的众多特性:

  • gRPC 使用 ProtoBuf 来定义服务、接口和数据类型,ProtoBuf 是由 Google 开发的一种数据序列化协议(类似于XML、JSON和hessian)。ProtoBuf 能够将数据进行序列化,并广泛应用在数据存储和通信协议等方面。
  • gRPC 支持多种语言,并能够基于语言自动生成客户端和服务端代码。gRPC支持 C、C++、Node.js、Python、Ruby、Objective-C、PHP和C# 等语言,目前已提供了 C 语言版本的 gRPC、Java 语言版本的 grpc-java 和 Go 语言版本的 grpc-go,其他语言的版本正在积极开发中,其中,grpc-java 已经支持 Android 开发。

1

如上图所示为 gRPC 的调用示意图,我们可以看到,一个 C++ 语言的服务器可以通过 gRPC 分别与 Ruby 语言开发的桌面客户端和 Java 语言开发的 Android 客户端进行交互。

gRPC基于 HTTP/2 标准设计,所以相对于其他 RPC 框架,gRPC 拥有更多强大功能,如双向流、头部压缩、多复用请求等。这些功能给移动设备带来重大益处,如节省带宽、降低 TCP 连接次数、提高 CPU 利用率和延长电池寿命等。同时,gRPC 还提高了云端服务和 Web 应用的性能。gRPC 既能够在客户端应用,也能够在服务器端应用,从而以透明的方式实现客户端和服务器端的通信和简化通信系统的构建。

gRPC 的安装

首先使用 go get 命令安装 grpc-go。

go get -u google.golang.org/grpc

接着要安装插件,先使用 which protoc 命令检查是否安装了protoc;如果没有,则使用 go install 命令安装 proto 和 protoc-gen-go 两个库,最后可以使用 protoc 方法判断是否成功安装了。

----- 查看 protoc 是否安装,确保是3.0版本
$ which protoc
$ protoc --version
----- 安装插件
$ go install github.com/golang/protobuf/proto
$ go install github.com/golang/protobuf/protoc-gen-go
----- 测试是否安装成功
$ protoc -I pb/string.proto--go_out=plugins=grpc:.pb/string.proto

gRPC 过程调用实践

gRPC 过程调用时,服务端和客户端需要依赖共同的 proto 文件。proto 文件可以定义远程调用的接口、方法名、参数和返回值等。通过 proto 文件可以自动生成客户端和客户端的相应 RPC 代码。借助这些代码,客户端可以十分方便地发送 RPC 请求,并且服务端也可以很简单地建立 RPC 服务器,处理 RPC 请求并且将返回值作为响应发送给客户端。

定义和编译 proto 文件

首先,我们要定义一个 proto 文件,其具体语法请查看 Protobuf3 语言指南。在该文件中,我们定义了两个参数结果,分别是 StringRequest 和 StringResponse,同时还有一个服务结构 StringService,代码如下:

syntax = "proto3";
package pb;
service StringService{
rpc Concat(StringRequest) returns (StringResponse) {}
rpc Diff(StringRequest) returns (StringResponse) {}
}

message StringRequest {
string A = 1;
string B = 2;
}

message StringResponse {
string Ret = 1;
string err = 2;
} 

StrtingService 有两个方法,分别为 Concat 和 Diff,每个方法都有对应的输入参数和返回值,这些值也都定义在 proto 文件中。

gRPC 可以定义 4 种类型的服务接口,分别是一元 RPC、服务器流 RPC、客户端流式 RPC 和双向流 RPC。

(1)一元 RPC 是指客户端向服务器发送请求并获得响应,就像正常的函数调用一样。

rpc Concat(StringRequest) returns (StringResponse) {} 

(2)服务器流 RPC 是指客户端发送一个对象,服务器端返回一个Stream(流式消息)。

rpc LotsOfServerStream(StringRequest) returns (stream StringResponse) {} 

(3)客户端流式 RPC,客户端发送一个 Stream(流式消息)服务端返回一个对象。

rpc LotsOfClientStream(stream StringRequest) returns (StringResponse) {} 

(4)双向流 RPC,两个流独立运行,客户端和服务器可以按照它们喜欢的顺序进行读取和写入;例如,服务器可以在写入响应之前等待接收所有客户端消息,也可以交替地进行消息的读取和写入,或读取和写入的其他组合。每个流中消息的顺序被保留。类似于 WebSocket(长连接),客户端可以向服务端请求消息,服务器端也可以向客户端请求消息。

rpc LotsOfServerAndClientStream(stream StringRequest) returns (stream StringResponse) {} 

接下来我们使用 protoc 编译工具编译这个protoc文件,生成服务端和客户端的代码,如下:

protoc --go_out=plugins=grpc:. pb/string.proto

从 proto 文件中的服务定义开始,gRPC 提供了生成客户机和服务器端代码的 protocol buffer 编译器插件。gRPC 用户通常在客户端调用这些 API,并在服务器端实现相应的 API。

在服务器端,服务器实现服务声明的方法,并运行 gRPC 服务器来处理客户端调用。gRPC 框架会接受网络传入请求,解析请求数据,执行相应服务方法和将方法结果编码成响应通过网络传递给客户端。客户端的本地定义方法,其方法名、参数和返回值与服务端定义的方法相同。客户端可以直接在本地对象上调用这些方法,将调用的参数包含在对应的 protocol buffer 消息类型中,gRPC再将请求发送到服务端,服务端解析请求。

客户端发送 RPC 请求

我们先来看客户端代码,首先调用 grpc.Dial 建立网络连接,然后使用 protoc 编译生成的 pb.NewStringServiceClient 函数创建 gRPC 客户端,然后调用客户端的 Concat 函数,进行RPC调用,代码如下所示:

package grpc
import (
"context"
"fmt"
"github.com/keets2012/Micro-Go-Pracrise/ch9-rpc/pb"
"google.golang.org/grpc"
)

func main() {
serviceAddress := "127.0.0.1:1234"
conn, err := grpc.Dial(serviceAddress, grpc.WithInsecure())
if err != nil {
    panic("connect error")
}

defer conn.Close()
stringClient := pb.NewStringServiceClient(conn)
stringReq := &pb.StringRequest{A: "A", B: "B"}
reply, _ := stringClient.Concat(context.Background(), stringReq)
fmt.Printf("StringService Concat : %s concat %s = %s", 
stringReq.A, stringReq.B, reply.Ret)
} 

服务端建立 RPC 服务

再来看看服务器端的代码,它首先需要调用 grpc.NewServer() 来建立RPC的服务端,然后将 StringService 注册到RPC服务端上,其具有的两个函数分别处理 Concat 和 Diff 请求,代码如下:

func main() {
flag.Parse()
lis, err := net.Listen("tcp","127.0.0.1:1234")
if err != nil {
    log.Fatalf("failed to listen: %v", err)
}
grpcServer := grpc.NewServer()
stringService := new(string_service.StringService)
pb.RegisterStringServiceServer(grpcServer, stringService)
grpcServer.Serve(lis)
} 

最后我们来看 StringService 的具体代码实现,它首先定义了StringService 结构体,然后实现了它的 Concat 方法和 Diff 方法。

type StringService struct{}

func (s * StringService) Concat(ctx context.Context, req *pb.StringRequest) (*pb.StringResponse, error) {
if len(req.A)+len(req.B) > StrMaxSize {
    response := pb.StringResponse{Ret: ""}
    return &response, nil
}
response := pb.StringResponse{Ret: req.A + req.B}
return &response, nil
}

func (s * StringService) Diff(ctx context.Context, req *pb.StringRequest) (*pb.StringResponse, error) {
if len(req.A) < 1 || len(req.B) < 1 {
    response := pb.StringResponse{Ret: ""}
    return &response, nil
}
res := ""
if len(req.A) >= len(req.B) {
    for _, char := range req.B {
        if strings.Contains(req.A, string(char)) {
            res = res + string(char)
        }
    }
} else {
    for _, char := range req.A {
        if strings.Contains(req.B, string(char)) {
            res = res + string(char)
        }
    }
}
response := pb.StringResponse{Ret: res}
return &response, nil
} 

如上代码所示,StringService 的 Concat 方法和 Diff 方法实现起来都很简单,Concat 方法就是将 StringRequest 中的 A 和 B 字符拼接在一起;而 Diff 方法则是通过循环遍历,将 A 和 B 字符的差异部分计算出来。

从上面的讲述可以看出,客户端发送一个请求后,必须等待服务器发回响应才能继续发送下一个请求,这种交互模式具有一定局限性,它无法更好地利用网络带宽,传递更多的请求或响应。而 gRPC 支持流式的请求响应模式来优化解决这一问题。

【云栖号在线课堂】每天都有产品技术专家分享!
课程地址: https://yqh.aliyun.com/live

立即加入社群,与专家面对面,及时了解课程最新动态!
【云栖号在线课堂 社群】https://c.tb.cn/F3.Z8gvnK

相关文章
|
4月前
|
运维 监控 算法
监控局域网其他电脑:Go 语言迪杰斯特拉算法的高效应用
在信息化时代,监控局域网成为网络管理与安全防护的关键需求。本文探讨了迪杰斯特拉(Dijkstra)算法在监控局域网中的应用,通过计算最短路径优化数据传输和故障检测。文中提供了使用Go语言实现的代码例程,展示了如何高效地进行网络监控,确保局域网的稳定运行和数据安全。迪杰斯特拉算法能减少传输延迟和带宽消耗,及时发现并处理网络故障,适用于复杂网络环境下的管理和维护。
|
4月前
|
编译器 Go
揭秘 Go 语言中空结构体的强大用法
Go 语言中的空结构体 `struct{}` 不包含任何字段,不占用内存空间。它在实际编程中有多种典型用法:1) 结合 map 实现集合(set)类型;2) 与 channel 搭配用于信号通知;3) 申请超大容量的 Slice 和 Array 以节省内存;4) 作为接口实现时明确表示不关注值。此外,需要注意的是,空结构体作为字段时可能会因内存对齐原因占用额外空间。建议将空结构体放在外层结构体的第一个字段以优化内存使用。
|
23天前
|
分布式计算 Go C++
初探Go语言RPC编程手法
总的来说,Go语言的RPC编程是一种强大的工具,让分布式计算变得简单如同本地计算。如果你还没有试过,不妨挑战一下这个新的编程领域,你可能会发现新的世界。
42 10
|
4月前
|
存储 缓存 监控
企业监控软件中 Go 语言哈希表算法的应用研究与分析
在数字化时代,企业监控软件对企业的稳定运营至关重要。哈希表(散列表)作为高效的数据结构,广泛应用于企业监控中,如设备状态管理、数据分类和缓存机制。Go 语言中的 map 实现了哈希表,能快速处理海量监控数据,确保实时准确反映设备状态,提升系统性能,助力企业实现智能化管理。
65 3
|
4月前
|
存储 缓存 安全
Go 语言中的 Sync.Map 详解:并发安全的 Map 实现
`sync.Map` 是 Go 语言中用于并发安全操作的 Map 实现,适用于读多写少的场景。它通过两个底层 Map(`read` 和 `dirty`)实现读写分离,提供高效的读性能。主要方法包括 `Store`、`Load`、`Delete` 等。在大量写入时性能可能下降,需谨慎选择使用场景。
|
4月前
|
SQL 安全 Java
阿里双十一背后的Go语言实践:百万QPS网关的设计与实现
解析阿里核心网关如何利用Go协程池、RingBuffer、零拷贝技术支撑亿级流量。 重点分享: ① 如何用gRPC拦截器实现熔断限流; ② Sync.Map在高并发读写中的取舍。
150 1
|
4月前
|
存储 算法 安全
基于 Go 语言的公司内网管理软件哈希表算法深度解析与研究
在数字化办公中,公司内网管理软件通过哈希表算法保障信息安全与高效管理。哈希表基于键值对存储和查找,如用户登录验证、设备信息管理和文件权限控制等场景,Go语言实现的哈希表能快速验证用户信息,提升管理效率,确保网络稳定运行。
65 0
|
6月前
|
开发框架 Go 计算机视觉
纯Go语言开发人脸检测、瞳孔/眼睛定位与面部特征检测插件-助力GoFly快速开发框架
开发纯go插件的原因是因为目前 Go 生态系统中几乎所有现有的人脸检测解决方案都是纯粹绑定到一些 C/C++ 库,如 OpenCV 或 dlib,但通过 cgo 调用 C 程序会引入巨大的延迟,并在性能方面产生显著的权衡。此外,在许多情况下,在各种平台上安装 OpenCV 是很麻烦的。使用纯Go开发的插件不仅在开发时方便,在项目部署和项目维护也能省很多时间精力。
161 5
|
7月前
|
Go 数据安全/隐私保护 开发者
Go语言开发
【10月更文挑战第26天】Go语言开发
92 3
|
7月前
|
Java 程序员 Go
Go语言的开发
【10月更文挑战第25天】Go语言的开发
86 3