찾다
백엔드 개발GolangJSON vs FlatBuffers vs 프로토콜 버퍼

서비스/마이크로서비스 간의 통신을 생각할 때 가장 먼저 떠오르는 옵션은 오래된 JSON입니다. 형식에는 다음과 같은 장점이 있으므로 이는 이유가 없습니다.

  • 컴퓨터와 사람 모두 쉽게 읽을 수 있습니다.
  • 모든 최신 프로그래밍 언어는 JSON을 읽고 생성할 수 있습니다.
  • 이전 대안인 Jurassic XML보다 훨씬 덜 장황합니다.

JSON을 사용하는 것은 기업의 일상 생활에서 개발되는 대부분의 API에 권장되는 사항입니다. 그러나 성능이 중요한 경우에는 다른 대안을 고려해야 할 수도 있습니다. 이 게시물의 목적은 애플리케이션 간 통신과 관련하여 JSON에 대한 두 가지 대안을 보여주는 것입니다.

그런데 JSON의 문제점은 무엇인가요? 장점 중 하나는 "사람이 쉽게 읽을 수 있다"는 점이지만, 이는 성능상 약점이 될 수 있다. 사실은 JSON 콘텐츠를 우리가 사용하는 프로그래밍 언어에 의해 알려진 일부 구조로 변환해야 한다는 것입니다. 이 규칙의 예외는 JSON이 기본이므로 JavaScript를 사용하는 경우입니다. 하지만 예를 들어 Go와 같은 다른 언어를 사용하는 경우 아래의 (불완전한) 코드 예에서 볼 수 있듯이 데이터를 구문 분석해야 합니다.

type event struct {
    ID      uuid.UUID
    Type    string `json:"type"`
    Source  string `json:"source"`
    Subject string `json:"subject"`
    Time    string `json:"time"`
    Data    string `json:"data"`
}

var e event
err := json.NewDecoder(data).Decode(&e)
if err != nil {
    http.Error(w, err.Error(), http.StatusBadRequest)
}

이 문제를 해결하기 위해 프로토콜 버퍼와 플랫 버퍼라는 두 가지 대안을 테스트할 수 있습니다.

프로토콜 버퍼

공식 웹사이트에 따르면 Google에서 만든 Protobuf(프로토콜 버퍼)는 다음과 같습니다.

프로토콜 버퍼는 구조화된 데이터를 직렬화하기 위한 Google의 언어 중립적이고 플랫폼 중립적이며 확장 가능한 메커니즘입니다. XML이라고 생각하면 되지만 더 작고 빠르며 더 간단합니다. 데이터를 한 번 구조화하는 방법을 정의합니다. 그런 다음 특별히 생성된 소스 코드를 사용하여 다양한 언어를 사용하는 다양한 데이터 스트림에 구조화된 데이터를 빠르게 쓰고 읽을 수 있습니다.

일반적으로 gRPC와 함께 사용되지만(반드시 그런 것은 아님) Protobuf는 JSON의 텍스트 형식에 비해 성능을 크게 향상시키는 바이너리 프로토콜입니다. 하지만 JSON과 동일한 문제가 '고통'됩니다. 즉, 이를 우리 언어의 데이터 구조로 구문 분석해야 합니다. 예를 들어 Go에서는 다음과 같습니다.

//generated code
type Event struct {
    state         protoimpl.MessageState
    sizeCache     protoimpl.SizeCache
    unknownFields protoimpl.UnknownFields

    Type    string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
    Subject string `protobuf:"bytes,2,opt,name=subject,proto3" json:"subject,omitempty"`
    Source  string `protobuf:"bytes,3,opt,name=source,proto3" json:"source,omitempty"`
    Time    string `protobuf:"bytes,4,opt,name=time,proto3" json:"time,omitempty"`
    Data    string `protobuf:"bytes,5,opt,name=data,proto3" json:"data,omitempty"`
}

e := Event{}
err := proto.Unmarshal(data, &e)
if err != nil {
    http.Error(w, err.Error(), http.StatusBadRequest)
}

