惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

P
Privacy & Cybersecurity Law Blog
Engineering at Meta
Engineering at Meta
Forbes - Security
Forbes - Security
MongoDB | Blog
MongoDB | Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
A
About on SuperTechFans
量子位
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
雷峰网
雷峰网
腾讯CDC
P
Proofpoint News Feed
S
Schneier on Security
S
Secure Thoughts
V
Visual Studio Blog
Help Net Security
Help Net Security
The Hacker News
The Hacker News
C
Cyber Attacks, Cyber Crime and Cyber Security
P
Privacy International News Feed
SecWiki News
SecWiki News
S
SegmentFault 最新的问题
T
Threatpost
小众软件
小众软件
MyScale Blog
MyScale Blog
F
Fortinet All Blogs
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
P
Proofpoint News Feed
T
Tailwind CSS Blog
I
Intezer
C
CERT Recently Published Vulnerability Notes
U
Unit 42
V
V2EX
Cyberwarzone
Cyberwarzone
Recorded Future
Recorded Future
O
OpenAI News
Project Zero
Project Zero
有赞技术团队
有赞技术团队
Google DeepMind News
Google DeepMind News
Last Week in AI
Last Week in AI
Hugging Face - Blog
Hugging Face - Blog
Know Your Adversary
Know Your Adversary
C
Cybersecurity and Infrastructure Security Agency CISA
Scott Helme
Scott Helme
V2EX - 技术
V2EX - 技术
博客园 - 叶小钗
S
Securelist
A
Arctic Wolf
The Cloudflare Blog
W
WeLiveSecurity
T
Threat Research - Cisco Blogs
博客园 - Franky

ipfans's Blog

程序员的提示工程手册 AI 产品的破局之道:以人为本 使用 Go 开发 AI Agent的选择:Genkit for Go RAG 技术在实际工程中的应用:OpenAI 的最佳实践分享 从零学习 Hypothetical Document Embeddings (HyDE) - 2 从零学习 Hypothetical Document Embeddings (HyDE) - 1 No GIL Python 的冒险 使用 Ollama 快速部署本地开源大语言模型 使用子解释器运行Python并行应用 Twirp基本概念:Hooks和Interceptors 去年的一点小工作(1):从BFF谈起 Byebye 2022, Hello 2023 给hugo添加mermaid支持 一些实用工具列表 Go 1.17 泛型尝鲜 Go Web应用中常见的反模式 什么是事件建模Event Modeling? 构建属于你自己的dapr绑定组件 构建属于你自己的dapr服务发现
Twirp初相识
2023-01-25 · via ipfans's Blog

Featured image of post Twirp初相识

什么是Twirp?

Twirp是Twitch在2018年开源的RPC框架。正如同他们在发布文章中说的那样,RPC相对于普通的RESTful API更方便设计、组织和维护,让开发者更加专注于业务。但是同样的,在Go社区中重要的gRPC方案严重与HTTP/2绑定,这也成为一个制约其推广的问题:HTTP/2的复杂性其实并不必要;与Go Runtime的割裂也是另外一个问题,导致部分优化难以直接通过升级Go版本在gRPC上显现。

Twirp则选择保留了部分好的地方:使用Protobuf这个IDL约束请求/返回类型,这样可以最大化借助Protobuf带来的优势,生成客户端和服务端代码。但是Twirp选择与Go标准库集成,这样可以更好的利用Go本身升级带来的优化。这同时也保证了Twirp本身的简洁性。同时,你也可以很方便的使用cURL等传统工具,借助json请求测试,而不需要手工处理二进制数据。同样的,借助Go标准库,未来Twirp可以更好的升级成HTTP/3而不是像gRPC一样等待上游更新。当然如果你更倾向于使用gRPC相关的实践,那么connect-go可能是你的另外一个不错选择。

当然,如果说缺点,Twirp并不完美:小众的社区,缺少生态,缺少相关信息内容等等。不过这些仍旧是瑕不掩瑜。毕竟实现一个相关的功能其实并不那么复杂。

如何使用Twirp

Twirp虽然官网比较简单,甚至社区也不是很大的样子,但是基本上需求的数据基本都可以在官网上找到入口。但是这也有个问题,导致整个流程对新手并不友好,有比较高的上手门槛。接下来的内容主要是完善这部分的内容,方便新手用户使用。

安装Protobuf相关工具

由于Twirp同样使用Protobuf,我们需要使用相关工具。首先是Protobuf,接下来是一些protoc-gen工具:

