这是我参与「第五届青训营 」伴学笔记创作活动的第 7 天
简介
Hertz[həːts] 是一个 Golang 微服务 HTTP 框架,在设计之初参考了其他开源框架 fasthttp、gin、echo 的优势, 并结合字节跳动内部的需求,使其具有高易用性、高性能、高扩展性等特点,目前在字节跳动内部已广泛使用。 如今越来越多的微服务选择使用 Golang,如果对微服务性能有要求,又希望框架能够充分满足内部的可定制化需求,Hertz 会是一个不错的选择。
架构设计
框架特点
-
高易用性
在开发过程中,快速写出来正确的代码往往是更重要的。因此,在 Hertz 在迭代过程中,积极听取用户意见,持续打磨框架,希望为用户提供一个更好的使用体验,帮助用户更快的写出正确的代码。
-
高性能
Hertz 默认使用自研的高性能网络库 Netpoll,在一些特殊场景相较于 go net,Hertz 在 QPS、时延上均具有一定优势。关于性能数据,可参考下图 Echo 数据。
四个框架的对比:
三个框架的对比:
-
高扩展性
Hertz 采用了分层设计,提供了较多的接口以及默认的扩展实现,用户也可以自行扩展。同时得益于框架的分层设计,框架的扩展性也会大很多。目前仅将稳定的能力开源给社区,更多的规划参考 RoadMap。
-
多协议支持
Hertz 框架原生提供 HTTP1.1、ALPN 协议支持。除此之外,由于分层设计,Hertz 甚至支持自定义构建协议解析逻辑,以满足协议层扩展的任意需求。
-
网络层切换能力
Hertz 实现了 Netpoll 和 Golang 原生网络库 间按需切换能力,用户可以针对不同的场景选择合适的网络库,同时也支持以插件的方式为 Hertz 扩展网络库实现。
网络库
Hertz 默认集成了 Netpoll 和 Golang 原生网络库 两个网络库,用户可以根据自己的场景选择合适的网络库以达到最佳性能。 对于 Server 来说,默认使用 netpoll ,可以通过配置项进行更改:
server.New(server.WithTransport(standard.NewTransporter))
server.New(server.WithTransport(netpoll.NewTransporter))
对于 Client 来说,可以通过配置项进行更改:
client.NewClient(client.WithDialer(standard.NewDialer()))
client.NewClient(client.WithDialer(netpoll.NewDialer()))
网络库的选择:
- 如果有启动 TLS Server 的需求,请使用
go net
网络库。netpoll
正在实现对 TLS 的支持。 - 由于网络库触发模式的不同:
go net
为 ET 模型,netpoll
为 LT 模型,使得两个网络库的适用场景有一些不同。 在 ET 模型下,由框架处理 Read / Write 事件;在 LT 模型下,由网络库处理 Read / Write 事件。 使得在小包场景下,由于更优的调度策略使得 LT 性能更好;在大包场景下,由于读 / 写不受框架层控制,使得大量数据被读入内存而不能及时处理,可能会造成内存压力。
- 在较大 request size 下( request size > 1M ),推荐使用 go net 网络库加流式。
- 在其他场景下,推荐使用 netpoll 网络库,会获得极致的性能。
路由
Hertz 提供了 GET
、POST
、PUT
、DELETE
、ANY
等方法用于注册路由。
方法 | 介绍 |
---|---|
Hertz.GET | 用于注册 HTTP Method 为 GET 的方法 |
Hertz.POST | 用于注册 HTTP Method 为 POST 的方法 |
Hertz.DELETE | 用于注册 HTTP Method 为 DELETE 的方法 |
Hertz.PUT | 用于注册 HTTP Method 为 PUT 的方法 |
Hertz.PATCH | 用于注册 HTTP Method 为 PATCH 的方法 |
Hertz.HEAD | 用于注册 HTTP Method 为 HEAD 的方法 |
Hertz.OPTIONS | 用于注册 HTTP Method 为 OPTIONS 的方法 |
Hertz.Handle | 这个方法支持用户手动传入 HTTP Method 用来注册方法,当用于注册普通的 HTTP Method 方法时和上述的方法作用是一致的,并且这个方法同时也支持用于注册自定义 HTTP Method 方法 |
Hertz.Any | 用于注册所有 HTTP Method 方法 |
示例代码:
package main
import (
"context"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/app/server"
"github.com/cloudwego/hertz/pkg/protocol/consts"
)
func main(){
h := server.Default(server.WithHostPorts("127.0.0.1:8080"))
h.StaticFS("/", &app.FS{Root: "./", GenerateIndexPages: true})
h.GET("/get", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "get")
})
h.POST("/post", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "post")
})
h.PUT("/put", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "put")
})
h.DELETE("/delete", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "delete")
})
h.PATCH("/patch", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "patch")
})
h.HEAD("/head", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "head")
})
h.OPTIONS("/options", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "options")
})
h.Any("/ping_any", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "any")
})
h.Handle("LOAD","/load", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "load")
})
h.Spin()
}