字节跳动开源 Go HTTP 框架 Hertz 设计实践( 二 )


Hertz 为满足这些需求重新构造了路由树,用户在注册路由时拥有很高的自由度:支持静态路由、参数路由的注册;支持按优先级匹配,如上述例子会优先匹配静态路由 /a/b ;支持路由回溯,如注册 /a/b、/:c/d,当匹配 /a/d 时仍然能够匹配上;支持尾斜线重定向,如注册 /a/b,当匹配 /a/b/ 时能够重定向到 /a/b 上 。Hertz 提供了丰富的路由能力来满足用户的需求,更多的功能可以参考 Hertz 配置文档 。
协议层协议层负责不同协议的实现和扩展 。
Hertz 支持协议的扩展,用户只需要实现下面的接口便可以按照自己的需求在引擎(Engine) 上扩展协议,同时也支持通过 ALPN 协议协商的方式注册 。Hertz 首批只开源了 HTTP1 实现,未来会陆续开源 HTTP2、QUIC 等实现 。协议层扩展提供的灵活性甚至可以超越 HTTP 协议的范畴,用户完全可以按需注册任意符合自身需求的协议层实现,并且加入到 Hertz 的引擎中来,同时,也能够无缝享受到传输层带来的极致性能 。
type ServerFactory interface {New(core Core) (server protocol.Server, err error)}type Server interface {Serve(c context.Context, conn network.Conn) error}传输层传输层负责底层的网络库的抽象和实现 。
Hertz 支持底层网络库的扩展 。Hertz 原生完美适配 Netpoll,在时延方面有很多深度的优化,非常适合时延敏感的业务接入 。Netpoll 对 TLS 能力的支持有待完善,而 TLS 能力又是 HTTP 框架必备能力,为此 Hertz 底层同时支持基于 Golang 标准网络库的实现适配,支持网络库的一键切换,用户可根据自己的需求选择合适的网络库进行替换 。如果用户有更加高效的网络库或其他网络库需求,也完全可以根据需求自行扩展 。
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 。如果想注入自己的实现,也可以实现下面的接口:
// Tracer is executed at the start and finish of an HTTP.type Tracer interface {Start(ctx context.Context, c *app.RequestContext) context.ContextFinish(ctx context.Context, c *app.RequestContext)}功能特性中间件Hertz 除了提供 Server 的中间件能力,还提供了 Client 中间件能力 。用户可以使用中间件能力将通用逻辑(如:日志记录、性能统计、异常处理、鉴权逻辑等等)和业务逻辑区分开,让用户更加专注于业务代码 。Server 和 Client 中间件使用方式相同,使用 Use 方法注册中间件,中间件执行顺序和注册顺序相同,同时支持预处理和后处理逻辑 。
Server 和 Client 的中间件实现方式并不相同 。对于 Server 来说,我们希望减少栈的深度,同时也希望中间件能够默认的执行下一个,用户需要手动终止中间件的执行 。因此,我们将 Server 的中间件分成了两种类型,即不在同一个函数调用栈(该中间件调用完后返回,由上一个中间件调用下一个中间件,如图 2 中 B 和 C)和在同一个函数调用栈的中间件(该中间件调用完后由该中间件继续调用下一个中间件,如图 2 中 C 和 Business Handler) 。

字节跳动开源 Go HTTP 框架 Hertz 设计实践

文章插图
 
图 2: 中间件链路
其核心是需要一个地方存下当前的调用位置 index,并始终保持其递增 。恰好 RequestContext 就是一个存储 index 合适的位置 。但是对于 Client,由于没有合适的地方存储 index,我们只能退而求其次,抛弃 index 的实现,将所有的中间件构造在同一调用链上,需要用户手动调用下一个中间件 。


推荐阅读