聊聊RPC、gRPC 、Twirp、 ProtoBuf那些事
# RPC gRPC Twirp ProtoBuf之間的關(guān)系
簡單介紹這幾個項目的關(guān)系:
- ProtoBuf是一種序列化數(shù)據(jù)結(jié)構(gòu)的協(xié)議
- protoc是一個能把proto數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)換為各種語言代碼的工具
- RPC是一種通信協(xié)議
- gRPC是一種使用ProtoBuf作為接口描述語言的一個RPC實現(xiàn)方案
# RPC
在分布式計算,遠(yuǎn)程過程調(diào)用(Remote Procedure Call,縮寫為 RPC)是一個計算機通信協(xié)議。該協(xié)議允許運行于一臺計算機的程序調(diào)用另一個地址空間(通常為一個開放網(wǎng)絡(luò)的一臺計算機)的子程序,而程序員就像**調(diào)用本地程序一樣**,無需額外地為這個交互作用編程(無需關(guān)注細(xì)節(jié))。RPC是一種**服務(wù)器-客戶端(Client/Server)模式**,經(jīng)典實現(xiàn)是一個通過發(fā)送請求-接受回應(yīng)進(jìn)行信息交互的系統(tǒng)。
RPC是一種進(jìn)程間通信的模式,程序分布在不同的地址空間里。如果在同一主機里,RPC可以通過不同的虛擬地址空間(即便使用相同的物理地址)進(jìn)行通訊,而在不同的主機間,則通過不同的物理地址進(jìn)行交互。許多技術(shù)(常常是不兼容)都是基于這種概念而實現(xiàn)的。

