共计 5655 个字符,预计需要花费 15 分钟才能阅读完成。
01 前言
Hertz 是字节跳动服务框架团队研发的超大规模的企业级微服务 HTTP 框架,具备高易用性、易扩大、低时延等特点。在通过了字节跳动外部一年多的应用和迭代后,现在已在 CloudWeGo 正式开源。目前,Hertz 曾经成为了字节跳动外部最大的 HTTP 框架,线上接入的服务数量超过 1 万,峰值 QPS 超过 4 千万。除了各个业务线的同学应用外,也服务于外部很多根底组件,如:函数计算平台 FaaS、压测平台、各类网关、Service Mesh 管制面等,均收到不错的应用反馈。在如此大规模的场景下,Hertz 领有极强的稳定性和性能,在外部实际中某些典型服务,如框架占比拟高的服务、网关等服务,迁徙 Hertz 后相比 Gin 框架,资源应用显著缩小,CPU 使用率随流量大小升高 30%—60%,时延也有明显降低。
Hertz 保持 内外保护一套代码,为开源应用提供了强有力的保障。通过开源,Hertz 也将丰盛云原生的 Golang 中间件体系,欠缺 CloudWeGo 生态矩阵,为更多开发者和企业搭建云原生化的大规模分布式系统,提供一种古代的、资源高效的的技术计划。
本文将重点关注 Hertz 的 架构设计 与性能个性。
02 我的项目缘起
最后,字节跳动外部的 HTTP 框架是对 Gin 框架的封装,具备不错的易用性、生态欠缺等长处。随着外部业务的一直倒退,高性能、多场景的需要日渐强烈。而 Gin 是对 Golang 原生 net/http 进行的二次开发,在按需扩大和性能优化上受到很大局限。因而,为了满足业务需要,更好的服务各大业务线,2020 年初,字节跳动服务框架团队通过外部应用场景和内部支流开源 HTTP 框架 Fasthttp、Gin、Echo 的调研后,开始基于自研网络库 Netpoll 开发外部框架 Hertz,让 Hertz 在面对企业级需要时,有更好的性能及稳定性体现,也可能满足业务倒退和应答一直演进的技术需要。
03 架构设计
Hertz 设计之初调研了大量业界优良的 HTTP 框架,同时参考了近年来外部实际中积攒的教训。为了保障框架整体上满足:1. 极致性能优化的可能性;2. 面对将来不可控需要的扩大能力,Hertz 采纳了 4 层分层设计,保障各个层级性能内聚,同时通过层级之间的接口达到灵便扩大的指标。整体架构图如图 1 所示。
图 1 Hertz 架构图
Hertz 从上到下分为:应用层、路由层、协定层和传输层,每一层各司其职,同时公共能力被对立形象到公共层(Common),做到跨层级复用。另外,同主库一起公布的还有作为子模块的 Hz 脚手架,它可能帮助使用者疾速搭建出我的项目外围骨架以及提供实用的构建工具链。
应用层
应用层是和用户间接交互的一层,提供丰盛易用的 API,次要包含 Server、Client 和一些其余通用形象。Server 提供了注册 HandlerFunc、Binding、Rendering 等能力;Client 提供了调用上游和服务发现等能力;以及形象一个 HTTP 申请所必须波及到的申请(Request)、响应(Response)、上下文(RequestContext)、中间件(Middleware)等等。Hertz 的 Server 和 Client 都可能提供中间件这样的扩大能力。
应用层中一个十分重要的形象就是对 Server HandlerFunc 的形象。晚期,Hertz 路由的处理函数(HandlerFunc)中并没有接管规范的 context.Context,咱们在大量的实际过程中发现,业务方通常须要一个规范的上下文在 RPC Client 或者日志、Tracing 等组件间传递,但因为申请上下文(RequestContext)生命周期局限于一次 HTTP 申请之内,而以上提到的场景往往存在异步的传递和解决,导致如果间接传递申请上下文,会导致呈现一些数据不统一的问题。为此咱们做了诸多尝试,然而因为外围起因在于申请上下文(RequestContext)的生命周期无奈优雅的按需缩短,最终在各种设计衡量下,咱们在路由的处理函数签名中减少一个规范的上下文入参,通过拆散出生命周期长短各异的两个上下文的形式,从根本上解决各种因为上下文生命周期不统一导致的异样问题,即:
路由层
路由层负责依据 URI 匹配对应的处理函数。
起初,Hertz 的路由基于 httprouter 开发,但随着应用的用户越来越多,httprouter 慢慢不可能满足需要,次要体现在 httprouter 不可能同时注册动态路由和参数路由,即 /a/b
,/:c/d
这两个路由不可能同时注册;甚至有一些更非凡的需要,如 /a/b
、/:c/b
,当匹配 /a/b
路由时,两个路由都可能匹配上。
Hertz 为满足这些需要从新结构了路由树,用户在注册路由时领有很高的自由度:反对动态路由、参数路由的注册;反对按优先级匹配,如上述例子会优先匹配动态路由 /a/b
;反对路由回溯,如注册 /a/b
、/:c/d
,当匹配 /a/d
时依然可能匹配上;反对尾斜线重定向,如注册 /a/b,当匹配 /a/b/ 时可能重定向到 /a/b
上。Hertz 提供了丰盛的路由能力来满足用户的需要,更多的性能能够参考 Hertz 配置文档。
httprouter:https://github.com/julienschm…
Hertz 配置文档:https://www.cloudwego.io/zh/d…
协定层
协定层负责不同协定的实现和扩大。
Hertz 反对协定的扩大,用户只须要实现上面的接口便能够依照本人的需要在引擎(Engine)上扩大协定,同时也反对通过 ALPN 协定协商的形式注册。Hertz 首批只开源了 HTTP1 实现,将来会陆续开源 HTTP2、QUIC 等实现。协定层扩大提供的灵活性甚至能够超过 HTTP 协定的领域,用户齐全能够按需注册任意合乎本身需要的协定层实现,并且退出到 Hertz 的引擎中来,同时,也可能无缝享受到传输层带来的极致性能。
协定扩大:
https://www.cloudwego.io/zh/d…
传输层
传输层负责底层的网络库的形象和实现。
Hertz 反对底层网络库的扩大。Hertz 原生完满适配 Netpoll,在时延方面有很多深度的优化,非常适合时延敏感的业务接入。Netpoll 对 TLS 能力的反对有待欠缺,而 TLS 能力又是 HTTP 框架必备能力,为此 Hertz 底层同时反对基于 Golang 规范网络库的实现适配,同时反对网络库的一键切换,用户可依据本人的需要抉择适合的网络库进行替换。如果用户有更加高效的网络库或其余网络库需要,也齐全能够依据需要自行扩大。
网络库的扩大:
https://www.cloudwego.io/zh/d…
Hz 脚手架
与 Hertz 一并开源的还有一个易用的命令行工具 Hz,用户只需提供一个 IDL,依据定义好的接口信息,Hz 便能够一键生成我的项目脚手架,让 Hertz 达到开箱即用的状态;Hz 也反对基于 IDL 的更新能力,可能基于 IDL 变动智能地更新我的项目代码。目前 Hz 反对了 Thrift 和 Protobuf 两种 IDL 定义。命令行工具内置丰盛的选项,能够依据本人的需要应用。同时它底层依赖 Protobuf 官网的编译器和自研的 Thriftgo 的编译器,两者都反对自定义的生成代码插件。如果默认模板不可能满足需要,齐全可能按需定义。
将来,咱们将持续迭代 Hz,继续集成各种罕用的中间件,提供更高层面的模块化构建能力。给 Hertz 的用户提供按需调整的能力,通过灵便的自定义配置打造一套满足本身开发需要的脚手架。
Common 组件
Common 组件次要寄存一些公共的能力,比方错误处理、单元测试能力、可观测性相干能力(Log、Trace、Metrics 等)。对于服务可观测性的能力,Hertz 提供了默认的实现,用户能够按需拆卸;如果用户有非凡的需要,也能够通过 Hertz 提供的接口注入。比方对于 Trace 能力,Hertz 提供了默认的实现,也提供了将 Hertz 和 Kitex 串起来的 Example。如果想注入本人的实现,也能够实现上面的接口:
Example:
https://github.com/cloudwego/…
04 性能个性
中间件
Hertz 除了提供 Server 的中间件能力,还提供了 Client 中间件能力。用户能够应用中间件能力将通用逻辑(如:日志记录、性能统计、异样解决、鉴权逻辑等等)和业务逻辑辨别开,让用户更加专一于业务代码。Server 和 Client 中间件应用形式雷同,应用 Use 办法注册中间件,中间件执行程序和注册程序雷同,同时反对预处理和后处理逻辑。
Server 和 Client 的中间件实现形式并不相同。对于 Server 来说,咱们心愿缩小栈的深度,同时也心愿中间件可能默认的执行下一个,用户须要手动终止中间件的执行。因而,咱们将 Server 的中间件分成了两种类型,即不在同一个函数调用栈(该中间件调用完后返回,由上一个中间件调用下一个中间件,如图 2 中 B 和 C)和在同一个函数调用栈的中间件(该中间件调用完后由该中间件持续调用下一个中间件,如图 2 中 C 和 Business Handler)。
图 2 中间件链路
其外围是须要一个中央存下以后的调用地位 index,并始终保持其递增。恰好 RequestContext 就是一个存储 index 适合的地位。然而对于 Client,因为没有适合的中央存储 index,咱们只能退而求其次,摈弃 index 的实现,将所有的中间件结构在同一调用链上,须要用户手动调用下一个中间件。
流式解决
Hertz 提供 Server 和 Client 的流式解决能力。HTTP 的文件场景是非常常见的场景,除了 Server 侧的上传场景之外,Client 的下载场景也非常常见。为此,Hertz 反对了 Server 和 Client 的流式解决。在外部网关场景中,从 Gin 迁徙到 Hertz 后,CPU 使用量随流量大小不同可节俭 30%—60% 不等,服务压力越大,收益越大。Hertz 开启流式性能的形式也很容易,只须要在 Server 上或 Client 上增加一个配置即可,可参考 CloudWeGo 官网 Hertz 文档的流式解决局部。
因为 Netpoll 采纳 LT 的触发模式,由网络库被动将将数据从 TCP 缓冲区读到用户态,并存储到 buffer 中,否则 epoll 事件会继续触发。因而 Server 在超大申请的场景下,因为 Netpoll 继续将数据读到用户态内存中,可能会有 OOM 的危险。HTTP 文件上传场景就是一个典型的场景,但 HTTP 上传服务又是很常见的场景,因而咱们反对规范网络库 go net,并针对 Hertz 做了非凡优化,暴露出 Read()
接口,避免 OOM 产生。
对于 Client,状况并不相同。流式场景下会将连贯封装成 Reader
裸露给用户,而 Client 有连接池治理,那这样连贯就多了一种状态,何时关连贯,何时复用连接成了一个问题。因为框架侧并不知道该连贯何时会用完,框架侧复用该连贯不事实,会导致串包问题。因为 GC 会敞开连贯,因而咱们起初构想流式场景下的连贯交由用户后,由 GC 负责敞开,这样也不会导致资源透露。然而在测试后发现,因为 GC 存在肯定工夫距离,另外 TCP 中被动敞开连贯的一方须要期待 2RTT,在高并发场景下会导致 fd 被打满的状况。最终咱们提供了复用连贯的接口,对于性能有场要求用户,在应用完连贯后能够将连贯从新放入连接池中复用。
流式解决:https://www.cloudwego.io/zh/d…
05 性能体现
Hertz 应用字节跳动自研高性能网络库 Netpoll,在进步网络库效率方面有诸多实际,参考已公布文章字节跳动在 Go 网络库上的实际。除此之外,Netpoll 还针对 HTTP 场景进行优化,通过缩小拷贝和零碎调用次数进步吞吐以及升高时延。为了掂量 Hertz 性能指标,咱们选取了社区中有代表性的框架 Gin(net/http)和 Fasthttp 作为比照,如图 3 所示。能够看到,Hertz 的极限吞吐、TP99 等指标均处于业界领先水平。将来,Hertz 还将持续和 Netpoll 深度配合,摸索 HTTP 框架性能的极限。
图 3 Hertz 和其余框架性能比照
06 一个 Demo
上面简略演示一下 Hertz 是如何开发一个服务的。
- 首先,定义 IDL,这里应用 Thrift 作为 IDL 的定义(也反对应用 Protobuf 定义的 IDL),编写一个名为 Demo 的 service。这个服务有一个 API: Hello,它的申请参数是一个 query,响应是一个蕴含一个 RespBody 字段的 Json。
- 接下来咱们应用 Hz 生成代码,并整顿和拉取依赖。
- 填充业务逻辑,比方咱们返回
hello,${Name}
,那咱们在biz/handler/example/hello_service.go
中增加以下代码即可。
- 编译并运行我的项目。
到当初一个简略的 Hertz 我的项目曾经生成,上面咱们来测试一下。
以上 Demo 能够在 Hertz-Examples 中查看,之后就能够欢快地构建本人的我的项目了。
07 后记
心愿以上的分享可能让大家对 Hertz 有一个整体上的意识。同时,咱们也在一直地迭代 Hertz、欠缺 CloudWeGo 整体生态。欢送各位感兴趣的同学们退出咱们,独特建设 CloudWeGo。
08 参考资料
Hertz:https://github.com/cloudwego/…
Hertz Doc:https://www.cloudwego.io/zh/d…
字节跳动在 Go 网络库上的实际: https://www.cloudwego.io/zh/b…
我的项目地址
GitHub:https://github.com/cloudwego
官网:www.cloudwego.io
流动预报
6 月 25 日,CloudWeGo & 稀土掘金 Meetup 直播流动行将开始,邀请到来自字节跳动、森马电商和华兴证券的资深开发者,向社区分享 CloudWeGo 的最新企业落地实际。预约流动可领周边大礼!
流动预约链接:https://mp.weixin.qq.com/s/D9…