砥砺前行 | Kratos 框架 v2 版本架构演进之路
Kratos 是一套轻量级 Go 微服务框架,包含大量微服务相关功能及工具。名字来源于游戏《战神》,该游戏以希腊神话为背景,讲述了奎托斯(Kratos)由凡人成为战神并展开弑神屠杀的冒险历程。
写在前面
从 2021 年 2 月份,github 上 kratos v2
(下文简称 kratos)版本第一次代码提交,到功能模块的讨论,修改,测试,最终定稿,已经过去了 13 个月,在社区各位伙伴的贡献下,kratos v2
已经从 2.0.0 alpha1
版本迭代到了 2.2.1
版本,已经具备微服务框架的完整能力。在此感谢各位社区伙伴的贡献。
概览
kratos v1
(下文简称 v1) 版本在设计时,后期的可扩展性考虑较少,框架模块与实现强依赖,类似于全家桶,导致框架本身灵活性不高,框架使用者无法更换框架模块的具体实现,没有办法在不衍生下游版本的前提下对框架功能实现进行替换,在实际的企业开发中对于企业的多样化需求,无法轻松地应对,遇到这种需求时,只能通过修改框架代码来实现。而kratos v2
版本它更像是一个采用 Go 语言构建微服务的工具箱,开发者可以按照自己的习惯像搭积木一样来构建自己的微服务。也正是由于这样的原因,kratos v2
并不会直接绑定某种特定的基础设施,所以可以很轻松地将任意您想要的库集成到项目中,与 kratos v2
共同协作。
kratos v2
版本的设计思想就是支持高度自由的定制化,框架制定接口规范,然后通过插件来实现具体需求,实现高度可拔插的微服务框架,企业在开发时可以选择框架已经提供的插件实现,也可以自己定制插件,实现了高度的可定制。并且在 kratos v2
版本中 API定义
、gRPC Service
、HTTP Service
、请求参数校验
、错误定义
、Swagger API json
、应用配置模版
等都是基于 Protobuf IDL 来构建的:
项目生态
围绕着 kratos v2
版本的核心设计理念,设计了如下的项目生态:
kratos
框架核心,包含了基础的 CLI 工具,内置支持了 HTTP/gRPC 传输协议,提供了服务的完整声明周期管理,提供了如 API 、日志 、错误处理、 配置、监控、序列化、注册发现、元数据传递、传输层、中间件等组件能力和相关接口定义。contrib
基于框架核心定义的基础接口,实现了对配置文件、日志系统、服务发现、监控等基础服务设施的适配,可以让开发者直接集成到 Kratos 项目中来。aegis
服务可用性的相关算法如:限流、熔断等。算法放在了独立的项目中,几乎没有外部依赖,即使您的项目没有依赖 Kratos 框架,您也可以直接任意项目中使用它。layout
参考了《领域驱动设计》和《简洁架构设计》的项目模板,并且提供了 Makefile 脚本和 Dockerfile 文件。我们推荐您使用 kratos 提供的项目结构,但您可以随意修改这个模板,或者使用自己喜欢的项目结构,框架本身不对项目做任何限制,您可以按照自己的想法来使用,具有很强的可定制性。gateway
一个使用 Go 语言开发的 API Gateway,后续您可以使用它作为您项目的微服务网关,用于微服务 API 的治理,项目正在研发中,敬请期待。
架构设计
kratos v2
版本在设计阶段主要进行了以下几个方面的思考:
面向包的设计理念
Transport HTTP/gRPC
应用生命周期管理
配置规范的思考
业务错误的设计
日志接口的设计
Metadata 传递和使用
Middleware 使用
简化的 DDD layout 实现
面向包的设计理念
在 kratos v2
框架中,我们主要是参考了 Go 的基础库设计思想,包名按照实际功能划分,每个包都具有单一的职责,当用户不可见或者不稳定的接口放到了/internal 目录中。并且在框架中不同包具有不同的功能特性:
/cmd
cmd 中包含了可以通过 go install 或 go get 一键安装的命令行工具,使用户可以更加方便的使用框架。/errors
统一的业务错误封装,便捷的返回错误码以及具体的业务错误原因。/config
支持多数据源接入,可以对配置进行合并,平铺,通过 Atomic 方式支持配置热更新。/transport
传输层(HTTP/gRPC)的抽象封装。/middleware
中间件的抽象接口,主要作为 transport 和 service 之间的桥梁适配器。/metadata
跨服务跨协议间的元数据传递及使用/registry
注册中心的抽象接口,可以实现支持各种服务注册与发现中心,如:etcd、consul、nacos。
Transport HTTP/gRPC
kratos v2
框架对传输层进行了抽象,用户也可以实现自己的传输层,框架默认实现了 gRPC
和 HTTP
两种通信协议传输层。Transport
主要的接口:
// 服务的启动和停止,用于管理服务生命周期。
type Server interface {Start(context.Context) errorStop(context.Context) error
}// 用于实现注册到注册中心的终端地址
// 如果不实现这个方法则不会注册到注册中心
type Endpointer interface {Endpoint() (*url.URL, error)
}// 请求头的元数据
type Header interface {Get(key string) stringSet(key string, value string)Keys() []string
}// Transporter is transport context value interface.
type Transporter interface {// 代表实现的通讯协议的类型。Kind() Kind// 提供的服务终端地址。Endpoint() string// 用于标识服务的方法路径Operation() stringRequestHeader() HeaderReplyHeader() Header
}
应用生命周期管理
在 kratos v2
中,可以通过实现 transport.Server
接口,然后通过 kratos.New 启动器进行管理服务生命周期。启动器主要处理:
server 生命周期管理
registry 注册中心管理
// AppInfo is application context value.
type AppInfo interface {ID() stringName() stringVersion() stringMetadata() map[string]stringEndpoint() []string
}
配置规范的思考
在使用 kratos v2
中,配置源可以指定多个,并且 Config
包会对配置合并成 key/value
,然后用户可以通过 Scan
或者 value
获取对应键值的内容,主要功能如下:
内置实现了基于本地文件的数据源。
用户可以通过插件接入自定义数据源如:nacos、consul、apollo 等。
支持配置热更新(watch),通过 Atomic 方式变更已有键的值。
支持自定义数据源 Decode 实现。
支持对 flags、环境变量 占位符的替换。
可以对铺平的 key/value,进行二次赋值替换。
配置规范的思考
在 kratos v2
中,默认通过 proto 定义配置的模板,主要有以下几点好处:
可以定义统一的模板配置
添加对应的配置校验
更好的管理配置
多语言支持
message Bootstrap {Server server = 1;Data data = 2;
}message Server {message HTTP {string network = 1;string addr = 2;google.protobuf.Duration timeout = 3;}message GRPC {string network = 1;string addr = 2;google.protobuf.Duration timeout = 3;}HTTP http = 1;GRPC grpc = 2;
}message Data {message Database {string driver = 1;string source = 2;}message Redis {string network = 1;string addr = 2;google.protobuf.Duration read_timeout = 3;google.protobuf.Duration write_timeout = 4;}Database database = 1;Redis redis = 2;
}
server:http:addr: 0.0.0.0:8000timeout: 1sgrpc:addr: 0.0.0.0:9000timeout: 1s
data:database:driver: mysqlsource: root:root@tcp(127.0.0.1:3306)/testredis:addr: 127.0.0.1:6379read_timeout: 0.2swrite_timeout: 0.2s
业务错误处理
在 kratos v2
中,业务错误主要通过 proto enum
进行定义。在 errors
包中,主要实现了 HTTP
和 gRPC
的接口:
StatusCode() int
GRPCStatus() *grpc.Status
业务错误,主要参考了 gRPC errdetails.ErrorInfo
的实现:
code
错误码,跟 http-status 一致,并且在 grpc 中可以转换为 grpc-status。message
错误信息,用户可读的信息,可作为用户提示内容。reason
错误原因,定义为业务判定的错误码。metadata
错误元信息,可以向错误附加可扩展信息。
实际使用
编写 proto
文件
syntax = "proto3";package helloworld.v1;
import "errors/errors.proto";option go_package = "github.com/go-kratos/kratos-layout/api/helloworld/v1;v1";
option java_multiple_files = true;
option java_package = "helloworld.v1.errors";
option objc_class_prefix = "APIHelloworldErrors";enum ErrorReason {USER_NOT_FOUND = 0;CONTENT_MISSING = 1;
}
在 biz
中依赖 proto enum
定义错误
var (// ErrUserNotFound is user not found.ErrUserNotFound = errors.NotFound(v1.ErrorReason_USER_NOT_FOUND.String(), "user not found")
)
使用错误
func (uc *GreeterUsecase) CreateGreeter(ctx context.Context, g *Greeter) (*Greeter, error) {uc.log.WithContext(ctx).Infof("CreateGreeter: %v", g.Hello)save, err := uc.repo.Save(ctx, g)if err != nil {return nil, ErrUserNotFound.WithMetadata(map[string]string{"error":err.Error()})}return save, nil
}
判定错误
// reasonif err != nil {if errors.Reason(err) == v1.ErrorReason_USER_NOT_FOUND.String() {// TODO: do something}return nil, err}// errors.Asif err != nil {if se := new(errors.Error); errors.As(err,&se) {switch se.Reason {case v1.ErrorReason_USER_NOT_FOUND.String():// TODO: do something}}}// errors.Isif err != nil {if errors.Is(err, ErrUserNotFound) {// TODO: do something}}
日志接口设计
在 kratos v2
日志模块中,主要分为 Logger
、Helper
、Filter
、Valuer
的实现。为了方便扩展,Logger
接口定义非常简单:
type Logger interface {Log(level Level, keyvals ...interface{}) error
}
这个 Logger
接口,非常容易组合和扩展:
// 也可以定义多种日志输出 log.MultiLogger(out, err),例如:info/warn/error,file/agent
logger := log.NewStdLogger(os.Stdout)å
// 根据日志级别进行过虑日志,或者 Key/Value/FilterFunc
logger := log.NewFilter(logger, log.FilterLevel(log.LevelInfo))
// 输出结构化日志
logger.Log(log.LevelInfo, "msg", "log info")
如果需要过滤日志中某些不应该被打印明文的字段,例如 password 等信息,可以通过 `log.NewFilter()` 来实现过滤功能。
logger := log.NewFilter(
log.DefaultLogger,
log.FilterLevel(log.LevelInfo), // 通过 Level 过滤日志
log.FilterKey("password"), // 通过 Key 过滤日志
log.FilterValue("123456"), // 通过 Value 过滤日志
log.FilterFunc(func(level Level, keyvals ...interface{}) bool { // 通过自定义 FilterFuncreturn level == log.LevelError})
logger.Log(log.LevelInfo, "password", "123456") // 输出格式为:password=***
通常在使用日志的过程中,我们可以通过 log.With()
和 Hook
定制 Fields
,例如 timestamp
、caller
、trace
等。在 kratos
日志模块中,主要通过实现 Valuer
进行定制化。
type Valuer func(ctx context.Context) interface{}
func Value(ctx context.Context, v interface{}) interface{} {if v, ok := v.(Valuer); ok {return v(ctx)}return v
}
所以,我们在 kratos v2
项目中可以这样使用日志模块:
logger := log.NewStdLogger(os.Stdout)
logger = log.NewFilter(logger, log.FilterLevel(log.LevelInfo))
logger = log.With(logger, "app", "helloworld","ts", log.DefaultTimestamp,"caller", log.DefaultCaller,"trace_id", log.TraceID(),"span_id", log.SpanID(),
)
helper := log.NewHelper(logger)
helper.WithContext(ctx).Info("info log")
Metadata 传递和使用
微服务之间主要通过 HTTP/gRPC
进行接口交互,所以在服务架构中应该进行统一的元数据传递和使用。在 HTTP/gRPC
中,其实是通过 HTTP Header
进行传递,在框架中首先通过 metadata
包将元数据封装成 key/value
结构,然后携带到 Transport Header
中。
Metadata
默认 Key
格式为:
x-md-blobal-xxx
全局传递,例如mirror
、color
、criticality
x-md-local-xxx
局部传递,例如caller
并且用户可以在 middleware/metadata 中定制自己的key prefix
,配置固定的元数据传递。
使用
Metadata
的主要用法为:
配置
client/server
对应的middleware/metadata
插件,可以自定义传递key prefix
,或者metadata
常量,例如caller
。然后通过
metadata
包,NewClientContext
或者FromServerContext
进行配置或者获取。
// server
grpcSrv := grpc.NewServer(grpc.Address(":9000"),grpc.Middleware(metadata.Server(),),
)
// client
conn, err := grpc.DialInsecure(context.Background(),grpc.WithEndpoint("127.0.0.1:9000"),grpc.WithMiddleware(metadata.Client(),),
)
// 获取
if md, ok := metadata.FromServerContext(ctx); ok {extra = md.Get("x-md-global-extra")
}
// 传递
ctx = metadata.AppendToClientContext(ctx, "x-md-global-extra", "2233")
Middleware 使用
kratos v2
内置了一系列的中间件用于处理日志、指标、跟踪链等通用场景。用户也可以通过实现 Middleware
接口,开发自定义 middleware
,进行通用的业务处理,比如用户鉴权等。主要的内置中间件:
recovery
用于 recovery panictracing
用于启用 tracelogging
用于请求日志的记录metrics
用于启用 metricsvalidate
用于处理参数校验metadata
用于启用元信息传递etc...
简化的 DDD 实现
如果你尝试学习 Go,或者你正在为自己建立一个 PoC
或一个玩具项目,这个项目布局是没啥必要的。从一些非常简单的事情开始(一个 main.go 文件绰绰有余)。当有更多的人参与这个项目时,你将需要更多的结构,包括需要一个 Toolkit
来方便生成项目的模板,尽可能大家统一的工程目录布局。
.
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── api // 下面维护了微服务使用的proto文件以及根据它们所生成的go文件
│ └── helloworld
│ └── v1
│ ├── error_reason.pb.go
│ ├── error_reason.proto
│ ├── error_reason.swagger.json
│ ├── greeter.pb.go
│ ├── greeter.proto
│ ├── greeter.swagger.json
│ ├── greeter_grpc.pb.go
│ └── greeter_http.pb.go
├── cmd // 整个项目启动的入口文件
│ └── server
│ ├── main.go
│ ├── wire.go // 我们使用wire来维护依赖注入
│ └── wire_gen.go
├── configs // 这里通常维护一些本地调试用的样例配置文件
│ └── config.yaml
├── generate.go
├── go.mod
├── go.sum
├── internal // 该服务所有不对外暴露的代码,通常的业务逻辑都在这下面,使用internal避免错误引用
│ ├── biz // 业务逻辑的组装层,类似 DDD 的 domain 层,data 类似 DDD 的 repo,repo 接口在这里定义,使用依赖倒置的原则。
│ │ ├── README.md
│ │ ├── biz.go
│ │ └── greeter.go
│ ├── conf // 内部使用的config的结构定义,使用proto格式生成
│ │ ├── conf.pb.go
│ │ └── conf.proto
│ ├── data // 业务数据访问,包含 cache、db 等封装,同时也是 rpc 调用的 acl 防腐层,它实现了 biz 的 repo 接口。我们可能会把 data 与 dao 混淆在一起,data 偏重业务的含义,它所要做的是将领域对象重新拿出来,我们去掉了 DDD 的 infra层。
│ │ ├── README.md
│ │ ├── data.go
│ │ └── greeter.go
│ ├── server // http和grpc实例的创建和配置
│ │ ├── grpc.go
│ │ ├── http.go
│ │ └── server.go
│ └── service // 实现了 api 定义的服务层,类似 DDD 的 application 层,处理 DTO 到 biz 领域实体的转换(DTO -> DO),同时协同各类 biz 交互,但是不应处理复杂逻辑
│ ├── README.md
│ ├── greeter.go
│ └── service.go
└── third_party // api 依赖的第三方proto├── README.md├── google│ └── api│ ├── annotations.proto│ ├── http.proto│ └── httpbody.proto└── validate├── README.md└── validate.proto
未来规划
综上可见,kratos v2
是一款凝结了开源社区力量以及 Go 同学们大量微服务工程实践后诞生的一款微服务框架,现阶段 kratos v2
框架已经功能逐渐完善,后续先期会将精力主要放在 kratos gateway
上,同时会开始 Kratos API interface
和服务治理平台 Kratos ui
的规划。在此也欢迎广大 gopher 加入 kratos
社区参与到 kratos
相关生态的开发中。
相关资料
官网
go-kratos.devkratos
https://github.com/go-kratos/kratoskratos gateway
https://github.com/go-kratos/gatewaykratos contrib
https://github.com/go-kratos/kratos/tree/main/contribkratos aegis
https://github.com/go-kratos/aegis
参考阅读:
B+树数据库加锁历史
前端工程化之FaaS SSR方案
Kafka 3.0新特性全面曝光,真香!
BIGO RTC如何低成本实现高画质
一文读懂 Web3:互联网发展的新时代还是骗局?
本文由高可用架构翻译。技术原创及架构实践文章,欢迎通过公众号菜单「联系我们」进行投稿。
高可用架构
改变互联网的构建方式
砥砺前行 | Kratos 框架 v2 版本架构演进之路相关推荐
- CTS(20)---CTS测试框架 -- V2版本
CTS测试框架 -- V2版本 目录 概述 组织case 入口CompatibilityConsole ModuleRepo 组件CompatibilityTest 执行测试 总结 1 概述 在An ...
- 阿里千亿级购物节背后,淘宝智能客服架构演进之路
" 淘宝上每天都有上百万的客服在线为上亿的买家提供服务,客服服务平台也从一个简单的分流系统逐步演进到覆盖买家.客服和客服主管三位一体的平台解决方案. 作者简介:淘宝技术部-媒体技术与消费连接 ...
- 从零到百亿级,揭秘科大讯飞广告平台架构演进之路
作者:仉乾隆 https://www.infoq.cn/article/PkWo_g6G5YGaXEiT8lm9 广告.电商和游戏是互联网变现的三个最主要手段,而电商中除了直接卖东西的部分,其他本质上 ...
- 服务端高并发分布式架构演进之路(转载,图画的好)
这个文章基本上从单机版到最终版,经历了加缓存,加机器,高可用,分布式,最后到云等过程,其实我一直想总结一套类似的东西,没想到有人已经先弄出来了,那就不重复造轮子了,而且我感觉这个文章也是花了功夫的. ...
- 今日头条架构演进之路——高压下的架构演进专题(含PPT)
今日头条架构演进之路--高压下的架构演进专题(含PPT) 原创 2016-07-06 夏绪宏 高可用架构 导读:高可用架构在 6 月 25 日举办了『高压下的架构演进』专题沙龙,进行了闭门私董会研讨及 ...
- SAP-R3被取代,苏宁采购平台的升级和架构演进之路
前言 在"智慧零售大开发"的战略驱动下,2018 年苏宁新开门店超过 8000 家,目前各类门店总数已经超过 1.1 万家,在线下形成了"两大两小多专"的智慧零 ...
- 抖音、美团等大厂千万级用户的Android客户端架构演进之路—
在移动开发中,对开发者来说不同的人具有不同的能力.就像读一本书一样,一千个读者,有一千个哈姆雷特.但不管怎样,只要你是个软件开发者你就必须学习windows或Linux等操作系统的运行原理.Andro ...
- 从新手到架构师,一篇就够:从100到1000万高并发的架构演进之路
1.引言 本文以设计淘宝网的后台架构为例,介绍从一百个并发到千万级并发情况下服务端的架构的14次演进过程,同时列举出每个演进阶段会遇到的相关技术,让大家对架构的演进有一个整体的认知.文章最后汇总了一些 ...
- 微信团队分享:微信直播聊天室单房间1500万在线的消息架构演进之路
本文由微信开发团队工程师" kellyliang"原创发表于"微信后台团队"公众号,收录时有修订和改动. 1.引言 随着直播和类直播场景在微信内的增长,这些业务 ...
最新文章
- 如何在高精度下求解亿级变量背包问题?
- Thread类中yield方法
- Topcoder SRM 630div 2
- 分布式系统概念 | 分布式理论:CAP、BASE
- 实现option上下移动_Perona-Malik方程(各向同性非线性扩散实现图像滤波)
- 在JDK 12精简数字格式中使用最小分数数字
- shell菜鸟学习之echo命令
- java运算符重载_为什么Java不支持运算符重载?
- c语言 465串口编程,用C语言编写串口程序
- [译] 实例解析 ES6 Proxy 使用场景
- LED产品认证和检测
- Hbase+JAVAWeb实现超市仓库管理系统
- matlab pi调节器,pi调节器的输入和输出_pi调节器的传递函数
- 常用网站有哪些,最常用的网站
- Xcode8注释快捷键不能使用
- Linux 下 va_start、va_end 学习及使用
- matlab上位机串口通信中如何发送16进制数,而不是当做ASCII字符发送(已实测成功)
- 如何自己开发一个Android APP(2)——项目框架
- 望(dream-coastline 3.0)
- html识别文字转语音,Speech Synthesis API入门 - web前端识别文字转语音
热门文章
- yarn create umi报错
- C# DataGridView 获取当前单元格输入内容
- 如何连接MySQL数据库
- android 按钮旋转等待,旋转框架布局,其中包含动态按钮
- 微星主板节能模式怎么关闭_649元华硕H410主板天坑?英特尔酷睿I5处理器降频的秘密...
- Go Modules详解
- Allure的安装与使用
- SEO的大忌之采集与群发
- IOS当前城市定位、城市列表选择(省市结构)
- 游戏全面“入侵”生活:Python发布“酷跑+”计划