一、Hertz 概述
Hertz是字节跳动开源的Go微服务HTTP框架,具有高易用性、高性能、高拓展性的一个特点。在设计之初参考了其他开源框架 fasthttp、gin、echo(这些框架加上RPC也可以成为微服务框架) 的优势。
下图是Hertz的一个架构设计。
在Application应用层,可以看到Server和Client,有对应的Handler处理器、绑定、渲染、上下文、服务发现、中间件等。然后紧接着的是路由层,有路由发现这些。然后就是Protocol协议,因为是HTTP框架,所以肯定支持HTTP1/2/3,包括WS。在传输网络层,可以用Netpoll和Go Net。Hz是Hertz的代码生成工具。
二、Hertz 简单demo
在Goland中创建一个新的项目,然后写入一个main.go
的文件来跑个简单案例看看Hertz是什么样的。
我们 写入下列代码,然后在浏览器中打开终端查看对应的结果。
package main
import (
"context"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/app/server"
"github.com/cloudwego/hertz/pkg/common/utils"
"github.com/cloudwego/hertz/pkg/protocol/consts"
)
func main() {
h := server.Default() //创建一个默认的 HTTP 服务器实例。
//"/ping":指定路由路径,表示处理 /ping 路径的 GET 请求。
h.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
//将响应数据以 JSON 格式返回给客户端。
//使用 utils.H 创建一个 JSON 对象,包含键值对 "message": "pong"。
ctx.JSON(consts.StatusOK, utils.H{"message": "pong"})
})
//启动 HTTP 服务器。
h.Spin()
}
可以看到对应的请求信息如下:
2025/02/05 19:19:54.036401 engine.go:669: [Debug] HERTZ: Method=GET absolutePath=/ping
--> handlerName=main.main.func1 (num=2 handlers)
2025/02/05 19:19:54.042003 engine.go:397: [Info] HERTZ: Using network library=standard
2025/02/05 19:19:54.042599 transport.go:65: [Info] HERTZ: HTTP server listening on address=[::]:8888
第一行信息表示是Hertz框架生成的日志,absolutePath=/ping:表示请求的路径是 /ping。handlerName=main.main.func1:表示处理这个请求的处理器函数是 main.main.func1。这通常是一个匿名函数,定义在 main 包的 main 函数中。
(num=2 handlers):表示这个请求路径 /ping 经过了 2 个处理器(handler)。这可能包括一个默认的中间件和一个具体的请求处理函数。
第二行Using network library=standard:表示 Hertz 框架正在使用标准的网络库(net/http)来处理 HTTP 请求。
第三行HTTP server listening on address=[::]:8888:表示 HTTP 服务器已经开始监听 [::]:8888 地址。[::] 是 IPv6 的通配符地址,表示服务器监听所有可用的网络接口。
我们在控制台中输入命令 curl http://localhost:8888/ping
,可以得到下面的信息:
三、Hertz 客户端
创建一个简单的Hertz客户端,代码如下:
package main
import (
"context"
"fmt"
"github.com/cloudwego/hertz/pkg/app/client"
"github.com/cloudwego/hertz/pkg/protocol"
)
// 客户端访问下面的server服务端
func performRequest() {
c, _ := client.NewClient()
//客户端有请求和响应,所以创建对应的请求和响应
req, resp := protocol.AcquireRequest(), protocol.AcquireResponse()
req.SetRequestURI("http://localhost:8080/hello")
req.SetMethod("GET")
_ = c.Do(context.Background(), req, resp) //执行请求
fmt.Printf("get response: %s\n", resp.Body()) //获得响应的内容
//status == 200 resp.Body() == []byte("hello hertz")
}
func main() {
performRequest()
}
main.go中我们写入简单的服务端的响应代码如下。
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.New(server.WithHostPorts(":8080"))
h.GET("/hello", func(c context.Context, ctx *app.RequestContext) {
ctx.JSON(consts.StatusOK, "hello hertz")
})
h.Spin()
}
先启动main.go,然后再执行客户端,就可以得到返回get response: "hello hertz"
。
客户端和request都可以启动对应的配置,比如下面的示例。
发送请求的几种方式有:
//响应会直接写入Response中去,是一个传出参数
func (c *client) Do(ctx content.Context,req *protocol.Request,resp *protocol.Response) error
func (c *client) DoRedirects //请求重定向
示例:err = c.DoRedirects(context.Background(),req,resp,1)
也就是需要设置最大的重定向次数
func (c *client) Get
示例:status,body,err := c.Get(context.Background(),nil,"https://locolhost:8080/ping")
示例:可以传递参数了,所以可以进行的传递参数
func (c *client) Post
c ,err := client.NewClient()
var postArgs protocol.Args
postArgs.Set("name","cloudwego") //设置post参数
status,body,err := c.Post(context.Background(),nil,"http://localhost:8080/hello",&postArgs)
四、Hertz 路由
Hertz提供了GET、POST、PUT、DELETE、ANY等方式用于路由注册:
除此之外还提供了路由组的概念,也就是支持路由分组,中间件也可以注册到路由组上。
路由分组的作用是系统有很多功能,比如用户模块、订单模块,所以可以对这些模块的路由进行分组。
路由组中还可以使用中间件,示例代码(使用basic_auth.BasicAuth)如下:
路由的优先级包括:静态路由 > 命名参数路由 > 通配参数路由,上面的例子都是静态路由。
我们来看看命名参数路由,使用:name
的命名参数设置对应的路由,并且命名参数只匹配单个路径段。
如果设置/user/:name
路由,匹配情况就是只能匹配对应的单个,比如/user/you
是可以匹配成功的,但是/user/you/name
这种匹配不成功。
通过RequestContext.Param
可以获取到路由中携带的参数。
通配参数路由,支持*path
这样的通配参数设置路由,并且通配参数会匹配所有的内容。
比如设置了/src/*path
路由,匹配情况/src/you/you.go
也是可以匹配成功的。
通过RequestContext.Param
可以获取到路由中携带的参数。依然使用param := c.Param("path)
进行提取。
五、Hertz 网络库
Hertz默认集成了Netpoll和Golang原生网络库Go net,可以根据自己的需要进行选择开发。
对于Server来说,默认使用netpoll,可以通过配置项进行修改。
server.New(server.WithTransport(standard.NewTransporter())
server.New(server.WithTransport(netpoll.NewTransporter())
例如:
h:= server.New(server.WithHostPorts(":8080"),server.WithTransport(standard.NewTransporter())
。
netpoll目前貌似不支持win,win通过条件编译的时候会自动将网络库切换为Go net网络库。
对于Client来说,可以通过配置项进行修改。
client.NewClient(client.WithTransport(standard.NewDialer())
client.NewClient(client.WithTransport(netpoll.NewDialer())
如果有启动TLS Server(https加密)的需求,可以使用Go net网络库,Netpoll正在实现对TLS的支持。
由于网络库的触发模式的不同,go net为ET模型,netpoll是LT模型。ET模型下,由框架处理Read/write事件,LT模型下,由网络库处理Read/Write事件。
在小包的场景下,由于更优的调度策略LT性能更好。但是大包场景下,由于读写不受框架层的限制,大量数据被读入内存而不能及时处理,可能会造成内存压力。
所以在较大的request size(比方说大于1M)下,推荐使用go net网络库+流式。
其他场景下可以用netpoll网络库。
六、Hertz hz代码生成
使用命令安装hz。
go install github.com/cloudwego/hertz/cmd/hz@latest
然后终端输入命令hz new
就会自动创建文件。
main中代码如下所示。
会进行注册,注册的话会在router_gen.go
(路由生成文件)中声明。这里使用了router的GeneratedRegister方法。
在这个文件中又导入了router
文件,我们来看看biz路径下的router文件。
真正的路由定义是在router.go
中进行定义的,这里定义的Ping方法会在biz下面的handler中进行定义。
七、Hertz Engine
Hertz的路由、中间件、服务启动和退出等重要的方法都是包含在server.Hertz
这个核心类型之中的,由route.Engine
和signalWaiter
组成。
下面是Hertz的定义:
通过server.Default()我们可以看到返回的是一个Hertz的类型的引用,而Hertz是一个结构体,核心就是Engine和SignalWaiter。
一般情况下用Default,default里面可以传过来很多配置选项,这个里面有很多配置。Default里边还调用了New方法。
default和new的重要区别就是default里边添加了一个中间件,也就是recovery
,以后有panic之后可以发现错误。
Hertz提供了Spin函数启动服务器。
和route.Engine中提供的run不同,除非有特殊的需求,不然都是使用Spin函数用于运行服务。在使用服务注册发现
的功能的时候,Spin会在服务启动的时候将服务注册进入注册中心(因为是微服务框架,所以有很多注册中心),并且使用signalWaiter
检测服务的异常。只有使用Spin来启动服务的时候才能支持优雅退出。
如果调用了run方法,还要调用错误处理,比如下面的示例代码:
h := server.New()
if err := h.Run(); err!=nil{
panic(err)
}
接下来来看看Engine引擎部分,包含了Engine名称、路由和协议服务器的选项、router前缀树、模版HTML(支持前后端不分离)、网络库选择、链路追踪、协议层、连接池、hook等。