brew install protobuf  # Mac Only
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install github.com/twitchtv/twirp/protoc-gen-twirp@latest

可选项:Buf

Buf是一个Protobuf管理工具,帮助你实现Schema Driven Development实践。它提供了一个CLI管理工具(支持lint,生成和破坏性检查等功能)和类似注册中心机制的BSR(Buf Schema Registry),你可以在这里管理你的Schema版本和引用其他公开服务的Schema。不使用Buf并不会带来功能缺失,并且Buf提供了付费SaaS服务(测试期间免费),可以根据你的情况选择是否使用。

brew install bufbuild/buf/buf  # Mac Only

可选项:Taskfile

Taskfile是我常用来替代Makefile的工具。这并不是必须的工具,你同样可以使用手工执行命令行和Makefile命令进行。事实上,使用Makefile其实可以更好的在Jenkins之类的pipeline里执行,但是对Github Action等现代pipeline而言,区别并不大。

brew install go-task/tap/go-task  # Mac Only

生成项目文件

这里我们使用一个简单的Greeter程序演示使用。假设我们已经存在了一个Go的空项目,那么我们接下来需要创建对应的目录和文件。按照官方的建议,我们可以使用如下结构创建我们的项目,你可以在Github上查看完整的代码

$ tree .
.
├── README.md
├── Taskfile.yaml
├── buf.gen-ts.yaml
├── buf.gen.yaml
├── buf.yaml
├── build
├── client
│   ├── package.json
│   ├── pnpm-lock.yaml
│   └── src
│       └── protoc-gen-twirp-es.ts
├── cmd
│   └── greeter
│       ├── main.go
│       └── main_test.go
├── go.mod
├── go.sum
├── internal
│   └── greetersvc
│       └── service.go
└── rpc
    └── greeter
        └── v1
            └── service.proto

编写服务端

我们先看一下greeter的服务定义:

syntax = "proto3";

package rpc.greeter.v1;
option go_package = "rpc/greeter/v1";

service GreeterService {
    rpc SayHello (SayHelloRequest) returns (SayHelloResponse);
}

message SayHelloRequest {
    string name = 1;
}

message SayHelloResponse {
    string message = 1;
}

我们可以使用task gen生成Protobuf对应的代码:

$ task gen
task: [gen] buf generate
task: [gen] go mod tidy
go: finding module for package github.com/twitchtv/twirp
go: finding module for package google.golang.org/protobuf/proto
go: finding module for package github.com/twitchtv/twirp/ctxsetters
go: finding module for package google.golang.org/protobuf/reflect/protoreflect
go: finding module for package google.golang.org/protobuf/runtime/protoimpl
go: finding module for package google.golang.org/protobuf/encoding/protojson
go: found github.com/twitchtv/twirp in github.com/twitchtv/twirp v8.1.3+incompatible
go: found github.com/twitchtv/twirp/ctxsetters in github.com/twitchtv/twirp v8.1.3+incompatible
go: found google.golang.org/protobuf/encoding/protojson in google.golang.org/protobuf v1.28.1
go: found google.golang.org/protobuf/proto in google.golang.org/protobuf v1.28.1
go: found google.golang.org/protobuf/reflect/protoreflect in google.golang.org/protobuf v1.28.1
go: found google.golang.org/protobuf/runtime/protoimpl in google.golang.org/protobuf v1.28.1
go: downloading github.com/google/go-cmp v0.5.5
go: downloading golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543
go: finding module for package github.com/pkg/errors
go: found github.com/pkg/errors in github.com/pkg/errors v0.9.1

接下来,我们就可以编辑internal/greetersvc/service.go文件,添加服务的实现:

package greetersvc

import (
	"context"

	pb "github.com/ipfans/twirp-demo/rpc/greeter/v1"
)

type Server struct{}

func (s *Server) SayHello(ctx context.Context, req *pb.SayHelloRequest) (*pb.SayHelloResponse, error) {
	return &pb.SayHelloResponse{
		Message: "Hello, " + req.Name,
	}, nil
}

最后我们完善一下入口cmd/greeter/main.go文件:

package main

import (
	"net/http"
	"time"

	"github.com/ipfans/twirp-demo/internal/greetersvc"
	greeterv1 "github.com/ipfans/twirp-demo/rpc/greeter/v1"
	"github.com/twitchtv/twirp"
)

