我做了一个Go语言的微服务工具包

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"多年以来,我一直认为自己是一名语言无关的软件开发人员,因为在编程语言方面,我总是把掌握基础知识和学习新概念放在首位,而不是“玩最爱”。在我 15 年的职业生涯中,我已经用多种语言(例如 Java、Scala、Go 等)编写了数千行代码。直到我精通 Go 之后,我才意识到:选择正确的语言很重要。我成为了一名真正的忠实主义者;今天,它无疑是我最喜欢的语言。它的简单、优雅以及强大的并发范式使其非常适用于下一代的分布式服务。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"为了表达我对这种语言的热爱,我开发了一个工具包,以帮助希望使用 Go 来增强微服务的其他开发人员。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"REST + gRPC: 打造完美的婚姻"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"微服务通常由 HTTP 或 RPC 框架(如 REST 和 gRPC)支持。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"REST 来自于人们熟悉的面向实体(entry) 设计——设计方法是 HTTP 协议的一 构建块。CRUD(Create、Read、Update、Delete)操作定义了实体的一组行为。REST API 使用 HTTP 方法的子集在通常表示 \/ 序列化为 JSON 的实体上执行 CRUD 操作。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"gRPC 是一个高性能的 RPC 框架(备注:RPC API 允许开发人员访问分布式的过程或方法,这些过程或方法在语法上与集中式的过程或方法没有区别,从而隐藏了通过网络进行数据序列化 \/ 传输的复杂性)。它提供了客户端、服务端和双向流。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在底层,gRPC 使用 HTTP\/2(用于传输)和 Protocol Buffers(用于高效的序列化)来实现比 REST+JSON 更高的性能。它为代码自动生成提供了一流的支持。protobuf 编译器生成客户端和服务端的代码,从而促进了应用程序的快速开发,并减少了发布新服务所需的工作量。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通过将 REST+gRPC 相结合,我们可以创建高性能的分布式服务,为客户提供双向访问模式,同时还能保留面向实体设计方法的优点。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面是上述介绍的一个示例,在这个例子中,我们首先定义了一个 gRPC 服务,使用 protobuf 规范以面向实体的方式操作"},{"type":"codeinline","content":[{"type":"text","text":"orders"}]},{"type":"text","text":"。使用"},{"type":"codeinline","content":[{"type":"text","text":"order"}]},{"type":"text","text":"作为实体,我们需要定义该实体能够支持的服务,即与 CRUD 操作相对应的 RPC 方法。我们将添加一个额外的 RPC 方法"},{"type":"codeinline","content":[{"type":"text","text":"List"}]},{"type":"text","text":",以支持列出 \/ 过滤现有的订单。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"properties"},"content":[{"type":"text","text":"syntax = \"proto3\";\npackage orders;\nimport \"google\/protobuf\/timestamp.proto\";\n\/\/ 使用 CRUD + List rpc 方法定义 Order 服务 \nservice OrderService {\n\n \/\/ 创建订单\n rpc Create (CreateOrderRequest) returns (CreateOrderResponse);\n\n \/\/ 检索现有的订单\n rpc Retrieve (RetrieveOrderRequest) returns (RetrieveOrderResponse);\n\n \/\/ 修改现有订单\n rpc Update (UpdateOrderRequest) returns (UpdateOrderResponse);\n\n \/\/ 删除现有订单\n rpc Delete (DeleteOrderRequest) returns (DeleteOrderResponse);\n\n \/\/ 现有订单的 List 列表\n rpc List (ListOrderRequest) returns (ListOrderResponse);\n}\n\/\/ 订单详细信息的 message(这是我们的实体)\nmessage Order {\n \/\/ 订单可能存在的状态\n enum Status {\n PENDING = 0;\n PAID = 1;\n SHIPPED = 2;\n DELIVERED = 3;\n CANCELLED = 4;\n }\n int64 order_id = 1;\n repeated Item items = 2;\n float total = 3;\n google.protobuf.Timestamp order_date = 5;\n Status status = 6;\n}\n\/\/ 支付信息的 message\nmessage PaymentMethod {\n enum Type {\n NOT_DEFINED = 0;\n VISA = 1;\n MASTERCARD = 2;\n PAYPAL = 3;\n APPLEPAY = 4;\n }\n Type payment_type = 1;\n string pre_authorization_token = 2; \n}\n\/\/ 包含在订单中的商品的详细信息的 message\nmessage Item {\n string description = 1;\n float price = 2;\n}\n\/\/ 创建订单的请求\nmessage CreateOrderRequest {\n repeated Item items = 1;\n PaymentMethod payment_method = 2;\n}\n\/\/ 订单创建的响应\nmessage CreateOrderResponse {\n Order order = 1;\n}\n\/\/ 检索订单的请求\nmessage RetrieveOrderRequest {\n int64 order_id = 1;\n}\n\/\/ 检索订单的响应\nmessage RetrieveOrderResponse {\n Order order = 1;\n}\n\/\/ 更新现有订单的请求\nmessage UpdateOrderRequest {\n int64 order_id = 1;\n repeated Item items = 2;\n PaymentMethod payment_method = 3;\n}\n\/\/ 更新现有订单的响应\nmessage UpdateOrderResponse {\n Order order = 1;\n}\n\/\/ 删除现有订单的请求\nmessage DeleteOrderRequest {\n int64 order_id = 1;\n repeated Item items = 2;\n}\n\/\/ 删除现有订单的响应\nmessage DeleteOrderResponse {\n Order order = 1;\n}\n\/\/ 获取现有订单列表的请求\nmessage ListOrderRequest {\n repeated int64 ids = 1;\n Order.Status statuses = 2;\n}\n\/\/ 获取现有订单列表的响应\nmessage ListOrderResponse {\n repeated Order order = 1;\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"order.proto接下来,我们使用带有必要 Go 选项的"},{"type":"codeinline","content":[{"type":"text","text":"protoc"}]},{"type":"text","text":"来编译"},{"type":"codeinline","content":[{"type":"text","text":"order.proto"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/46\/460f4f234b757afbc5ff2164d9a0cf12.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"编译 order.proto"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"运行上面的命令将生成两个文件:"},{"type":"codeinline","content":[{"type":"text","text":"order.pb.go"}]},{"type":"text","text":"和"},{"type":"codeinline","content":[{"type":"text","text":"order_grpc.pb.go"}]},{"type":"text","text":"。"},{"type":"codeinline","content":[{"type":"text","text":"order.pb.go"}]},{"type":"text","text":"包含了针对"},{"type":"codeinline","content":[{"type":"text","text":"order.proto"}]},{"type":"text","text":"中定义的每种 protobuf 的"},{"type":"codeinline","content":[{"type":"text","text":"message"}]},{"type":"text","text":"类型的结构体。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/e4\/e418aba20be74355a34a824614ae1843.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"Order 的结构体(生成的代码)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"order_grpc.pb.go"}]},{"type":"text","text":"提供了用于与订单服务交互的客户端 \/ 服务端代码。这个文件中包括了"},{"type":"codeinline","content":[{"type":"text","text":"OrderServiceServer"}]},{"type":"text","text":"——"},{"type":"codeinline","content":[{"type":"text","text":"OrderService"}]},{"type":"text","text":"的接口转换(为了与“婚姻”进行类比,可以将它看作是司仪)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/b2\/b2ba0d182dbed6a52e9631941cc66d73.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"OrderServiceServer 接口(生成的代码)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"为了启动并运行 gRPC 服务,我们需要实现"},{"type":"codeinline","content":[{"type":"text","text":"OrderServiceServer"}]},{"type":"text","text":"接口。在本练习中,我们可以使用"},{"type":"codeinline","content":[{"type":"text","text":"UnimplementedOrderServiceServer"}]},{"type":"text","text":"(生成的代码中提供的基本的实现)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/9d\/9d362483dc2595cbec410d18bc2a59c8.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"UnimplementedOrderServiceServer(生成的代码)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"RegisterOrderServiceServer"}]},{"type":"text","text":"方法接受"},{"type":"codeinline","content":[{"type":"text","text":"grpc.Server"}]},{"type":"text","text":"以及"},{"type":"codeinline","content":[{"type":"text","text":"OrderServiceServer"}]},{"type":"text","text":"接口;此方法基于我们订单服务接口实现封装了一个"},{"type":"codeinline","content":[{"type":"text","text":"grpc.Server"}]},{"type":"text","text":",并且必须要在调用服务的"},{"type":"codeinline","content":[{"type":"text","text":"Serve()"}]},{"type":"text","text":"方法之前调用它。请参见下面的示例。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"import(\n \"log\"\n \"net\"\n \"google.golang.org\/grpc\"\n)\nconst (\n grpcPort = \"50051\"\n)\nfunc main() {\n grpcServer := grpc.NewServer()\n orderService := UnimplementedOrderServiceServer{}\n RegisterOrderServiceServer(grpcServer, &orderService)\n lis, err := net.Listen(\"tcp\", \":\" + grpcPort)\n if err != nil {\n log.Fatalf(\"failed to listen: %v\", err)\n }\n if err := grpcServer.Serve(lis); err != nil {\n log.Fatalf(\"failed to start gRPC server: %v\", err)\n }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"初始化 gRPC 服务"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通过这个步骤,gRPC 订单服务只需要几行代码就可以完成了。最后一步是开发一个 REST 服务。通过将"},{"type":"codeinline","content":[{"type":"text","text":"OrderServiceServer"}]},{"type":"text","text":"接口注入到 REST 服务,我们可以正式实现这种“联姻”。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"swift"},"content":[{"type":"text","text":"import (\n \"net\/http\"\n \"github.com\/gin-gonic\/gin\"\n \"github.com\/golang\/protobuf\/jsonpb\"\n \"google.golang.org\/grpc\"\n)\n\/\/ RestServer 为订单服务实现了一个 REST 服务\ntype RestServer struct {\n server *http.Server\n orderService OrderServiceServer \/\/ 与我们注入到 gRPC 服务的订单服务相同\n}\n\/\/ NewRestServer 是一个创建 RestServer 的便捷函数\nfunc NewRestServer(orderService OrderServiceServer, port string) RestServer {\n rs := RestServer{\n server: &http.Server{\n Addr: \":\" + port,\n Handler: router,\n },\n orderService: orderService,\n }\n \/\/ 注册 routes\n router.POST(\"\/order\", rs.create)\n router.GET(\"\/order\/:id\", rs.retrieve)\n router.PUT(\"\/order\", rs.update)\n router.DELETE(\"\/order\", rs.delete)\n router.GET(\"\/order\", rs.list)\n return rs\n}\n\/\/ Start 启动服务器\nfunc (r RestServer) Start() error {\n return r.server.ListenAndServe()\n}\n\/\/ create 是一个处理函数,它根据订单请求创建订单 (JSON 主体)\nfunc (r RestServer) create(c *gin.Context) {\n var req CreateOrderRequest\n \/\/ unmarshal 订单请求\n err := jsonpb.Unmarshal(c.Request.Body, &req)\n if err != nil {\n c.String(http.StatusInternalServerError, \"error creating order request\")\n }\n \/\/ 根据请求,使用订单服务创建订单\n resp, err := r.orderService.Create(c.Request.Context(), &req)\n if err != nil {\n c.String(http.StatusInternalServerError, \"error creating order\")\n }\n m := &jsonpb.Marshaler{}\n if err := m.Marshal(c.Writer, resp); err != nil {\n c.String(http.StatusInternalServerError, \"error sending order response\")\n }\n}\nfunc (r RestServer) retrieve(c *gin.Context) {\n c.String(http.StatusNotImplemented, \"not implemented yet\")\n}\nfunc (r RestServer) update(c *gin.Context) {\n c.String(http.StatusNotImplemented, \"not implemented yet\")\n}\nfunc (r RestServer) delete(c *gin.Context) {\n c.String(http.StatusNotImplemented, \"not implemented yet\")\n}\nfunc (r RestServer) list(c *gin.Context) {\n c.String(http.StatusNotImplemented, \"not implemented yet\")\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"嵌入订单服务接口的 REST 服务示例"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最后,更新"},{"type":"codeinline","content":[{"type":"text","text":"main"}]},{"type":"text","text":"方法,将 REST + gRPC 结合起来。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"import(\n \"log\"\n \"net\"\n \"google.golang.org\/grpc\"\n)\nconst (\n grpcPort = \"50051\"\n restPort = \"8080\"\n)\nfunc main() {\n grpcServer := grpc.NewServer()\n orderService := UnimplementedOrderServiceServer{}\n RegisterOrderServiceServer(grpCServer, &orderService)\n lis, err := net.Listen(\"tcp\", \":\" + grpcPort)\n if err != nil {\n log.Fatalf(\"failed to listen: %v\", err)\n }\n go func() {\n\n \/\/ Serve() 是一个阻塞调用,因此需要将这个调用加入到 goroutine 中\n grpcServer.Serve(lis)\n }()\n\n restServer := NewRestServer(orderService, restPort)\n \/\/ Start() 也在阻塞,但这是可以的,因为我们需要一个阻塞调用来防止 main() 突然退\n \/\/ 出。我们很快就会重构这个逻辑!\n restServer.Start()\n\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"使用服务接口统一 REST + gRPC 服务"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"现在,都使用相同的订单服务实现来启动并运行 gRPC 和 REST 服务了。请注意,我们可以对上面的代码片段进行一些优化,因为它涉及到了错误处理、并发、可读性等。稍后我们将解决这些问题。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如上所述,gRPC 框架提供了丰富的 protobuf 工具,可促进应用程序的快速开发,使开发人员能够生成客户端 \/ 服务端代码,包括可用于将 gRPC 与 REST 或其他 HTTP API 结合使用的服务接口。"}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"并发:Goroutines & Channels"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Goroutine"}]},{"type":"text","text":"是与其他函数并发执行的函数。可以将它们视为不会阻塞当前执行线程的后台进程。在后台,这些轻量级的线程被多路复用到一个或多个(n:1)操作系统线程(OS threads)。这样一来,Go 程序可以处理数百万个"},{"type":"codeinline","content":[{"type":"text","text":"goroutine"}]},{"type":"text","text":",而 Java"},{"type":"codeinline","content":[{"type":"text","text":"future"}]},{"type":"text","text":"可以处理的线程数量将会受到可用 OS 线程数的限制(因为 Java 线程与 OS 线程的比例是 1:1)。这种性能优势的注意事项是,Go 线程共享内存空间,并且必须同步访问该内存空间(这对于 Java 开发人员来说应该很熟悉)。这里"},{"type":"codeinline","content":[{"type":"text","text":"channel"}]},{"type":"text","text":"可以从自由竞争状态和死锁的地狱中拯救我们。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Channel"}]},{"type":"text","text":"是基本类型的管道(你可以把它们视为邮箱),它允许"},{"type":"codeinline","content":[{"type":"text","text":"goroutine"}]},{"type":"text","text":"在没有互斥锁的情况下安全地来回共享数据。通道读 \/ 写 阻塞) 当前执行线程,直到发送方或接收方准备就绪为止。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面是可能会使用"},{"type":"codeinline","content":[{"type":"text","text":"goroutine"}]},{"type":"text","text":"的一些常见任务。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"应用程序任务:"},{"type":"text","text":" 运行 Web 服务端、DB 连接池、守护程序、API 轮询、数据处理队列"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"请求 \/ 事件任务:"},{"type":"text","text":" 处理传入的 HTTP 请求,执行昂贵的子任务(例如多个网络调用)来完成请求,向 Kafka 发布新消息"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"即发即弃(Fire & Forget)任务:"},{"type":"text","text":" 日志记录、报警、度量指标"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"阻塞当前执行线程,直到服务端完成服务请求为止。如果你想了解 Go 的 HTTP 服务端是如何处理请求的,请签出源码(TL;DR,为每个传入的 HTTP 请求生成一个"},{"type":"codeinline","content":[{"type":"text","text":"goroutine"}]},{"type":"text","text":")。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由于"},{"type":"codeinline","content":[{"type":"text","text":"grpcServer.Serve()"}]},{"type":"text","text":"和"},{"type":"codeinline","content":[{"type":"text","text":"restServer.Start()"}]},{"type":"text","text":"都是阻塞调用,因此在"},{"type":"codeinline","content":[{"type":"text","text":"main"}]},{"type":"text","text":"执行线程中只能执行其中的一个调用。另一个必须在后台执行。REST 和 gRPC 服务的"},{"type":"codeinline","content":[{"type":"text","text":"start"}]},{"type":"text","text":"\/"},{"type":"codeinline","content":[{"type":"text","text":"serve"}]},{"type":"text","text":"方法也会返回错误,我们需要优雅地处理这些错误。(关于此技巧的快速提示:将每个服务包装在一个暴露错误通道的结构体中。调用"},{"type":"codeinline","content":[{"type":"text","text":"goroutine"}]},{"type":"text","text":"中的 start\/serve 方法,将错误写入错误通道。这允许我们使用"},{"type":"codeinline","content":[{"type":"text","text":"select"}]},{"type":"text","text":"来等待多个通道操作的执行完成)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以下代码演示了如何优化 REST 和 gRPC 服务以进行后台处理和基于通道的错误传播。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"import (\n \"net\/http\"\n \"github.com\/gin-gonic\/gin\"\n \"github.com\/golang\/protobuf\/jsonpb\"\n \"google.golang.org\/grpc\"\n)\n\/\/ RestServer 为订单服务实现了一个 REST 服务。\ntype RestServer struct {\n server *http.Server\n orderService OrderServiceServer \/\/ 与我们注入 gRPC 服务端的订单服务相同\n errCh chan error\n}\n\/\/ NewRestServer 是一个创建 RestServer 的便捷函数\nfunc NewRestServer(orderService OrderServiceServer, port string) RestServer {\n router := gin.Default()\n rs := RestServer{\n server: &http.Server{\n Addr: \":\" + port,\n Handler: router,\n },\n orderService: orderService,\n errCh: make(chan error),\n }\n \/\/ 注册路由\n router.POST(\"\/order\", rs.create)\n router.GET(\"\/order\/:id\", rs.retrieve)\n router.PUT(\"\/order\", rs.update)\n router.DELETE(\"\/order\", rs.delete)\n router.GET(\"\/order\", rs.list)\n return rs\n}\n\/\/ Start 在后台启动 REST 服务,将错误推入错误通道\nfunc (r RestServer) Start() {\n go func() {\n r.errCh
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章