바이너리 프로토콜을 채택하면 성능이 향상되지만 여전히 데이터 구문 분석 문제를 해결해야 합니다. 세 번째 경쟁자는 이 문제를 해결하는 데 중점을 두고 있습니다.

플랫버퍼

공식 홈페이지에 따르면:

FlatBuffers는 C++, C#, C, Go, Java, Kotlin, JavaScript, Lobster, Lua, TypeScript, PHP, Python, Rust 및 Swift를 위한 효율적인 크로스 플랫폼 직렬화 라이브러리입니다. 처음에는 게임 개발 및 기타 성능이 중요한 애플리케이션을 위해 Google에서 만들어졌습니다.

처음에는 게임 개발을 위해 만들어졌지만 이 게시물에서 연구하는 환경에 완벽하게 들어맞습니다. 장점은 바이너리 프로토콜일 뿐만 아니라 데이터를 구문 분석할 필요가 없다는 것입니다. 예를 들어 Go에서는 다음과 같습니다.

//generated code
e := events.GetRootAsEvent(data, 0)

//we can use the data directly
saveEvent(string(e.Type()), string(e.Source()), string(e.Subject()), string(e.Time()), string(e.Data()))

하지만 JSON에 대한 두 가지 대안이 얼마나 더 성능이 좋나요? 조사해보자...

애플리케이션

첫 번째로 든 질문은 '이것을 실제 시나리오에 어떻게 적용할 수 있을까?'였습니다. 저는 다음과 같은 시나리오를 상상했습니다.

내부 마이크로서비스 아키텍처를 갖추고 수백만 명의 고객이 매일 액세스하는 모바일 애플리케이션을 보유하고 있으며 감사 목적으로 사용자 및 시스템에서 생성된 이벤트를 저장해야 하는 회사입니다.

실제 시나리오입니다. 너무 현실적이라 제가 일하는 회사에서도 매일 같이 생활하고 있어요 :)

JSON vs FlatBuffers vs Protocol Buffers

참고: 위의 시나리오는 단순화된 것이며 팀 애플리케이션의 실제 복잡성을 나타내지는 않습니다. 교육적인 목적으로 사용됩니다.

첫 번째 단계는 프로토콜 버퍼와 플랫 버퍼에서 이벤트를 정의하는 것입니다. 둘 다 스키마를 정의하기 위한 자체 언어를 가지고 있으며 이를 사용하여 사용할 언어로 코드를 생성할 수 있습니다. 각 구성표에 대한 자세한 내용은 문서에서 쉽게 찾을 수 있으므로 다루지 않겠습니다.

event.proto 파일에는 프로토콜 버퍼 정의가 있습니다.

syntax = "proto3";
package events;

option go_package = "./events_pb";

message Event {
    string type = 1;
    string subject = 2;
    string source = 3;
    string time = 4;
    string data = 5;
}

event.fbs 파일에는 Flatbuffers에 해당하는 파일이 있습니다.

namespace events;

table Event {
    type: string;
    subject:string;
    source:string;
    time:string;
    data:string;
}

root_type Event;

다음 단계는 이러한 정의를 사용하여 필요한 코드를 생성하는 것입니다. 다음 명령은 macOS에 종속성을 설치합니다.

go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
brew install protobuf
protoc -I=. --go_out=./ event.proto
brew install flatbuffers
flatc --go event.fbs

그 결과 각 형식의 데이터를 조작하는 Go 패키지가 생성되었습니다.

요구사항이 충족되면 다음 단계는 이벤트 API를 구현하는 것이었습니다. main.go는 다음과 같습니다.

package main

import (
    "fmt"
    "net/http"
    "os"

    "github.com/go-chi/chi/v5"
    "github.com/go-chi/chi/v5/middleware"
    "github.com/google/uuid"
)

func main() {
    r := handlers()
    http.ListenAndServe(":3000", r)
}

