本篇文檔闡述Kratos的設(shè)計(jì)理念,介紹Kratos項(xiàng)目的整體情況和主要組件
Kratos是一個(gè)Go語言實(shí)現(xiàn)的微服務(wù)框架,說得更準(zhǔn)確一點(diǎn),它更類似于一個(gè)使用Go構(gòu)建微服務(wù)的工具箱,開發(fā)者可以按照自己的習(xí)慣選用或定制其中的的組件,來打造自己的微服務(wù)。也正是由于這樣的原因,Kratos并不綁定于特定的基礎(chǔ)設(shè)施,不限定于某種注冊中心,或數(shù)據(jù)庫ORM等,所以您可以十分輕松地將任意庫集成進(jìn)項(xiàng)目里,與Kratos共同運(yùn)作。
圍繞這樣的核心設(shè)計(jì)理念,我們設(shè)計(jì)了如下的項(xiàng)目生態(tài):
kratos
?Kratos框架核心,主要包含了基礎(chǔ)的CLI工具,內(nèi)置的HTTP/gRPC接口生成和服務(wù)生命周期管理,提供鏈路追蹤、配置文件、日志、服務(wù)發(fā)現(xiàn)、監(jiān)控等組件能力和相關(guān)接口定義。
contrib
?基于上述核心定義的基礎(chǔ)接口,對配置文件、日志、服務(wù)發(fā)現(xiàn)、監(jiān)控等服務(wù)進(jìn)行具體實(shí)現(xiàn)所形成的一系列插件,可以直接使用它們,也可以參考它們的代碼,做您需要的服務(wù)的適配,從而集成進(jìn)kratos項(xiàng)目中來。
aegis
?我們將服務(wù)可用性相關(guān)的算法:如限流、熔斷等算法放在了這個(gè)獨(dú)立的項(xiàng)目里,幾乎沒有外部依賴,它更不依賴Kratos,您可以在直接在任意項(xiàng)目中使用。您也可以輕松將它集成到Kratos中使用,提高服務(wù)的可用性。
layout
?我們設(shè)計(jì)的一個(gè)默認(rèn)的項(xiàng)目模板,它包含一個(gè)參考了DDD和簡潔架構(gòu)設(shè)計(jì)的項(xiàng)目結(jié)構(gòu)、Makefile腳本和Dockerfile文件。但這個(gè)項(xiàng)目模板不是必需的,您可以任意修改它,或使用自己設(shè)計(jì)的項(xiàng)目結(jié)構(gòu),Kratos依然可以正常工作??蚣鼙旧聿粚?xiàng)目結(jié)構(gòu)做任何假設(shè)和限制,您可以按照自己的想法來使用,具有很強(qiáng)的可定制性。
gateway
?這個(gè)是我們剛剛起步,用Go開發(fā)的API Gateway,后續(xù)您可以使用它來作為您Kratos微服務(wù)的網(wǎng)關(guān),用于微服務(wù)API的治理,項(xiàng)目正在施工中,歡迎關(guān)注。GitHub倉庫:https://github.com/go-kratos
微信群:go-kratos 官方微信群
Discord:go-kratos
以前關(guān)注過kratos項(xiàng)目的可能知道,Kratos的v1版本已經(jīng)開源了很久,也是個(gè)較為完善的框架。那么為什么不直接基于v1繼續(xù)迭代,而是要推倒重來,推出完全重新設(shè)計(jì)的v2呢?
經(jīng)驗(yàn)源自踩坑。
在業(yè)務(wù)不斷迭代、項(xiàng)目不斷膨脹的情況下,我們發(fā)現(xiàn),過去的框架和項(xiàng)目結(jié)構(gòu)設(shè)計(jì),導(dǎo)致代碼變更成本逐漸升高,而沒有進(jìn)行合理的抽象,導(dǎo)致更難進(jìn)行模塊的測試,也更難對第三方基礎(chǔ)庫進(jìn)行適配和遷移,這在一定程度上拉低了生產(chǎn)力。
因此,我們參考了大量的DDD和Clean Architecture等業(yè)界先進(jìn)設(shè)計(jì)理念,重新設(shè)計(jì)了微服務(wù)的項(xiàng)目結(jié)構(gòu),并且這個(gè)結(jié)構(gòu)隨著我們的后續(xù)研究,會(huì)進(jìn)一步進(jìn)行迭代,讓它成為微服務(wù)項(xiàng)目結(jié)構(gòu)的最佳實(shí)踐。
沒錯(cuò),新版本的是從kratos-layout開始的。也許剛接觸這個(gè)項(xiàng)目結(jié)構(gòu)時(shí)會(huì)覺得不適應(yīng),但隨著項(xiàng)目迭代,代碼復(fù)雜度的提高,這個(gè)定義良好的結(jié)構(gòu),將使項(xiàng)目保持優(yōu)秀的代碼可讀性、可測試性,以及令人滿意的開發(fā)效率和可維護(hù)性。
更重要的一點(diǎn)是,這一次我們想面向社區(qū)來設(shè)計(jì)和開發(fā)這個(gè)框架。讓更多的開發(fā)者能夠使用我們的框架來提高生產(chǎn)力,同時(shí)參與到我們的項(xiàng)目中來。
所以我們把整個(gè)框架設(shè)計(jì)成為一個(gè)插座,我們希望整個(gè)框架輕量,插件化,可定制。對于幾乎每一個(gè)微服務(wù)相關(guān)的功能模塊,我們都設(shè)計(jì)了標(biāo)準(zhǔn)化接口,對于第三方庫設(shè)計(jì)為插件,這樣就能迅速把任意基礎(chǔ)設(shè)施集成到使用Kratos的項(xiàng)目里,因此,無論您的公司使用何種基礎(chǔ)設(shè)施,有何種規(guī)范,您都可以輕松將Kratos定制成與您的開發(fā)、生產(chǎn)環(huán)境相匹配的樣子。
不破不立,v2是一次從內(nèi)到外的徹底革新,我們無法在舊版本上修修補(bǔ)補(bǔ),而是選擇重新設(shè)計(jì)和開發(fā)新版本。而目前v2版本也已經(jīng)在很多生產(chǎn)環(huán)境使用,我們也將持續(xù)迭代和完善這個(gè)框架,同時(shí)也更歡迎各位開發(fā)者參與進(jìn)來,一起讓它變得更好。
正如前文提到的,Kratos框架不限制您使用任何第三方庫來進(jìn)行項(xiàng)目開發(fā),因此您可以根據(jù)喜好來選擇庫進(jìn)行集成。我們也會(huì)逐步針對更多被廣泛使用的第三方庫開發(fā)插件。
這里給出一些被廣泛使用的庫供參考:
數(shù)據(jù)庫:
緩存:
消息隊(duì)列:
其它更多的優(yōu)秀go庫,可以在awesome-go這個(gè)倉庫中找找。
kratos命令目前主要用于從模板創(chuàng)建項(xiàng)目,維護(hù)依賴包版本等。
Kratos使用Protobuf進(jìn)行API定義。Protobuf是由Google開發(fā)的一種語言中立的數(shù)據(jù)序列化協(xié)議。它有結(jié)構(gòu)定義清晰、可擴(kuò)展性好、體積小、性能優(yōu)秀等特點(diǎn),在眾多公司和項(xiàng)目被廣泛使用。
在使用Kratos的項(xiàng)目中,您將使用如下的IDL進(jìn)行您的接口定義,并且通過protoc工具生成相應(yīng)的.pb.go文件,其中包含根據(jù)定義生成的的服務(wù)端和客戶端代碼。隨后您就可以在自己的項(xiàng)目內(nèi)部注冊服務(wù)端代碼使用,或引用客戶端代碼進(jìn)行遠(yuǎn)程調(diào)用。
Kratos默認(rèn)僅生成gRPC接口的代碼,如果需要生成HTTP代碼,請?jiān)趐roto文件中使用option (google.api.http)來添加HTTP部分的定義后再進(jìn)行生成。默認(rèn)情況下,HTTP接口將使用JSON作為序列化格式,如果想使用其它序列化格式(form,XML等),請參考文檔序列化進(jìn)行相應(yīng)的配置即可。
syntax = "proto3";
package helloworld.v1;
import "google/api/annotations.proto";
option go_package = "github.com/go-kratos/kratos-layout/api/helloworld/v1;v1";
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {
option (google.api.http) = {
get: "/helloworld/{name}"
};
}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
需要注意,雖然Protobuf定義的API的可靠性更強(qiáng),但字段結(jié)構(gòu)靈活性相對JSON要弱一些,因此如果您有諸如文件上傳接口,或者某些無法對應(yīng)到proto的JSON結(jié)構(gòu)需要使用,我門還提供了“逃生門”,在我們的Protobuf體系之外定義這些接口,實(shí)現(xiàn)為普通的http.Handler并且掛載到路由上,或者用struct來定義您的字段??梢詤⒖嘉覀兊?a href="http://m.hgci.cn/targetlink?url=https://github.com/go-kratos/examples/blob/main/http/upload/main.go" target="_blank">upload例子進(jìn)行實(shí)現(xiàn)。
服務(wù)之間的API調(diào)用,如果有某些元信息需要傳遞過去,而不是寫在payload消息中,可以使用Metadata包進(jìn)行字段設(shè)置和提取。
Kratos的errors模塊提供了error的封裝??蚣芤差A(yù)定義了一系列標(biāo)準(zhǔn)錯(cuò)誤供使用。
錯(cuò)誤處理這一塊的設(shè)計(jì)也經(jīng)過了很久的討論才定下來,主要設(shè)計(jì)理念如下:
code
?語義近似HTTP的Status Code(例如客戶端傳參數(shù)錯(cuò)誤用400)同時(shí)也作為大類錯(cuò)誤,在HTTP接口中的HTTP Code會(huì)使用它,好處是網(wǎng)關(guān)層可以根據(jù)這個(gè)code觸發(fā)相應(yīng)策略(重試、限流、熔斷等)。
reason
?業(yè)務(wù)的具體錯(cuò)誤碼,為可讀的字符串,能夠表明,在同一個(gè)服務(wù)中應(yīng)該唯一。
message
?用戶可讀的信息,可以在客戶端(App、瀏覽器等)進(jìn)行相應(yīng)的展示給用戶看。
metadata
?為一些附加信息,可以作為補(bǔ)充信息使用。在API返回的錯(cuò)誤信息中,以HTTP接口為例,消息結(jié)構(gòu)大概是長這個(gè)樣子的:
{
// 錯(cuò)誤碼,跟 http-status 一致,并且在 grpc 中可以轉(zhuǎn)換成 grpc-status
"code": 500,
// 錯(cuò)誤原因,定義為業(yè)務(wù)判定錯(cuò)誤碼
"reason": "USER_NOT_FOUND",
// 錯(cuò)誤信息,為用戶可讀的信息,可作為用戶提示內(nèi)容
"message": "invalid argument error",
// 錯(cuò)誤元信息,為錯(cuò)誤添加附加可擴(kuò)展信息
"metadata": {"some-key": "some-value"}
}
在Kratos中您可以使用proto文件定義您的業(yè)務(wù)錯(cuò)誤,并通過工具生成對應(yīng)的處理邏輯和方法。(如使用layout中提供的make errors指令。)
錯(cuò)誤定義:
syntax = "proto3";
package api.blog.v1;
import "errors/errors.proto";
option go_package = "github.com/go-kratos/examples/blog/api/v1;v1";
enum ErrorReason {
// 設(shè)置缺省錯(cuò)誤碼
option (errors.default_code) = 500;
// 為某個(gè)枚舉單獨(dú)設(shè)置錯(cuò)誤碼
USER_NOT_FOUND = 0 [(errors.code) = 404];
CONTENT_MISSING = 1 [(errors.code) = 400];;
}
錯(cuò)誤創(chuàng)建:
// 通過 errors.New() 響應(yīng)錯(cuò)誤
errors.New(500, "USER_NAME_EMPTY", "user name is empty")
// 通過 proto 生成的代碼響應(yīng)錯(cuò)誤,并且包名應(yīng)替換為自己生成代碼后的 package name
api.ErrorUserNotFound("user %s not found", "kratos")
// 傳遞metadata
err := errors.New(500, "USER_NAME_EMPTY", "user name is empty")
err = err.WithMetadata(map[string]string{
"foo": "bar",
})
錯(cuò)誤斷言:
err := wrong()
// 通過 errors.Is() 斷言
if errors.Is(err,errors.BadRequest("USER_NAME_EMPTY","")) {
// do something
}
// 通過判斷 *Error.Reason 和 *Error.Code
e := errors.FromError(err)
if e.Reason == "USER_NAME_EMPTY" && e.Code == 500 {
// do something
}
// 通過 proto 生成的代碼斷言錯(cuò)誤,并且包名應(yīng)替換為自己生成代碼后的 package name
if api.IsUserNotFound(err) {
// do something
})
Kratos提供了統(tǒng)一的接口,支持配置文件的加載和變更訂閱。
通過實(shí)現(xiàn)Source 和 Watcher即可實(shí)現(xiàn)任意配置源(本地或遠(yuǎn)程)的配置文件加載和變更訂閱。
已經(jīng)實(shí)現(xiàn)了下列插件:
Kratos定義了統(tǒng)一的注冊接口,通過實(shí)現(xiàn)Registrar和Discovery,您可以很輕松地將Kratos接入到您的注冊中心中。
您也可以直接使用我們已經(jīng)實(shí)現(xiàn)好的插件:
Kratos的日志模塊由兩部分組成:
我們已經(jīng)實(shí)現(xiàn)好的插件用于適配目前一些日志庫,您也可以參考它們的代碼來實(shí)現(xiàn)自己需要的日志庫的適配:
監(jiān)控告警方面,您可以通過實(shí)現(xiàn)metrics相關(guān)接口將服務(wù)的統(tǒng)計(jì)數(shù)據(jù)上報(bào)給監(jiān)控平臺(tái)。
也可以直接使用我們已經(jīng)實(shí)現(xiàn)好的插件:
Kratos使用OpenTelemetry作為分布式鏈路追蹤所使用的標(biāo)準(zhǔn),您可以通過對client和server配置tracing來將服務(wù)接入到鏈路追蹤平臺(tái)(如jaeger等),從而對服務(wù)的接口調(diào)用關(guān)系,耗時(shí),錯(cuò)誤等進(jìn)行追蹤。
Kratos內(nèi)置了若干種負(fù)載均衡算法,如Weighted round robin(默認(rèn))、P2C,Random等,您可以通過在client初始化時(shí)配置來使用他們。
Kratos提供了限流ratelimit和熔斷circuitbreaker中間件,用于微服務(wù)出現(xiàn)異常故障時(shí)自動(dòng)對流量進(jìn)行限制,提升服務(wù)的健壯性,避免雪崩。這兩個(gè)中間件使用的算法,也可以在我們的可用性算法倉庫aegis中找到,獨(dú)立于Kratos直接使用。
您可以通過Kratos的middleware機(jī)制,統(tǒng)一微服務(wù)接口的某些共同邏輯。上面提到的功能插件,您可以通過實(shí)現(xiàn)Middleware編寫Kratos能夠使用的中間件。
同時(shí)在倉庫的middleware目錄下,我們也提供了一系列中間件供您使用。
除了上述提到的插件外,我們還提供了一些其它插件,完整的插件列表請參考文檔社區(qū)插件
如果您看過文檔后,對某些功能的使用仍有疑惑,或者是希望尋找一些用Kratos寫項(xiàng)目的靈感,在倉庫的examples目錄下我們提供了很多代碼供參考。
更多建議: