您当前的位置:首页 > 电脑百科 > 程序开发 > 编程百科

使用Protobuf增强您的REST API

时间:2019-12-16 11:00:28  来源:  作者:

当涉及到REST API时,JSON(JAVAScript对象表示法)已经成为数据交换的格式。很久以前,开发人员放弃了XML,转而支持JSON,因为JSON紧凑,无模式,易于阅读且易于在线传输。

JSON的无模式性质确保您可以添加或删除字段,并且仍然拥有有效的JSON。但是,这也意味着,由于添加或删除了字段,您现在功能全面的客户端将开始失败。当您具有微服务体系结构并且有100个服务通过JSON相互通信并且您不小心更改了其中一个服务的JSON响应时,此问题会放大。

此外,JSON通过重复字段名(如果你使用的是阵列)发生不必要的额外空间,变得相当难读的,一旦你开始建立你的数据结构。

2001年,google开发了一种内部,平台和语言独立的数据序列化格式,称为Protobuf(协议缓冲区的缩写),以解决JSON的所有缺点。Protobuf的设计目标是简化和提高速度。

在本文中,我将分享什么是Protobuf,以及在REST API中替换JSON如何显着简化客户端和服务器之间的数据序列化。

表中的内容

  1. Protobuf是什么
  2. 工具
  3. Protobuf定义
  4. 创建REST端点
  5. 使用REST端点
  6. 与JSON相比
  7. 结论

1. Protobuf是什么

Protobuf的维基百科说:

协议缓冲区(Protobuf)是一种序列化结构化数据的方法。在开发程序时,通过线路相互通信或存储数据是很有用的。该方法涉及描述某些数据结构的接口描述语言和从该描述生成源代码的程序,用于生成或解析表示结构化数据的字节流。

在Protobuf中,开发人员在.proto文件中定义数据结构(称为消息),然后在编译protoc器的帮助下编译为代码。该编译器带有用于多种语言(来自Google和社区)的代码生成器,并生成用于存储数据的数据结构和用于对其进行序列化和反序列化的方法。

Protobuf消息被序列化为二进制格式,而不是诸如JSON之类的文本,因此Protobuf中的消息根本不是人类可读的。由于二进制性质,Protobuf消息可以压缩,并且比等效的JSON消息占用更少的空间。

一旦完成服务器的实现,就可以.proto与客户端共享文件(就像共享API期望并返回的JSON模式一样),它们可以利用相同的代码生成来使用消息。

2.工具

