我做了一個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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章