在 Go 中使用 CloudEvents
采用事件驱动架构(EDA)来提高可扩展性并减少组件/服务之间的耦合在复杂环境中相对常见。
虽然这种方法解决了许多问题,但团队面临的挑战之一是标准化事件以确保所有组件之间的兼容性。为了缓解这一挑战,我们可以使用 CloudEvents 项目。
该项目旨在成为标准化和描述事件的规范,带来一致性、可访问性和可移植性。另一个优点是,该项目除了作为规范之外,还提供了一系列 SDK 来加速团队采用。
在这篇文章中,我想在一个虚构的项目中演示 Go SDK(Python SDK 的特殊外观)的使用。
让我们考虑一个由两个微服务组成的环境:一个用于管理用户 (CRUD) 的用户,以及一个审核服务,用于在环境中存储重要事件以供将来分析。
用户服务的服务代码如下:
package main import ( "context" "encoding/json" "log" "net/http" "time" cloudevents "github.com/cloudevents/sdk-go/v2" "github.com/cloudevents/sdk-go/v2/protocol" "github.com/go-chi/chi/v5" "github.com/go-chi/httplog" "github.com/google/uuid" ) const auditService = "http://localhost:8080/" func main() { logger := httplog.NewLogger("user", httplog.Options{ JSON: true, }) ctx := context.Background() ceClient, err := cloudevents.NewClientHTTP() if err != nil { log.Fatalf("failed to create client, %v", err) } r := chi.NewRouter() r.Use(httplog.RequestLogger(logger)) r.Post("/v1/user", storeUser(ctx, ceClient)) http.Handle("/", r) srv := &http.Server{ ReadTimeout: 30 * time.Second, WriteTimeout: 30 * time.Second, Addr: ":3000", Handler: http.DefaultServeMux, } err = srv.ListenAndServe() if err != nil { logger.Panic().Msg(err.Error()) } } type userRequest struct { ID uuid.UUID Name string `json:"name"` Password string `json:"password"` } func storeUser(ctx context.Context, ceClient cloudevents.Client) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { oplog := httplog.LogEntry(r.Context()) var ur userRequest err := json.NewDecoder(r.Body).Decode(&ur) if err != nil { w.WriteHeader(http.StatusBadRequest) oplog.Error().Msg(err.Error()) return } ur.ID = uuid.New() //TODO: store user in a database // Create an Event. event := cloudevents.NewEvent() event.SetSource("github.com/eminetto/post-cloudevents") event.SetType("user.storeUser") event.SetData(cloudevents.ApplicationJSON, map[string]string{"id": ur.ID.String()}) // Set a target. ctx := cloudevents.ContextWithTarget(context.Background(), auditService) // Send that Event. var result protocol.Result if result = ceClient.Send(ctx, event); cloudevents.IsUndelivered(result) { oplog.Error().Msgf("failed to send, %v", result) w.WriteHeader(http.StatusInternalServerError) return } return } }
在代码中,您可以看到事件的创建以及将其发送到审核服务,如下所示:
package main import ( "context" "fmt" "log" cloudevents "github.com/cloudevents/sdk-go/v2" ) func receive(event cloudevents.Event) { // do something with event. fmt.Printf("%s", event) } func main() { // The default client is HTTP. c, err := cloudevents.NewClientHTTP() if err != nil { log.Fatalf("failed to create client, %v", err) } if err = c.StartReceiver(context.Background(), receive); err != nil { log.Fatalf("failed to start receiver: %v", err) } }
通过运行这两个服务,您可以通过向用户发送请求来了解它们的工作原理:
curl -X "POST" "http://localhost:3000/v1/user" \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ -d $'{ "name": "Ozzy Osbourne", "password": "12345" }'
用户输出是:
{"level":"info","service":"user","httpRequest":{"proto":"HTTP/1.1","remoteIP":"[::1]:50894","requestID":"Macbook-Air-de-Elton.local/3YUAnzEbis-000001","requestMethod":"POST","requestPath":"/v1/user","requestURL":"http://localhost:3000/v1/user"},"httpRequest":{"header":{"accept":"application/json","content-length":"52","content-type":"application/json","user-agent":"curl/8.7.1"},"proto":"HTTP/1.1","remoteIP":"[::1]:50894","requestID":"Macbook-Air-de-Elton.local/3YUAnzEbis-000001","requestMethod":"POST","requestPath":"/v1/user","requestURL":"http://localhost:3000/v1/user","scheme":"http"},"timestamp":"2024-11-28T15:52:27.947355-03:00","message":"Request: POST /v1/user"} {"level":"warn","service":"user","httpRequest":{"proto":"HTTP/1.1","remoteIP":"[::1]:50894","requestID":"Macbook-Air-de-Elton.local/3YUAnzEbis-000001","requestMethod":"POST","requestPath":"/v1/user","requestURL":"http://localhost:3000/v1/user"},"httpResponse":{"bytes":0,"elapsed":2.33225,"status":0},"timestamp":"2024-11-28T15:52:27.949877-03:00","message":"Response: 0 Unknown"}
审核服务的输出表明事件已收到。
❯ go run main.go Context Attributes, specversion: 1.0 type: user.storeUser source: github.com/eminetto/post-cloudevents id: 5190bc29-a3d5-4fca-9a88-85fccffc16b6 time: 2024-11-28T18:53:17.474154Z datacontenttype: application/json Data, { "id": "8aadf8c5-9c4e-4c11-af24-beac2fb9a4b7" }
为了验证可移植性目标,我使用 Python SDK 实现了一个版本的审计服务:
from flask import Flask, request from cloudevents.http import from_http app = Flask(__name__) # create an endpoint at http://localhost:/3000/ @app.route("/", methods=["POST"]) def home(): # create a CloudEvent event = from_http(request.headers, request.get_data()) # you can access cloudevent fields as seen below print( f"Found {event['id']} from {event['source']} with type " f"{event['type']} and specversion {event['specversion']}" ) return "", 204 if __name__ == "__main__": app.run(port=8080)
应用程序输出显示事件的接收,无需更改服务用户:
(.venv) eminetto@Macbook-Air-de-Elton audit-python % python3 main.py * Serving Flask app 'main' * Debug mode: off WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Running on http://127.0.0.1:8080 Press CTRL+C to quit Found ce1abe22-dce5-40f0-8c82-12093b707ed7 from github.com/eminetto/post-cloudevents with type user.storeUser and specversion 1.0 127.0.0.1 - - [28/Nov/2024 15:59:31] "POST / HTTP/1.1" 204 -
前面的示例介绍了 CloudEvents SDK,但它违反了基于事件的架构的一个原则:松散耦合。应用程序用户了解审核应用程序并与之绑定,这不是一个好的做法。我们可以通过使用其他 CloudEvents 功能(例如 pub/sub)或添加 Kafka 等功能来改善这种情况。以下示例使用 Kafka 来解耦两个应用程序。
第一步是创建一个 docker-compose.yaml 来使用 Kafka:
services: kafka: image: bitnami/kafka:latest restart: on-failure ports: - 9092:9092 environment: - KAFKA_CFG_BROKER_ID=1 - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092 - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://127.0.0.1:9092 - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181 - KAFKA_CFG_NUM_PARTITIONS=3 - ALLOW_PLAINTEXT_LISTENER=yes depends_on: - zookeeper zookeeper: image: bitnami/zookeeper:latest ports: - 2181:2181 environment: - ALLOW_ANONYMOUS_LOGIN=yes
服务用户进行了以下更改:
package main import ( "context" "encoding/json" "log" "net/http" "time" "github.com/IBM/sarama" "github.com/cloudevents/sdk-go/protocol/kafka_sarama/v2" cloudevents "github.com/cloudevents/sdk-go/v2" "github.com/go-chi/chi/v5" "github.com/go-chi/httplog" "github.com/google/uuid" ) const ( auditService = "127.0.0.1:9092" auditTopic = "audit" ) func main() { logger := httplog.NewLogger("user", httplog.Options{ JSON: true, }) ctx := context.Background() saramaConfig := sarama.NewConfig() saramaConfig.Version = sarama.V2_0_0_0 sender, err := kafka_sarama.NewSender([]string{auditService}, saramaConfig, auditTopic) if err != nil { log.Fatalf("failed to create protocol: %s", err.Error()) } defer sender.Close(context.Background()) ceClient, err := cloudevents.NewClient(sender, cloudevents.WithTimeNow(), cloudevents.WithUUIDs()) if err != nil { log.Fatalf("failed to create client, %v", err) } r := chi.NewRouter() r.Use(httplog.RequestLogger(logger)) r.Post("/v1/user", storeUser(ctx, ceClient)) http.Handle("/", r) srv := &http.Server{ ReadTimeout: 30 * time.Second, WriteTimeout: 30 * time.Second, Addr: ":3000", Handler: http.DefaultServeMux, } err = srv.ListenAndServe() if err != nil { logger.Panic().Msg(err.Error()) } } type userRequest struct { ID uuid.UUID Name string `json:"name"` Password string `json:"password"` } func storeUser(ctx context.Context, ceClient cloudevents.Client) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { oplog := httplog.LogEntry(r.Context()) var ur userRequest err := json.NewDecoder(r.Body).Decode(&ur) if err != nil { w.WriteHeader(http.StatusBadRequest) oplog.Error().Msg(err.Error()) return } ur.ID = uuid.New() //TODO: store user in a database // Create an Event. event := cloudevents.NewEvent() event.SetID(uuid.New().String()) event.SetSource("github.com/eminetto/post-cloudevents") event.SetType("user.storeUser") event.SetData(cloudevents.ApplicationJSON, map[string]string{"id": ur.ID.String()}) // Send that Event. if result := ceClient.Send( // Set the producer message key kafka_sarama.WithMessageKey(context.Background(), sarama.StringEncoder(event.ID())), event, ); cloudevents.IsUndelivered(result) { oplog.Error().Msgf("failed to send, %v", result) w.WriteHeader(http.StatusInternalServerError) return } return } }
需要进行一些更改,主要是为了与 Kafka 建立连接,但事件本身没有改变。
我对审核服务进行了类似的更改:
package main import ( "context" "fmt" "log" "github.com/IBM/sarama" "github.com/cloudevents/sdk-go/protocol/kafka_sarama/v2" cloudevents "github.com/cloudevents/sdk-go/v2" ) const ( auditService = "127.0.0.1:9092" auditTopic = "audit" auditGroupID = "audit-group-id" ) func receive(event cloudevents.Event) { // do something with event. fmt.Printf("%s", event) } func main() { saramaConfig := sarama.NewConfig() saramaConfig.Version = sarama.V2_0_0_0 receiver, err := kafka_sarama.NewConsumer([]string{auditService}, saramaConfig, auditGroupID, auditTopic) if err != nil { log.Fatalf("failed to create protocol: %s", err.Error()) } defer receiver.Close(context.Background()) c, err := cloudevents.NewClient(receiver) if err != nil { log.Fatalf("failed to create client, %v", err) } if err = c.StartReceiver(context.Background(), receive); err != nil { log.Fatalf("failed to start receiver: %v", err) } }
应用程序的输出保持不变。
引入 Kafka 后,我们解耦了应用程序,不再违反 EDA 原则,同时保留了 CloudEvents 提供的优势。
这篇文章的目标是介绍该标准并演示使用 SDK 实现的简易性。我可以更深入地讨论这个主题,但我希望我已经实现了目标并激发了对该技术的研究和使用。
如果您已经使用/已经使用过 CloudEvents 并想在评论中分享您的经验,这将非常有用。
您可以在 GitHub 上的存储库中找到我在这篇文章中提供的代码。
最初于 2024 年 11 月 29 日发布于 https://eltonminetto.dev。
以上是在 Go 中使用 CloudEvents的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

Golang在性能和可扩展性方面优于Python。1)Golang的编译型特性和高效并发模型使其在高并发场景下表现出色。2)Python作为解释型语言,执行速度较慢,但通过工具如Cython可优化性能。

Golang在并发性上优于C ,而C 在原始速度上优于Golang。1)Golang通过goroutine和channel实现高效并发,适合处理大量并发任务。2)C 通过编译器优化和标准库,提供接近硬件的高性能,适合需要极致优化的应用。