我们需要安装以下工具来遵循本教程。

  1. VS代码或您最喜欢的代码编辑器。
  2. Golang编译器和工具(我们将在Go中编写服务器和客户端)
  3. [protoc](https://github.com/protocolbuffers/protobuf/releases) protobuf编译器。

请遵循每个工具的安装说明。为了简洁起见,我跳过了此处的说明,但是如果您遇到任何错误,请告诉我,我们将很乐意为您提供帮助。

3. Protobuf定义

在本节中,我们将创建一个.proto文件,在整个演示过程中将使用该文件。该原始文件将包含两个消息EchoRequest和EchoResponse。

然后,我们将创建REST端点接受EchoRequest并使用进行回复EchoResponse。然后,我们将使用REST端点创建一个客户端(也在Go中)。

在开始之前,我希望您注意有关该项目目录结构的一些事情。

  1. 我已经在文件夹github.com/kaysush中创建了一个文件$GOPATH/src夹。$GOPATH安装go编译器和工具时会设置变量。
  2. 我将项目文件夹protobuf-demo放入github.com/kaysush。

您可以在下图中看到目录结构。

$GOPATH
 ├── bin
 ├── pkg
 └── src
 └── github.com
 └── kaysush
 └── protobuf-demo
 ├── server
 │ └── test.go
 ├── client
 └── proto
 └── echo
 ├── echo.proto 
 └── echo.pb.go

创建一个echo.proto文件。

syntax = "proto3";
package echo;
option go_package="echo";
message EchoRequest {
 string name = 1;
}
message EchoResponse {
 string message = 1;
}

echo.proto

将proto文件编译为golang代码。

protoc echo.proto --go_out=.

这将生成一个echo.pb.go文件,该文件具有将我们的消息定义为的go代码struct。

作为测试,我们将查看封送和反封送消息是否正常工作。

package main
import (
 "fmt"
 "log"
 "github.com/golang/protobuf/proto"
 "github.com/kaysush/protobuf-demo/proto/echo" //<-- Take a note that I've created my code folder in $GOPATH/src
)
func main() {
 req := &echo.EchoRequest{Name: "Sushil"}
 data, err := proto.Marshal(req)
 if err != nil {
 log.Fatalf("Error while marshalling the object : %v", err)
 }
 res := &echo.EchoRequest{}
 err = proto.Unmarshal(data, res)
 if err != nil {
 log.Fatalf("Error while un-marshalling the object : %v", err)
 }
 fmt.Printf("Value from un-marshalled data is %v", res.GetName())
}

test.go

执行它。

go run test.go

您应该看到以下输出。

Value from un-marshalled data is Sushil

这表明我们的Protobuf定义运行良好。在下一节中,我们将实现REST端点并接受Protobuf消息作为请求的有效负载。

4.创建REST端点

Golang的net.http软件包足以创建REST API,但为了使我们更容易一点,我们将使用该[gorilla/mux](https://www.gorillatoolkit.org/pkg/mux)软件包来实现REST端点。

使用以下命令安装软件包。

go get github.com/gorilla/mux

server.go在server文件夹中创建一个文件,然后开始编码。

package main
import (
 "fmt"
 "io/ioutil"
 "log"
 "net/http"
 "time"
 "github.com/golang/protobuf/proto"
 "github.com/gorilla/mux"
 "github.com/kaysush/protobuf-demo/proto/echo"
)
func Echo(resp http.ResponseWriter, req *http.Request) {
 contentLength := req.ContentLength
 fmt.Printf("Content Length Received : %vn", contentLength)
 request := &echo.EchoRequest{}
 data, err := ioutil.ReadAll(req.Body)
 if err != nil {
 log.Fatalf("Unable to read message from request : %v", err)
 }
 proto.Unmarshal(data, request)
 name := request.GetName()
 result := &echo.EchoResponse{Message: "Hello " + name}
 response, err := proto.Marshal(result)
 if err != nil {
 log.Fatalf("Unable to marshal response : %v", err)
 }
 resp.Write(response)
}
func main() {
 fmt.Println("Starting the API server...")
 r := mux.NewRouter()
 r.HandleFunc("/echo", Echo).Methods("POST")
 server := &http.Server{
 Handler: r,
 Addr: "0.0.0.0:8080",
 WriteTimeout: 2 * time.Second,
 ReadTimeout: 2 * time.Second,
 }
 log.Fatal(server.ListenAndServe())
}

server.go

当前目录如下所示。

$GOPATH
 ├── bin
 ├── pkg
 └── src
 └── github.com
 └── kaysush
 └── protobuf-demo
 ├── server
 │ ├── test.go
 │ └── server.go
 ├── client
 └── proto
 └── echo
 ├── echo.proto 
 └── echo.pb.go

该Echo函数的代码应易于理解。我们http.Request使用读取字节iotuil.ReadAll,然后从中读取Unmarshal字节。EchoRequest``Name

然后,我们按照相反的步骤来构造一个EchoResponse。

在Main()函数中,我们定义了一条路由/echo,该路由应接受POST方法并通过调用Echo函数来处理请求。

启动服务器。

go run server.go

您应该会看到消息 Starting API server...

具有/echo端点接受POST功能的REST-ish API(因为我们未遵循POST请求的REST规范)已准备好接受来自客户端的Protobuf消息。

5.使用REST端点

在本节中,我们将实现使用/echo端点的客户端。

我们的客户端和服务器都在相同的代码库中,因此我们不需要从proto文件中重新生成代码。在实际使用中,您将proto与客户端共享文件,然后客户端将以其选择的编程语言生成其代码文件。

client.go在client文件夹中创建一个文件。

package main
import (
 "bytes"
 "fmt"
 "io/ioutil"
 "log"
 "net/http"
 "github.com/golang/protobuf/proto"
 "github.com/kaysush/protobuf-demo/proto/echo"
)
func makeRequest(request *echo.EchoRequest) *echo.EchoResponse {
 req, err := proto.Marshal(request)
 if err != nil {
 log.Fatalf("Unable to marshal request : %v", err)
 }
 resp, err := http.Post("http://0.0.0.0:8080/echo", "Application/x-binary", bytes.NewReader(req))
 if err != nil {
 log.Fatalf("Unable to read from the server : %v", err)
 }
 respBytes, err := ioutil.ReadAll(resp.Body)
 if err != nil {
 log.Fatalf("Unable to read bytes from request : %v", err)
 }
 respObj := &echo.EchoResponse{}
 proto.Unmarshal(respBytes, respObj)
 return respObj
}
func main() {
 request := &echo.EchoRequest{Name: "Sushil"}
 resp := makeRequest(request)
 fmt.Printf("Response from API is : %vn", resp.GetMessage())
}

client.go

客户应该更容易理解。我们正在使用http.Post将Protobuf字节发送到我们的API服务器,并读回响应,然后将Unmarshal其发送给EchoResponse。

立即运行客户端。

go run client.go

您应该看到服务器的响应。

Response from API is : Hello Sushil

6.与JSON相比

我们已经成功实现了使用Protobuf而不是JSON的API。

在本节中,我们将实现一个终结点,该终结点EchoJsonRequest在JSON中接受类似内容,并在JSON中也进行响应。

我已经structs为JSON 实现了另一个程序包。

package echojson
type EchoJsonRequest struct {
 Name string
}
type EchoJsonResponse struct {
 Message string
}

echo.json.go

然后将新功能添加到server.go。

func EchoJson(resp http.ResponseWriter, req *http.Request) {
 contentLength := req.ContentLength
 fmt.Printf("Content Length Received : %vn", contentLength)
 request := &echojson.EchoJsonRequest{}
 data, err := ioutil.ReadAll(req.Body)
 if err != nil {
 log.Fatalf("Unable to read message from request : %v", err)
 }
 json.Unmarshal(data, request)
 name := request.Name
 result := &echojson.EchoJsonResponse{Message: "Hello " + name}
 response, err := json.Marshal(result)
 if err != nil {
 log.Fatalf("Unable to marshal response : %v", err)
 }
 resp.Write(response)
}

server.go

在中为此新功能添加绑定main()。

r.HandleFunc("/echo_json", EchoJson).Methods("POST")

让我们修改客户端,以将重复的请求发送到Protobuf和JSON端点,并计算平均响应时间。

package main
import (
 "bytes"
 "encoding/json"
 "fmt"
 "io/ioutil"
 "log"
 "net/http"
 "time"
 "github.com/golang/protobuf/proto"
 "github.com/kaysush/protobuf-demo/proto/echo"
 "github.com/kaysush/protobuf-demo/proto/echojson"
)
func makeRequest(request *echo.EchoRequest) *echo.EchoResponse {
 req, err := proto.Marshal(request)
 if err != nil {
 log.Fatalf("Unable to marshal request : %v", err)
 }
 resp, err := http.Post("http://0.0.0.0:8080/echo", "application/json", bytes.NewReader(req))
 if err != nil {
 log.Fatalf("Unable to read from the server : %v", err)
 }
 respBytes, err := ioutil.ReadAll(resp.Body)
 if err != nil {
 log.Fatalf("Unable to read bytes from request : %v", err)
 }
 respObj := &echo.EchoResponse{}
 proto.Unmarshal(respBytes, respObj)
 return respObj
}
func makeJsonRequest(request *echojson.EchoJsonRequest) *echojson.EchoJsonResponse {
 req, err := json.Marshal(request)
 if err != nil {
 log.Fatalf("Unable to marshal request : %v", err)
 }
 resp, err := http.Post("http://0.0.0.0:8080/echo_json", "application/json", bytes.NewReader(req))
 if err != nil {
 log.Fatalf("Unable to read from the server : %v", err)
 }
 respBytes, err := ioutil.ReadAll(resp.Body)
 if err != nil {
 log.Fatalf("Unable to read bytes from request : %v", err)
 }
 respObj := &echojson.EchoJsonResponse{}
 json.Unmarshal(respBytes, respObj)
 return respObj
}
func main() {
 var totalPBTime, totalJSONTime int64
 requestPb := &echo.EchoRequest{Name: "Sushil"}
 for i := 1; i <= 1000; i++ {
 fmt.Printf("Sending request %vn", i)
 startTime := time.Now()
 makeRequest(requestPb)
 elapsed := time.Since(startTime)
 totalPBTime += elapsed.Nanoseconds()
 }
 requestJson := &echojson.EchoJsonRequest{Name: "Sushil"}
 for i := 1; i <= 1000; i++ {
 fmt.Printf("Sending request %vn", i)
 startTime := time.Now()
 makeJsonRequest(requestJson)
 elapsed := time.Since(startTime)
 totalJSONTime += elapsed.Nanoseconds()
 }
 fmt.Printf("Average Protobuf Response time : %v nano-secondsn", totalPBTime/1000)
 fmt.Printf("Average JSON Response time : %v nano-secondsn", totalJSONTime/1000)
}

运行服务器和客户端。

我们的服务器记录了请求的内容长度,您可以看到Protobuf请求为8个字节,而相同的JSON请求为17个字节

 

使用Protobuf增强您的REST API

 

 

JSON的请求大小是普通消息的两倍

客户端记录Protobuf和JSON请求的平均响应时间(以纳秒为单位)(封送请求+发送请求+封送响应)。

我运行了client.go3次,尽管平均响应时间差异很小,但我们可以看到Protobuf请求的平均响应时间始终较小。

差异很小,因为我们的消息非常小,随着消息大小的增加,将其取消编组为JSON的成本也随之增加。

 

使用Protobuf增强您的REST API

 

 

多个比较请求

7.结论

在REST API中使用Protobuf而不是JSON可以导致更小的请求大小和更快的响应时间。在我们的演示中,由于有效负载较小,因此响应时间效果并不明显,但是看到这种模式,可以肯定地说Protobuf的性能应优于JSON。

那里有它。在您的REST API中使用Protobuf替换JSON。

如果您发现我的代码有任何问题或有任何疑问,请随时发表评论。

直到快乐的编码!:)

翻译自:https://medium.com/@Sushil_Kumar/supercharge-your-rest-apis-with-protobuf-b38d3d7a28d3



Tags:Protobuf   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
作者:iversonluo,腾讯 WXG 应用开发工程师有些后台同学将自己称为 SQL Boy,因为负责的业务主要是对数据库进行增删改查。经常和 Proto 打交道的同学,是不是也会叫自己 PB Boy?因...【详细内容】
2020-12-01  Tags: Protobuf  点击:(111)  评论:(0)  加入收藏
结构化数据的序列化是通过网络传输信息或存储数据的关键步骤,因为这是一个非常占用CPU的任务。 实际上,在许多通信方案中,瓶颈都是数据序列化和反序列化。开发序列化需要考虑跨...【详细内容】
2019-12-25  Tags: Protobuf  点击:(74)  评论:(0)  加入收藏
当涉及到REST API时,JSON(JavaScript对象表示法)已经成为数据交换的格式。很久以前,开发人员放弃了XML,转而支持JSON,因为JSON紧凑,无模式,易于阅读且易于在线传输。JSON的无模式性...【详细内容】
2019-12-16  Tags: Protobuf  点击:(89)  评论:(0)  加入收藏
什么是 ProtobufProtobuf是Protocol Buffers的简称,它是Google公司开发的一种数据描述语言,用于描述一种轻便高效的结构化数据存储格式,并于2008年对外开源。Protobuf可以用于...【详细内容】
2019-11-06  Tags: Protobuf  点击:(115)  评论:(0)  加入收藏
▌简易百科推荐
本文分为三个等级自顶向下地分析了glibc中内存分配与回收的过程。本文不过度关注细节,因此只是分别从arena层次、bin层次、chunk层次进行图解,而不涉及有关指针的具体操作。前...【详细内容】
2021-12-28  linux技术栈    Tags:glibc   点击:(3)  评论:(0)  加入收藏
摘 要 (OF作品展示)OF之前介绍了用python实现数据可视化、数据分析及一些小项目,但基本都是后端的知识。想要做一个好看的可视化大屏,我们还要学一些前端的知识(vue),网上有很多比...【详细内容】
2021-12-27  项目与数据管理    Tags:Vue   点击:(2)  评论:(0)  加入收藏
程序是如何被执行的&emsp;&emsp;程序是如何被执行的?许多开发者可能也没法回答这个问题,大多数人更注重的是如何编写程序,却不会太注意编写好的程序是如何被运行,这并不是一个好...【详细内容】
2021-12-23  IT学习日记    Tags:程序   点击:(9)  评论:(0)  加入收藏
阅读收获✔️1. 了解单点登录实现原理✔️2. 掌握快速使用xxl-sso接入单点登录功能一、早期的多系统登录解决方案 单系统登录解决方案的核心是cookie,cookie携带会话id在浏览器...【详细内容】
2021-12-23  程序yuan    Tags:单点登录(   点击:(8)  评论:(0)  加入收藏
下载Eclipse RCP IDE如果你电脑上还没有安装Eclipse,那么请到这里下载对应版本的软件进行安装。具体的安装步骤就不在这赘述了。创建第一个标准Eclipse RCP应用(总共分为六步)1...【详细内容】
2021-12-22  阿福ChrisYuan    Tags:RCP应用   点击:(7)  评论:(0)  加入收藏
今天想简单聊一聊 Token 的 Value Capture,就是币的价值问题。首先说明啊,这个话题包含的内容非常之光,Token 的经济学设计也可以包含诸多问题,所以几乎不可能把这个问题说的清...【详细内容】
2021-12-21  唐少华TSH    Tags:Token   点击:(10)  评论:(0)  加入收藏
实现效果:假如有10条数据,分组展示,默认在当前页面展示4个,点击换一批,从第5个开始继续展示,到最后一组,再重新返回到第一组 data() { return { qList: [], //处理后...【详细内容】
2021-12-17  Mason程    Tags:VUE   点击:(14)  评论:(0)  加入收藏
什么是性能调优?(what) 为什么需要性能调优?(why) 什么时候需要性能调优?(when) 什么地方需要性能调优?(where) 什么时候来进行性能调优?(who) 怎么样进行性能调优?(How) 硬件配...【详细内容】
2021-12-16  软件测试小p    Tags:性能调优   点击:(20)  评论:(0)  加入收藏
Tasker 是一款适用于 Android 设备的高级自动化应用,它可以通过脚本让重复性的操作自动运行,提高效率。 不知道从哪里听说的抖音 app 会导致 OLED 屏幕烧屏。于是就现学现卖,自...【详细内容】
2021-12-15  ITBang    Tags:抖音防烧屏   点击:(25)  评论:(0)  加入收藏
11 月 23 日,Rust Moderation Team(审核团队)在 GitHub 上发布了辞职公告,即刻生效。根据公告,审核团队集体辞职是为了抗议 Rust 核心团队(Core team)在执行社区行为准则和标准上...【详细内容】
2021-12-15  InfoQ    Tags:Rust   点击:(25)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条