func handlers() *chi.Mux {
    r := chi.NewRouter()
    if os.Getenv("DEBUG") != "false" {
        r.Use(middleware.Logger)
    }
    r.Post("/json", processJSON())
    r.Post("/fb", processFB())
    r.Post("/pb", processPB())
    return r
}

func saveEvent(evType, source, subject, time, data string) {
    if os.Getenv("DEBUG") != "false" {
        id := uuid.New()
        q := fmt.Sprintf("insert into event values('%s', '%s', '%s', '%s', '%s', '%s')", id, evType, source, subject, time, data)
        fmt.Println(q)
    }
    // save event to database
}

더 나은 정리를 위해 각 기능을 구분하는 파일을 만들었습니다.

package main

import (
    "encoding/json"
    "net/http"

    "github.com/google/uuid"
)

type event struct {
    ID      uuid.UUID
    Type    string `json:"type"`
    Source  string `json:"source"`
    Subject string `json:"subject"`
    Time    string `json:"time"`
    Data    string `json:"data"`
}

func processJSON() http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        var e event
        err := json.NewDecoder(r.Body).Decode(&e)
        if err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
        }
        saveEvent(e.Type, e.Source, e.Subject, e.Time, e.Data)
        w.WriteHeader(http.StatusCreated)
        w.Write([]byte("json received"))
    }
}

package main

import (
    "io"
    "net/http"

    "github.com/eminetto/post-flatbuffers/events_pb"
    "google.golang.org/protobuf/proto"
)

func processPB() http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        body := r.Body
        data, _ := io.ReadAll(body)

        e := events_pb.Event{}
        err := proto.Unmarshal(data, &e)
        if err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
        }
        saveEvent(e.GetType(), e.GetSource(), e.GetSubject(), e.GetTime(), e.GetData())
        w.WriteHeader(http.StatusCreated)
        w.Write([]byte("protobuf received"))
    }
}
package main

import (
    "io"
    "net/http"

    "github.com/eminetto/post-flatbuffers/events"
)

func processFB() http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        body := r.Body
        data, _ := io.ReadAll(body)
        e := events.GetRootAsEvent(data, 0)
        saveEvent(string(e.Type()), string(e.Source()), string(e.Subject()), string(e.Time()), string(e.Data()))
        w.WriteHeader(http.StatusCreated)
        w.Write([]byte("flatbuffer received"))
    }
}

In the functions processPB() and processFB(), we can see how the generated packages are used to manipulate the data.

Benchmark

The last step of our proof of concept is generating the benchmark to compare the formats. I used the Go stdlib benchmark package for this.

The file main_test.go has tests for each format:

package main

import (
    "bytes"
    "fmt"
    "net/http"
    "net/http/httptest"
    "os"
    "strings"
    "testing"

    "github.com/eminetto/post-flatbuffers/events"
    "github.com/eminetto/post-flatbuffers/events_pb"
    flatbuffers "github.com/google/flatbuffers/go"
    "google.golang.org/protobuf/proto"
)

func benchSetup() {
    os.Setenv("DEBUG", "false")
}