goisidealforbeginnersandsubableforforcloudnetworkservicesduetoitssimplicity,效率和concurrencyFeatures.1)installgromtheofficialwebsitealwebsiteandverifywith'.2)

Golang适合快速开发和并发场景,C 适用于需要极致性能和低级控制的场景。1)Golang通过垃圾回收和并发机制提升性能,适合高并发Web服务开发。2)C 通过手动内存管理和编译器优化达到极致性能,适用于嵌入式系统开发。

Golang和Python各有优势:Golang适合高性能和并发编程,Python适用于数据科学和Web开发。 Golang以其并发模型和高效性能着称,Python则以简洁语法和丰富库生态系统着称。

Golang和C 在性能上的差异主要体现在内存管理、编译优化和运行时效率等方面。1)Golang的垃圾回收机制方便但可能影响性能,2)C 的手动内存管理和编译器优化在递归计算中表现更为高效。

Golang和C 在性能竞赛中的表现各有优势:1)Golang适合高并发和快速开发,2)C 提供更高性能和细粒度控制。选择应基于项目需求和团队技术栈。

Golangisidealforbuildingscalablesystemsduetoitsefficiencyandconcurrency,whilePythonexcelsinquickscriptinganddataanalysisduetoitssimplicityandvastecosystem.Golang'sdesignencouragesclean,readablecodeanditsgoroutinesenableefficientconcurrentoperations,t
