本文目錄
- 一、Kitex概述
- 二、第一個Kitex應用
- 三、IDL
- 四、服務注冊與發現
一、Kitex概述
長話短說,就是字節跳動內部的 Golang 微服務 RPC 框架,具有高性能、強可擴展的特點,在字節內部已廣泛使用。
如果對微服務性能有要求,又希望定制擴展融入自己的治理體系,Kitex 會是一個不錯的選擇。
可以看到下面是Kitex的架構設計。
這個框架有一些特點,比如:
- 高性能
使用自研的高性能網絡庫 Netpoll,性能相較 go net 具有顯著優勢。
- 擴展性
提供了較多的擴展接口以及默認擴展實現,使用者也可以根據需要自行定制擴展,具體見下面的框架擴展。
- 多消息協議
RPC 消息協議默認支持 Thrift、Kitex Protobuf、gRPC。Thrift 支持 Buffered 和 Framed 二進制協議,與支持原生 Thrift 協議的多語言框架都能互通; Kitex Protobuf 是 Kitex 自定義的 Protobuf 消息協議,協議格式類似 Thrift;gRPC 是對 gRPC 消息協議的支持,可以與 gRPC 互通。除此之外,使用者也可以擴展自己的消息協議,目前社區也提供了 Dubbo 協議的支持,可以與 Dubbo 互通。
- 多傳輸協議
傳輸協議封裝消息協議進行 RPC 互通,傳輸協議可以額外透傳元信息,用于服務治理,Kitex 支持的傳輸協議有 TTHeader、HTTP2。TTHeader 可以和 Thrift、Kitex Protobuf 結合使用;HTTP2 目前主要是結合 gRPC 協議使用,后續也會支持 Thrift。
- 多種消息類型
支持 PingPong、Oneway、雙向 Streaming。其中 Oneway 目前只對 Thrift 協議支持,雙向 Streaming 只對 gRPC 支持,后續會考慮支持 Thrift 的雙向 Streaming。
- 服務治理
支持服務注冊/發現、負載均衡、熔斷、限流、重試、監控、鏈路跟蹤、日志、診斷等服務治理模塊,大部分均已提供默認擴展,使用者可選擇集成。
- 代碼生成
Kitex 內置代碼生成工具,可支持生成 Thrift、Protobuf 以及腳手架代碼。
二、第一個Kitex應用
首先我們創建一個新的Go項目,然后安裝對應的插件:kitex
和thriftgo
。
然后在終端輸入對應的安裝命令:
go install github.com/cloudwego/kitex/tool/cmd/kitex@latest
go install github.com/cloudwego/thriftgo@latest
然后等待對應的安裝即可。
在項目路徑下安裝新建thrift文件,然后輸入如下代碼:
namespace go apistruct Request{1:string message
}struct Response{1:string message
}service Echo{Response echo(1:Request req)
}
簡單來說,這段代碼定義一個簡單的服務 Echo,它包含一個方法 echo。這個方法接收一個 Request 類型的對象作為輸入,處理后返回一個 Response 類型的對象。Request 和 Response 都包含一個字符串字段 message,分別用于傳遞請求消息和響應消息。這種定義方式使得 Thrift 可以生成不同語言的代碼(例如 Go、Java、Python 等),方便跨語言的服務調用。
然后在命令行輸入命令:
kitex -module exampleKitex -service exampleKitex echo.thrift
來生成基本的代碼。
等待一會后,可以看到目錄已經更新了。
在main.go
中輸入如下代碼:
package mainimport (api "exampleKitex/kitex_gen/api/echo""log"
)
/*api.NewServer 是一個函數,用于創建一個新的服務實例。new(EchoImpl) 創建了一個 EchoImpl 類型的實例,并將其傳遞給 api.NewServer。調用 svr.Run() 方法啟動服務。
*/
func main() {svr := api.NewServer(new(EchoImpl))err := svr.Run()if err != nil {log.Println(err.Error())}
}
如果有報錯,可以輸入go mod tidy
來補充對應需要的庫。
然后在handler.go
中來輸入邏輯代碼。
(s *EchoImpl):這是方法的接收者,表示這個方法屬于 EchoImpl 類型的指針 s。
EchoImpl 是一個結構體類型,通常是由 Thrift 生成的接口的具體實現。
echo是方法名稱,對應 Thrift 定義中的 echo 方法。
ctx context.Context:這是方法的第一個參數,類型為 context.Context。
context 是 Go 標準庫中的一個包,用于在程序中傳遞上下文信息,例如超時、取消信號等。
req *api.Request:這是方法的第二個參數,類型為 *api.Request,表示這是一個指向 api.Request 類型的指針。
api.Request 是 Thrift 定義的 Request 結構體,通過 Thrift 生成的 Go 代碼中的 api 包來訪問。&api.Response{}:創建了一個 api.Response 類型的實例,并取其地址(返回一個指針)。
Message: req.Message:將 req 參數中的 Message 字段值賦給新創建的 Response 實例的 Message 字段。這是一個簡單的回顯服務,即客戶端發送一個消息,服務端原樣返回這個消息。
package mainimport ("context"api "exampleKitex/kitex_gen/api"
)// EchoImpl implements the last service interface defined in the IDL.
type EchoImpl struct{}// Echo implements the EchoImpl interface.
func (s *EchoImpl) Echo(ctx context.Context, req *api.Request) (resp *api.Response, err error) {// TODO: Your code here...return &api.Response{Message: req.Message}, nil
}
然后我們來啟動main.go和handler.go. 啟動之后會默認在監聽端口進行對應的監聽。
然后我們新建一個客戶端,并且寫入對應的客戶端代碼。
package mainimport ("context""github.com/cloudwego/kitex/client/callopt""log""exampleKitex/kitex_gen/api""exampleKitex/kitex_gen/api/echo""time"
)import "github.com/cloudwego/kitex/client"func main() {//下面的destService對應服務名稱是我們在啟動kitex代碼生成工具命令中的service名稱// kitex -moudle exampleKitex -service exampleKitex(服務名稱) echo.thriftc, err := echo.NewClient("exampleKitex", client.WithHostPorts("0.0.0.0:8888"))if err != nil {log.Fatal(err)}req := &api.Request{Message: "my request"}resp, err := c.Echo(context.Background(), req, callopt.WithRPCTimeout(3*time.Second))if err != nil {log.Fatal(err)}log.Println(resp)
}
然后運行客戶端,新開一個終端,輸入命令go run .\client.go
即可。
然后可以看到返回的Response如下:
所以總結一下,就是,先在echo.thrift
中編寫接口,定義發送什么樣的請求,請求里邊有什么樣的一個信息,然后就是繼續定義一個響應,以及服務的邏輯。
三、IDL
IDL(Interface Definition Language,接口定義語言)是一種用于定義軟件組件之間交互接口的規范語言。它允許開發者以一種語言無關的方式描述服務接口、數據結構和通信協議,從而實現不同編程語言之間的互操作性。IDL 的核心目的是提供一種標準化的方式來定義服務的接口和數據結構,使得這些接口和數據結構可以在多種編程語言中被實現和使用。
IDL就是我們剛剛定義的這個thrift文件。
如果進行PRC,那么就需要知道對方的接口是什么,需要傳什么參數,也需要知道返回值是什么樣子的。這個時候就需要通過IDL來約定雙方的協議。就像調用某個函數,需要知道函數簽名一樣。
定義好thrift文件之后,就可以使用命令生成代碼了。
kitex -module example -service example echo.thrift
生成代碼。
服務是默認監聽8888端口的。
四、服務注冊與發現
目前Kitex的服務注冊與發現已經對接了主流的服務注冊與發現中心,如ETCD、Nacos、zookeeper、Eureka等。
接下來我們進行一個簡單的實戰。
首先我們下載etcd:https://github.com/etcd-io/etcd/releases
,找到對應的電腦的版本。
下載好之后找個文件夾進行解壓縮,然后打開,雙擊打開即可。
運行起來之后可以看到對應的2380和2379兩個端口。
到這一步,服務器已經啟用成功了。
接下來我們需要把服務注冊到etcd注冊中心去
。修改handler.go
的相關代碼。
簡單說明下作用:實現一個 Kitex 服務端程序,通過 ETCD 注冊中心將自身注冊為一個可發現的服務,并提供了一個簡單的 Echo 方法。程序的主要功能包括:
package mainimport ("context"api "exampleKitex/kitex_gen/api""exampleKitex/kitex_gen/api/echo""github.com/cloudwego/kitex/pkg/rpcinfo""github.com/cloudwego/kitex/server"etcd "github.com/kitex-contrib/registry-etcd""log"
)// EchoImpl implements the last service interface defined in the IDL.EchoImpl:定義了一個結構體,用于實現服務接口。
type EchoImpl struct{}// Echo implements the EchoImpl interface.
// ctx context.Context:上下文對象,用于傳遞超時和取消信號。
// req *api.Request:請求對象,包含客戶端發送的數據。
func (s *EchoImpl) Echo(ctx context.Context, req *api.Request) (resp *api.Response, err error) {// TODO: Your code here...return &api.Response{Message: req.Message}, nil}func main() {//etcd.NewEtcdRegistry:創建一個 ETCD 注冊中心實例。//參數 []string{"127.0.0.1:2379"} 指定了 ETCD 服務的地址。r, err := etcd.NewEtcdRegistry([]string{"127.0.0.1:2379"})if err != nil {log.Fatal(err)}//echo.NewServer:創建一個服務實例。//new(EchoImpl):創建一個 EchoImpl 實例,作為服務的具體實現。//server.WithRegistry(r):指定使用 ETCD 注冊中心實例 r,將服務注冊到 ETCD 中。//server.WithServerBasicInfo:設置服務的基本信息,例如服務名稱。ServiceName: "Hello":將服務名稱設置為 "Hello",客戶端可以通過這個名稱找到服務。server := echo.NewServer(new(EchoImpl), server.WithRegistry(r), server.WithServerBasicInfo(&rpcinfo.EndpointBasicInfo{ServiceName: "Hello",}))err = server.Run()if err != nil {log.Fatal(err)}}
同時我們修改client.go
的代碼如下:
一句話概括下作用,就是:使用 Kitex 框架的客戶端程序,通過 ETCD 注冊中心動態發現服務,并調用名為 Hello 的服務的 Echo 方法。代碼的主要功能是定期發送請求到服務端,并打印響應結果。
package mainimport ("context""exampleKitex/kitex_gen/api""exampleKitex/kitex_gen/api/echo""github.com/cloudwego/kitex/client"etcd "github.com/kitex-contrib/registry-etcd""log""time"
)func main() {//etcd.NewEtcdResolver:創建一個 ETCD 解析器實例,用于從 ETCD 服務注冊中心獲取服務實例的地址。//參數 []string{"127.0.0.1:2379"} 指定了 ETCD 服務的地址。r, err := etcd.NewEtcdResolver([]string{"127.0.0.1:2379"})if err != nil {log.Fatal(err)}//echo.MustNewClient:創建一個服務客戶端實例。//"Hello" 是服務名稱,與服務端在 ETCD 中注冊的名稱一致。//client.WithResolver(r):指定使用 ETCD 解析器 r 來動態發現服務實例。這意味著客戶端會從 ETCD 中獲取服務實例的地址,而不是直接指定服務地址。client := echo.MustNewClient("Hello", client.WithResolver(r))//創建一個帶有超時時間的上下文,超時時間為 3 秒。for {ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)//調用服務端的 Echo 方法,傳遞一個請求對象,其中 Message 字段為 "hello"。resp, err := client.Echo(ctx, &api.Request{Message: "hello"})cancel() //調用 cancel() 釋放上下文資源。if err != nil {log.Fatal(err)}log.Println(resp)time.Sleep(time.Second)}
}
來捋順一下步驟:
實現一個 Kitex 客戶端程序,通過 ETCD 注冊中心動態發現服務,并調用名為 Hello 的服務的 Echo 方法:
創建 ETCD 解析器實例,用于從 ETCD 注冊中心獲取服務實例的地址。然后創建服務客戶端實例,指定使用 ETCD 解析器。并且在一個無限循環中,每隔 1 秒調用一次服務的 Echo 方法。打印服務端返回的響應。
這種設計適用于微服務架構,其中服務實例可能動態變化,通過 ETCD 注冊中心可以實現服務發現和負載均衡。
通過命令 go run .\handler.go
來啟動服務端,會將我們的服務注冊到etcd里面去。
然后運行client,會看到如下的消息。