func main() {
	server := &greetersvc.Server{} // implements `GreeterService` interface
	twirpHandler := greeterv1.NewGreeterServiceServer(
		server,
		twirp.WithServerPathPrefix(""), // Default will be `twirp`
	)

	httpServer := &http.Server{
		Addr:              "127.0.0.1:8080",
		Handler:           twirpHandler,
		ReadHeaderTimeout: 30 * time.Second,
		WriteTimeout:      60 * time.Second,
	}
	err := httpServer.ListenAndServe()
	if err != nil {
		panic(err)
	}
}

然后执行task,启动Server看一下。

$ task
task: [gen] buf generate
task: [gen] go mod tidy
task: [lint] golangci-lint run ./...
task: [lint] buf lint
task: [build] go build -o build/greeter ./cmd/greeter

这样服务就监听在本地的8080端口上。我们也可以通过比如curlie等工具访问本地服务确定程序已经成功启动。

$ curlie POST http://localhost:8080/rpc.greeter.v1.GreeterService/SayHello -H "Content-Type: application/json" -d '{"name":"kevin"}'
HTTP/1.1 200 OK
Content-Length: 26
Content-Type: application/json
Date: Tue, 24 Jan 2023 14:15:06 GMT

{
    "message": "Hello, kevin"
}

这里注意一下URL内容,我们可以看到http://localhost:8080/rpc.greeter.v1.GreeterService/SayHello这个地址中,rpc.greeter.v1 是我们之前在Protobuf中定义的package名称,GreeterService是Protobuf中的service名称,而SayHello 则是定义的rpc名称。因为我们在这个例子中将Prefix设定为空,所以被跳过了,否则,完整的URL将会是http://localhost:8080/twipc/rpc.greeter.v1.GreeterService/SayHello 。在这个例子中,我们也是使用了JSON进行了数据传输和测试,这个传输数据格式会通过Content-Type设定,并由框架自动处理。如果是需要传输Protobuf数据,则可以声明为application/protobuf类型。

你会发现Twirp的这套协议中,URL组成对API网关会特别友好,这也是Twirp Wire Protocol会被很多公司选择的原因。除了Twitch自身的使用外,包括SoundCloud、Github等公司也是Twirp Wire Protocol的用户。当然,未必是Twirp本身这套框架的用户。

编写客户端

除了服务端以外,对微服务而言,常见的服务间调用非常重要的过程。这部分当然也包括Go本身和前端代码。

Go Client

这里首先介绍一下Go代码本身的实现。因为借助buf-cli就包含了一套完整的客户端实现,我们可以通过NewGreeterServiceProtobufClient初始化一个Protobuf的Client:

client := greeterv1.NewGreeterServiceProtobufClient("http://127.0.0.1:8080", &http.Client{
		Timeout: 10 * time.Second,
	}, twirp.WithClientPathPrefix(""))
	resp, _ := client.SayHello(context.TODO(), &greeterv1.SayHelloRequest{Name: "Kevin"})

TypeScript Client

你可以在项目中可以看到client目录,这个目录是为了前端项目而存在的,对应的前端项目可以参考实现。如果需要在已有项目中实现,可以先安装必要的依赖:

pnpm i @bufbuild/protobuf @bufbuild/protoc-gen-es @bufbuild/protoplugin typescript tsx

我们可以使用buf生成前端TypeScript代码:

$ task gen-ts
task: [gen-ts] buf generate --template buf.gen-ts.yaml

这样就会在client/src/gen目录下生成对应的TypeScript客户端代码。

client/src/gen
└── rpc
    └── greeter
        └── v1
            ├── service_pb.ts
            └── service_twirp.ts

当然,你可以选择仅仅生成pb的代码,调用twirp的相关代码可以根据你自己的习惯自行编写。

Next

如此,一个服务完整的布局将已经构成,这样我们可以更加专注于业务本身,而不仅仅把时间投入在技术层面纠结上了。Twirp一个使用上的一个好处就在于它可以支持符合REST规范的API,也就是说可以使用和RESTful API相同的URL。这在实际的开发中有很大的便利性,因为可以使开发者在不改变原有API规范的前提下更换RPC的实现方式。

同时,Twirp也提供了一定的性能优势,在一些场景下也让开发者能够拥有良好的性能。根据某项benchmark数据,在使用Protobuf通讯时,相对JSON可以获得更好的性能数据。

总而言之,Twirp作为一个新兴的RPC实现框架,是一个非常适合纳入考虑的框架。