# ProtoBuf
Protocol Buffers(簡稱ProtoBuf)是一種序列化數(shù)據(jù)結(jié)構(gòu)的協(xié)議。對于透過管道(pipeline)或存儲資料進(jìn)行通信的程序開發(fā)上是很有用的。這個方法包含一個接口描述語言,描述一些數(shù)據(jù)結(jié)構(gòu),并提供程序工具根據(jù)這些>描述產(chǎn)生代碼,用于將這些數(shù)據(jù)結(jié)構(gòu)產(chǎn)生或解析資料流。[官方文檔-Language Guide (proto3)](https://protobuf.dev/overview/)
# Protoc([Protocol Compiler](https://github.com/protocolbuffers/protobuf)編譯器)
要生成Java、Python、C ++、Go、Ruby、Objective-C或C#代碼,您需要使用.proto文件中定義的消息類型,需要在.proto上運行協(xié)議緩沖區(qū)編譯器協(xié)議。如果尚未安裝編譯器,請下載軟件包并按照自述文件中的說明進(jìn)行操作。對于Go,還需要為編譯器安裝一個特殊的代碼生成器插件,請閱讀Go Generated Code。
## protoc-gen-go([生成go代碼插件](https://github.com/protocolbuffers/protobuf-go))
protocol buffer編譯器需要一個插件來生成Go代碼
# GRPC
gRPC(gRPC Remote Procedure Calls)是Google發(fā)起的一個開源遠(yuǎn)程過程調(diào)用(Remote procedure call)系統(tǒng)。該系統(tǒng)基于HTTP/2協(xié)議傳輸,使用Protocol Buffers作為接口描述語言。***實現(xiàn)RPC協(xié)議的框架***。
其他功能:
- 認(rèn)證(authentication)
- 雙向流(bidirectional streaming)
- 流控制(flow control)
- 超時(timeouts)
可能的使用場景:
- 內(nèi)部微服務(wù)之間的通信。
- 高數(shù)據(jù)負(fù)載(gRPC 使用協(xié)議緩沖區(qū),其速度最高可比 REST 調(diào)用快七倍)。
- 您只需要一個簡單的服務(wù)定義,不需要編寫完整的客戶端庫。
- 在gRPC服務(wù)器中使用流式傳輸gRPC來構(gòu)建響應(yīng)更快的應(yīng)用和 API。
# Twirp[是一個基于 Google Protobuf 的 RPC 框架](https://twitchtv.github.io/twirp/docs/intro.html)
Twirp通過在.proto文件中定義服務(wù),然后自動生產(chǎn)服務(wù)器和客戶端的代碼。讓我們可以將更多的精力放在業(yè)務(wù)邏輯上。咦?這不就是 gRPC 嗎?不同的是,gRPC 自己實現(xiàn)了一套 HTTP 服務(wù)器和網(wǎng)絡(luò)傳輸層,twirp 使用標(biāo)準(zhǔn)庫net/http。另外 gRPC 只支持 HTTP/2 協(xié)議,twirp 還可以運行在 HTTP 1.1 之上。同時 twirp 還可以使用 JSON 格式交互。當(dāng)然并不是說 twirp 比 gRPC 好,只是多了解一種框架也就多了一個選擇。

# kratos框架項目結(jié)構(gòu):
```bash
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── api
│?? └── helloworld
│?? ? ? ├── helloworld.pb.go ? // protobuf生成的go的代碼
│?? ? ? ├── helloworld.proto ? // proto協(xié)議文件原始定義
│?? ? ? ├── helloworld_grpc.pb.go ? ?// --go_out=plugins=grpc:service go插件生成的grpc協(xié)議文件
│?? ? ? └── v1
│?? ? ? ? ? ├── error_reason.pb.go
│?? ? ? ? ? ├── error_reason.proto
│?? ? ? ? ? ├── greeter.pb.go
│?? ? ? ? ? ├── greeter.proto
│?? ? ? ? ? ├── greeter_grpc.pb.go ? ?// grpc客戶端/服務(wù)端rpc調(diào)用
│?? ? ? ? ? └── greeter_http.pb.go ? ?// restful api net/http 調(diào)用
├── cmd
│?? └── helloworld
│?? ? ? ├── main.go
│?? ? ? ├── wire.go
│?? ? ? └── wire_gen.go
├── configs
│?? └── config.yaml
├── go.mod
├── go.sum
├── internal
│?? ├── biz
│?? │?? ├── README.md
│?? │?? ├── biz.go
│?? │?? └── greeter.go
│?? ├── conf
│?? │?? ├── conf.pb.go
│?? │?? └── conf.proto
│?? ├── data
│?? │?? ├── README.md
│?? │?? ├── data.go
│?? │?? └── greeter.go
│?? ├── server
│?? │?? ├── grpc.go
│?? │?? ├── http.go
│?? │?? └── server.go
│?? └── service
│?? ? ? ├── README.md
│?? ? ? ├── greeter.go
│?? ? ? ├── helloworld.go
│?? ? ? └── service.go
├── openapi.yaml
└── third_party
? ?├── README.md
? ?├── errors
? ?│?? └── errors.proto
? ?├── google
? ?│?? ├── api
? ?│?? └── v3
? ?│?? ? ? ├── annotations.proto
? ?│?? ? ? └── openapi.proto
? ?└── validate
? ? ? ?├── README.md
? ? ? ?└── validate.proto
```
kratos框架我們了解到定義原始的proto文件、會幫我們生成**符合服務(wù)間調(diào)用的grpc(client/server)源代碼、以及對外暴露的restful api既符合protobuf協(xié)議又符合json格式的數(shù)據(jù)、一舉兩得,可以讓我們專注于業(yè)務(wù)邏輯的處理***。
## Kratos 一套輕量級 Go 微服務(wù)框架,包含大量微服務(wù)相關(guān)功能及工具
> 名字來源于:《戰(zhàn)神》游戲以希臘神話為背景,講述奎托斯(Kratos)由凡人成為戰(zhàn)神并展開弒神屠殺的冒險經(jīng)歷。
Principles
- 簡單:不過度設(shè)計,代碼平實簡單;
- 通用:通用業(yè)務(wù)開發(fā)所需要的基礎(chǔ)庫的功能;
- 高效:提高業(yè)務(wù)迭代的效率;
- 穩(wěn)定:基礎(chǔ)庫可測試性高,覆蓋率高,有線上實踐安全可靠;
- 健壯:通過良好的基礎(chǔ)庫設(shè)計,減少錯用;
- 高性能:性能高,但不特定為了性能做 hack 優(yōu)化,引入 unsafe ;
- 擴展性:良好的接口設(shè)計,來擴展實現(xiàn),或者通過新增基礎(chǔ)庫目錄來擴展功能;
- 容錯性:為失敗設(shè)計,大量引入對 SRE 的理解,魯棒性高;
- 工具鏈:包含大量工具鏈,比如 cache 代碼生成,lint 工具等等
## Features
- APIs :協(xié)議通信以 HTTP/gRPC 為基礎(chǔ),通過 Protobuf 進(jìn)行定義;
- Errors :通過 Protobuf 的 Enum 作為錯誤碼定義,以及工具生成判定接口;
- Metadata :在協(xié)議通信 HTTP/gRPC 中,通過 Middleware 規(guī)范化服務(wù)元信息傳遞;
- Config :支持多數(shù)據(jù)源方式,進(jìn)行配置合并鋪平,通過 Atomic 方式支持動態(tài)配置;
- Logger :標(biāo)準(zhǔn)日志接口,可方便集成三方 log 庫,并可通過 fluentd 收集日志;
- Metrics :統(tǒng)一指標(biāo)接口,可以實現(xiàn)各種指標(biāo)系統(tǒng),默認(rèn)集成 Prometheus;
- Tracing :遵循 OpenTelemetry 規(guī)范定義,以實現(xiàn)微服務(wù)鏈路追蹤;
- Encoding :支持 Accept 和 Content-Type 進(jìn)行自動選擇內(nèi)容編碼;
- Transport :通用的 HTTP /gRPC 傳輸層,實現(xiàn)統(tǒng)一的 Middleware 插件支持;
- Registry :實現(xiàn)統(tǒng)一注冊中心接口,可插件化對接各種注冊中心;
- Validation: 通過Protobuf統(tǒng)一定義校驗規(guī)則,并同時適用于HTTP/gRPC服務(wù).
- SwaggerAPI: 通過集成第三方Swagger插件 能夠自動生成Swagger API json并啟動一個內(nèi)置的Swagger UI服務(wù).
## kratos 強大的grpc和http兼容能力、服務(wù)端注冊源碼
### http server
```go
package server
import (
? v1 "helloworld/api/helloworld/v1"
? "helloworld/internal/conf"
? "helloworld/internal/service"
? "github.com/go-kratos/kratos/v2/log"
? "github.com/go-kratos/kratos/v2/middleware/recovery"
? "github.com/go-kratos/kratos/v2/transport/http"
)
// NewHTTPServer new an HTTP server.
func NewHTTPServer(c *conf.Server, greeter *service.GreeterService, logger log.Logger) *http.Server {
? var opts = []http.ServerOption{
? ? ?http.Middleware(
? ? ? ? recovery.Recovery(),
? ? ?),
? }
? if c.Http.Network != "" {
? ? ?opts = append(opts, http.Network(c.Http.Network))
? }
? if c.Http.Addr != "" {
? ? ?opts = append(opts, http.Address(c.Http.Addr))
? }
? if c.Http.Timeout != nil {
? ? ?opts = append(opts, http.Timeout(c.Http.Timeout.AsDuration()))
? }
? srv := http.NewServer(opts...)
? v1.RegisterGreeterHTTPServer(srv, greeter)
? return srv
}
```
### grpc server
```go
package server
import (
? v1 "helloworld/api/helloworld/v1"
? "helloworld/internal/conf"
? "helloworld/internal/service"
? "github.com/go-kratos/kratos/v2/log"
? "github.com/go-kratos/kratos/v2/middleware/recovery"
? "github.com/go-kratos/kratos/v2/transport/grpc"
)
// NewGRPCServer new a gRPC server.
func NewGRPCServer(c *conf.Server, greeter *service.GreeterService, logger log.Logger) *grpc.Server {
? var opts = []grpc.ServerOption{
? ? ?grpc.Middleware(
? ? ? ? recovery.Recovery(),
? ? ?),
? }
? if c.Grpc.Network != "" {
? ? ?opts = append(opts, grpc.Network(c.Grpc.Network))
? }
? if c.Grpc.Addr != "" {
? ? ?opts = append(opts, grpc.Address(c.Grpc.Addr))
? }
? if c.Grpc.Timeout != nil {
? ? ?opts = append(opts, grpc.Timeout(c.Grpc.Timeout.AsDuration()))
? }
? srv := grpc.NewServer(opts...)
? v1.RegisterGreeterServer(srv, greeter)
? return srv
}
```
以上kratos框架的server端可以同時監(jiān)聽grpc server和http server
### protoc-gen-go-grpc
```go
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc ? ? ? ? ? ? v3.19.3
// source: helloworld/v1/greeter.proto
package v1
import (
? context "context"
? grpc "google.golang.org/grpc"
? codes "google.golang.org/grpc/codes"
? status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// GreeterClient is the client API for Greeter service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type GreeterClient interface {
? // Sends a greeting
? SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
}
type greeterClient struct {
? cc grpc.ClientConnInterface
}
func NewGreeterClient(cc grpc.ClientConnInterface) GreeterClient {
? return &greeterClient{cc}
}
func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {
? out := new(HelloReply)
? err := c.cc.Invoke(ctx, "/helloworld.v1.Greeter/SayHello", in, out, opts...)
? if err != nil {
? ? ?return nil, err
? }
? return out, nil
}
// GreeterServer is the server API for Greeter service.
// All implementations must embed UnimplementedGreeterServer
// for forward compatibility
type GreeterServer interface {
? // Sends a greeting
? SayHello(context.Context, *HelloRequest) (*HelloReply, error)
? mustEmbedUnimplementedGreeterServer()
}
// UnimplementedGreeterServer must be embedded to have forward compatible implementations.
type UnimplementedGreeterServer struct {
}
func (UnimplementedGreeterServer) SayHello(context.Context, *HelloRequest) (*HelloReply, error) {
? return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented")
}
func (UnimplementedGreeterServer) mustEmbedUnimplementedGreeterServer() {}
// UnsafeGreeterServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to GreeterServer will
// result in compilation errors.
type UnsafeGreeterServer interface {
? mustEmbedUnimplementedGreeterServer()
}
func RegisterGreeterServer(s grpc.ServiceRegistrar, srv GreeterServer) {
? s.RegisterService(&Greeter_ServiceDesc, srv)
}
func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
? in := new(HelloRequest)
? if err := dec(in); err != nil {
? ? ?return nil, err
? }
? if interceptor == nil {
? ? ?return srv.(GreeterServer).SayHello(ctx, in)
? }
? info := &grpc.UnaryServerInfo{
? ? ?Server: ? ? srv,
? ? ?FullMethod: "/helloworld.v1.Greeter/SayHello",
? }
? handler := func(ctx context.Context, req interface{}) (interface{}, error) {
? ? ?return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest))
? }
? return interceptor(ctx, in, info, handler)
}
// Greeter_ServiceDesc is the grpc.ServiceDesc for Greeter service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Greeter_ServiceDesc = grpc.ServiceDesc{
? ServiceName: "helloworld.v1.Greeter",
? HandlerType: (*GreeterServer)(nil),
? Methods: []grpc.MethodDesc{
? ? ?{
? ? ? ? MethodName: "SayHello",
? ? ? ? Handler: ? ?_Greeter_SayHello_Handler,
? ? ?},
? },
? Streams: ?[]grpc.StreamDesc{},
? Metadata: "helloworld/v1/greeter.proto",
}
```
### protoc-gen-go-http
```go
// Code generated by protoc-gen-go-http. DO NOT EDIT.
// versions:
// protoc-gen-go-http v2.1.3
package v1
import (
? context "context"
? http "github.com/go-kratos/kratos/v2/transport/http"
? binding "github.com/go-kratos/kratos/v2/transport/http/binding"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the kratos package it is being compiled against.
var _ = new(context.Context)
var _ = binding.EncodeURL
const _ = http.SupportPackageIsVersion1
type GreeterHTTPServer interface {
? SayHello(context.Context, *HelloRequest) (*HelloReply, error)
}
func RegisterGreeterHTTPServer(s *http.Server, srv GreeterHTTPServer) {
? r := s.Route("/")
? r.GET("/helloworld/{name}", _Greeter_SayHello0_HTTP_Handler(srv))
}
func _Greeter_SayHello0_HTTP_Handler(srv GreeterHTTPServer) func(ctx http.Context) error {
? return func(ctx http.Context) error {
? ? ?var in HelloRequest
? ? ?if err := ctx.BindQuery(&in); err != nil {
? ? ? ? return err
? ? ?}
? ? ?if err := ctx.BindVars(&in); err != nil {
? ? ? ? return err
? ? ?}
? ? ?http.SetOperation(ctx, "/helloworld.v1.Greeter/SayHello")
? ? ?h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
? ? ? ? return srv.SayHello(ctx, req.(*HelloRequest))
? ? ?})
? ? ?out, err := h(ctx, &in)
? ? ?if err != nil {
? ? ? ? return err
? ? ?}
? ? ?reply := out.(*HelloReply)
? ? ?return ctx.Result(200, reply)
? }
}
type GreeterHTTPClient interface {
? SayHello(ctx context.Context, req *HelloRequest, opts ...http.CallOption) (rsp *HelloReply, err error)
}
type GreeterHTTPClientImpl struct {
? cc *http.Client
}
func NewGreeterHTTPClient(client *http.Client) GreeterHTTPClient {
? return &GreeterHTTPClientImpl{client}
}
func (c *GreeterHTTPClientImpl) SayHello(ctx context.Context, in *HelloRequest, opts ...http.CallOption) (*HelloReply, error) {
? var out HelloReply
? pattern := "/helloworld/{name}"
? path := binding.EncodeURL(pattern, in, true)
? opts = append(opts, http.Operation("/helloworld.v1.Greeter/SayHello"))
? opts = append(opts, http.PathTemplate(pattern))
? err := c.cc.Invoke(ctx, "GET", path, nil, &out, opts...)
? if err != nil {
? ? ?return nil, err
? }
? return &out, err
}
```
# Twirp 框架demo項目結(jié)構(gòu)
```bash
~/go/src/twirp-helloworld/example ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 19:59:52
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?19:59:52
│?? ? ? └── statter.go
├── gen.go
├── service.pb.go
│?? ├── cmd
│?? │?? ├── client
│?? │?? │?? └── main.go
│?? │?? └── server
│?? │?? ? ? ├── README.md
│?? │?? ? ? ├── main.go
│?? │?? ? ? └── statter.go
│?? ├── gen.go
│?? ├── service.pb.go
│?? ├── service.proto
│?? └── service.twirp.go ? // go插件twirp生成協(xié)議文件 --twirp_out=. rpc/haberdasher/service.proto
├── go.mod
└── go.sum
```
## Twirp框架rpc實現(xiàn)
```go
// Copyright 2018 Twitch Interactive, Inc. ?All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may not
// use this file except in compliance with the License. A copy of the License is
// located at
//
// ? ? http://www.apache.org/licenses/LICENSE-2.0
//
// or in the "license" file accompanying this file. This file is distributed on
// an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.
package main
import (
? "context"
? "log"
? "math/rand"
? "net/http"
? "os"
? "github.com/twitchtv/twirp"
? "github.com/twitchtv/twirp/example"
? "github.com/twitchtv/twirp/hooks/statsd"
)
type randomHaberdasher struct{}
func (h *randomHaberdasher) MakeHat(ctx context.Context, size *example.Size) (*example.Hat, error) {
? if size.Inches <= 0 {
? ? ?return nil, twirp.InvalidArgumentError("Inches", "I can't make a hat that small!")
? }
? colors := []string{"white", "black", "brown", "red", "blue"}
? names := []string{"bowler", "baseball cap", "top hat", "derby"}
? return &example.Hat{
? ? ?Size: ?size.Inches,
? ? ?Color: colors[rand.Intn(len(colors))],
? ? ?Name: ?names[rand.Intn(len(names))],
? }, nil
}
func main() {
? hook := statsd.NewStatsdServerHooks(LoggingStatter{os.Stderr})
? server := example.NewHaberdasherServer(&randomHaberdasher{}, hook)
? log.Fatal(http.ListenAndServe(":8080", server))
}
```
### RestFulApi
```shell
curl --request "POST" \
? ?--header "Content-Type: application/json" \
? ?--data '{"inches": 0}' \
? ?http://127.0.0.1:8080/twirp/twitch.twirp.example.Haberdasher/MakeHat
{"code":"invalid_argument","msg":"Inches I can't make a hat that small!","meta":{"argument":"Inches"}}%
```
### protobuf 格式
```go
// Copyright 2018 Twitch Interactive, Inc. ?All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may not
// use this file except in compliance with the License. A copy of the License is
// located at
//
// ? ? http://www.apache.org/licenses/LICENSE-2.0
//
// or in the "license" file accompanying this file. This file is distributed on
// an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.
package main
import (
? "context"
? "fmt"
? "log"
? "net/http"
? "github.com/twitchtv/twirp"
? "github.com/twitchtv/twirp/example"
)
func main() {
? client := example.NewHaberdasherJSONClient("http://localhost:8080", &http.Client{})
? ? ? ? ? ? ?//proto:=example.NewHaberdasherProtobufClient("http://localhost:8080", &http.Client{})
? var (
? ? ?hat *example.Hat
? ? ?err error
? )
? for i := 0; i < 5; i++ {
? ? ?hat, err = client.MakeHat(context.Background(), &example.Size{Inches: 12})
? ? ?if err != nil {
? ? ? ? if twerr, ok := err.(twirp.Error); ok {
? ? ? ? ? ?if twerr.Meta("retryable") != "" {
? ? ? ? ? ? ? // Log the error and go again.
? ? ? ? ? ? ? log.Printf("got error %q, retrying", twerr)
? ? ? ? ? ? ? continue
? ? ? ? ? ?}
? ? ? ? }
? ? ? ? // This was some fatal error!
? ? ? ? log.Fatal(err)
? ? ?}
? }
? fmt.Printf("%+v", hat)
}
```