func BenchmarkJSON(b *testing.B) {
    benchSetup()
    r := handlers()
    payload := fmt.Sprintf(`{
        "type": "button.clicked",
        "source": "Login",
        "subject": "user1000",
        "time": "2018-04-05T17:31:00Z",
        "data": "User clicked because X"}`)
    for i := 0; i 



<p>It generates an event in each format and sends it to the API.</p>

<p>When we run the benchmark, we have the following result:<br>
</p>

<pre class="brush:php;toolbar:false">Running tool: /opt/homebrew/bin/go test -benchmem -run=^$ -coverprofile=/var/folders/vn/gff4w90d37xbfc_2tn3616h40000gn/T/vscode-gojAS4GO/go-code-cover -bench . github.com/eminetto/post-flatbuffers/cmd/api -failfast -v

goos: darwin
goarch: arm64
pkg: github.com/eminetto/post-flatbuffers/cmd/api
BenchmarkJSON
BenchmarkJSON-8               658386          1732 ns/op        2288 B/op         26 allocs/op
BenchmarkFlatBuffers
BenchmarkFlatBuffers-8       1749194           640.5 ns/op      1856 B/op         21 allocs/op
BenchmarkProtobuffer
BenchmarkProtobuffer-8       1497356           696.9 ns/op      1952 B/op         21 allocs/op
PASS
coverage: 77.5% of statements
ok      github.com/eminetto/post-flatbuffers/cmd/api    5.042s

If this is the first time you have analyzed the results of a Go benchmark, I recommend reading this post, where the author describes the details of each column and its meaning.

To make it easier to visualize, I created graphs for the most critical information generated by the benchmark:

‌Number of iterations (higher is better)

JSON vs FlatBuffers vs Protocol Buffers

Nanoseconds per operation (lower is better)

JSON vs FlatBuffers vs Protocol Buffers

Number of bytes allocated per operation (lower is better)

JSON vs FlatBuffers vs Protocol Buffers

Number of allocations per operation (lower is better)

JSON vs FlatBuffers vs Protocol Buffers

Conclusion

The numbers show a great advantage of binary protocols over JSON, especially Flatbuffers. This advantage is that we do not need to parse the data into structures of the language we are using.

Should you refactor your applications to replace JSON with Flatbuffers? Not necessarily. Performance is just one factor that teams must consider when selecting a communication protocol between their services and applications. But if your application receives billions of requests per day, performance improvements like those presented in this post can make a big difference in terms of costs and user experience.

The codes presented here can be found in this repository. I made the examples using the Go language, but both Protocol Buffers and Flatbuffers support different programming languages, so I would love to see other versions of these comparisons. Additionally, other benchmarks can be used, such as network consumption, CPU, etc. (since we only compare memory here).

I hope this post serves as an introduction to these formats and an incentive for new tests and experiments.

Originally published at https://eltonminetto.dev on August 05, 2024

위 내용은 JSON vs FlatBuffers vs 프로토콜 버퍼의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
Go Language Pack 가져 오기 : 밑줄과 밑줄이없는 밑줄의 차이점은 무엇입니까?Go Language Pack 가져 오기 : 밑줄과 밑줄이없는 밑줄의 차이점은 무엇입니까?Mar 03, 2025 pm 05:17 PM

이 기사에서는 GO의 패키지 가져 오기 메커니즘을 설명합니다. 명명 된 수입 (예 : 가져 오기 & quot; fmt & quot;) 및 빈 가져 오기 (예 : import _ & quot; fmt & quot;). 명명 된 가져 오기는 패키지 내용을 액세스 할 수있게하고 빈 수입은 t 만 실행합니다.

Beego 프레임 워크에서 페이지간에 단기 정보 전송을 구현하는 방법은 무엇입니까?Beego 프레임 워크에서 페이지간에 단기 정보 전송을 구현하는 방법은 무엇입니까?Mar 03, 2025 pm 05:22 PM

이 기사에서는 웹 애플리케이션에서 페이지 간 데이터 전송에 대한 Beego의 NewFlash () 기능을 설명합니다. NewFlash ()를 사용하여 컨트롤러간에 임시 메시지 (성공, 오류, 경고)를 표시하여 세션 메커니즘을 활용하는 데 중점을 둡니다. 한계

MySQL 쿼리 결과 목록을 GO 언어로 사용자 정의 구조 슬라이스로 변환하는 방법은 무엇입니까?MySQL 쿼리 결과 목록을 GO 언어로 사용자 정의 구조 슬라이스로 변환하는 방법은 무엇입니까?Mar 03, 2025 pm 05:18 PM

이 기사에서는 MySQL 쿼리 결과를 GO 구조 슬라이스로 효율적으로 변환합니다. 수동 구문 분석을 피하고 최적의 성능을 위해 데이터베이스/SQL의 스캔 방법을 사용하는 것을 강조합니다. DB 태그 및 Robus를 사용한 구조물 필드 매핑에 대한 모범 사례

이동 중에 테스트를 위해 모의 개체와 스터브를 작성하려면 어떻게합니까?이동 중에 테스트를 위해 모의 개체와 스터브를 작성하려면 어떻게합니까?Mar 10, 2025 pm 05:38 PM

이 기사는 단위 테스트를 위해 이동 중에 모의와 스터브를 만드는 것을 보여줍니다. 인터페이스 사용을 강조하고 모의 구현의 예를 제공하며 모의 집중 유지 및 어설 션 라이브러리 사용과 같은 모범 사례에 대해 설명합니다. 기사

GO에서 제네릭에 대한 사용자 정의 유형 제약 조건을 어떻게 정의 할 수 있습니까?GO에서 제네릭에 대한 사용자 정의 유형 제약 조건을 어떻게 정의 할 수 있습니까?Mar 10, 2025 pm 03:20 PM

이 기사에서는 GO의 제네릭에 대한 사용자 정의 유형 제약 조건을 살펴 봅니다. 인터페이스가 일반 함수에 대한 최소 유형 ​​요구 사항을 정의하여 유형 안전 및 코드 재사성을 향상시키는 방법에 대해 자세히 설명합니다. 이 기사는 또한 한계와 모범 사례에 대해 설명합니다

편리하게 GO 언어로 파일을 작성하는 방법?편리하게 GO 언어로 파일을 작성하는 방법?Mar 03, 2025 pm 05:15 PM

이 기사는 OS.WriteFile (작은 파일에 적합)과 OS.OpenFile 및 Buffered Writes (큰 파일에 최적)를 비교하여 효율적인 파일 쓰기를 자세히 설명합니다. 강력한 오류 처리, 연기 사용 및 특정 오류 확인을 강조합니다.

GO에서 단위 테스트를 어떻게 작성합니까?GO에서 단위 테스트를 어떻게 작성합니까?Mar 21, 2025 pm 06:34 PM

이 기사는 GO에서 단위 테스트 작성, 모범 사례, 조롱 기술 및 효율적인 테스트 관리를위한 도구를 다루는 것에 대해 논의합니다.

추적 도구를 사용하여 GO 응용 프로그램의 실행 흐름을 이해하려면 어떻게해야합니까?추적 도구를 사용하여 GO 응용 프로그램의 실행 흐름을 이해하려면 어떻게해야합니까?Mar 10, 2025 pm 05:36 PM

이 기사는 추적 도구를 사용하여 GO 응용 프로그램 실행 흐름을 분석합니다. 수동 및 자동 계측 기술, Jaeger, Zipkin 및 OpenTelemetry와 같은 도구 비교 및 ​​효과적인 데이터 시각화를 강조합니다.

See all articles

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

AI Hentai Generator

AI Hentai Generator

AI Hentai를 무료로 생성하십시오.

뜨거운 도구

mPDF

mPDF

mPDF는 UTF-8로 인코딩된 HTML에서 PDF 파일을 생성할 수 있는 PHP 라이브러리입니다. 원저자인 Ian Back은 자신의 웹 사이트에서 "즉시" PDF 파일을 출력하고 다양한 언어를 처리하기 위해 mPDF를 작성했습니다. HTML2FPDF와 같은 원본 스크립트보다 유니코드 글꼴을 사용할 때 속도가 느리고 더 큰 파일을 생성하지만 CSS 스타일 등을 지원하고 많은 개선 사항이 있습니다. RTL(아랍어, 히브리어), CJK(중국어, 일본어, 한국어)를 포함한 거의 모든 언어를 지원합니다. 중첩된 블록 수준 요소(예: P, DIV)를 지원합니다.

SublimeText3 영어 버전

SublimeText3 영어 버전

권장 사항: Win 버전, 코드 프롬프트 지원!

Dreamweaver Mac版

Dreamweaver Mac版

시각적 웹 개발 도구

Atom Editor Mac 버전 다운로드

Atom Editor Mac 버전 다운로드

가장 인기 있는 오픈 소스 편집기

스튜디오 13.0.1 보내기

스튜디오 13.0.1 보내기

강력한 PHP 통합 개발 환경