Hertz中的重试和日志
重试
Hertz 为用户提供的自定义重试逻辑。
Hertz 为用户提供了自定义的重试逻辑,下面来看一下 Client 的 Retry 使用方法。注意:Hertz 版本 >= v0.4.0
Retry 次数及延迟策略配置
首先创建 Client,使用配置项 WithRetryConfig() 来配置 Retry 相关逻辑(这一部分主要配置 Retry 的次数和延时部分)
package main
import (
"github.com/cloudwego/hertz/pkg/app/client"
"github.com/cloudwego/hertz/pkg/app/client/retry"
)
func main() {
cli, err := client.NewClient(
client.WithRetryConfig(
retry.WithXxx(), // 设置 Retry 配置的方式
),
)
}
| 配置名称 | 类型 | 介绍 |
|---|---|---|
| WithMaxAttemptTimes | uint | 用于设置最大尝试次数,默认 1 次(即只请求 1 次不重试) |
| WithInitDelay | time.Duration | 用于设置初始延迟时间,默认 1ms |
| WithMaxDelay | time.Duration | 用于设置最大延迟时间,默认 100ms |
| WithMaxJitter | time.Duration | 用于设置最大扰动时间,需要配合 RandomDelayPolicy 使用,会生成不超过最大扰动时间的随机时间,默认 20ms |
| WithDelayPolicy | type DelayPolicyFunc func(attempts uint, err error, retryConfig *Config) time.Duration | 用于设置延迟策略,可以使用以下四种的任意结合,FixedDelayPolicy, BackOffDelayPolicy, RandomDelayPolicy, DefaultDelayPolicy 默认使用 DefaultDelayPolicy(即重试延迟为 0) |
延迟策略
retry.WithDelayPolicy() 使用方法
cli, err := client.NewClient(
client.WithRetryConfig(
...
retry.WithDelayPolicy(retry.CombineDelay(retry.FixedDelayPolicy, retry.BackOffDelayPolicy, retry.RandomDelayPolicy)),
...
),
)
| 函数名称 | 说明 |
|---|---|
| CombineDelay | 用于将下面四种策略进行任意组合,将所选策略计算出的值进行加和。 当你只需要下面四种策略中的一种时,你可以选择使用 CombineDelay 或选择直接将任意一种策略传入 WithDelayPolicy 作为参数 |
| FixedDelayPolicy | 用于设置固定延迟时间,使用 WithInitDelay 设置的值,来生成等值的延迟时间 |
| BackOffDelayPolicy | 用于设置指数级延迟时间,使用 WithInitDelay 设置的值,根据当前是第几次重试,指数级生成延迟时间 |
| RandomDelayPolicy | 用于设置随机延迟时间,使用 WithMaxJitter 设置的值,生成不超过该值的随机延迟时间 |
| DefaultDelayPolicy | 用于设置默认延迟时间,返回 0,一般单独使用,和其他策略结合没有效果 |
完整示例
package main
import (
"github.com/cloudwego/hertz/pkg/app/client"
"github.com/cloudwego/hertz/pkg/app/client/retry"
)
func main() {
cli, err := client.NewClient(
client.WithRetryConfig(
retry.WithMaxAttemptTimes(3), // 最大的尝试次数,包括初始调用
retry.WithInitDelay(1*time.Millisecond), // 初始延迟
retry.WithMaxDelay(6*time.Millisecond), // 最大延迟,不管重试多少次,策略如何,都不会超过这个延迟
retry.WithMaxJitter(2*time.Millisecond), // 延时的最大扰动,结合 RandomDelayPolicy 才会有效果
/*
配置延迟策略,你可以选择下面四种中的任意组合,最后的结果为每种延迟策略的加和
FixedDelayPolicy 使用 retry.WithInitDelay 所设置的值,
BackOffDelayPolicy 在 retry.WithInitDelay 所设置的值的基础上随着重试次数的增加,指数倍数增长,
RandomDelayPolicy 生成 [0,2*time.Millisecond)的随机数值,2*time.Millisecond 为 retry.WithMaxJitter 所设置的值,
DefaultDelayPolicy 生成 0 值,如果单独使用则立刻重试,
retry.CombineDelay() 将所设置的延迟策略所生成的值加和,最后结果即为当前次重试的延迟时间,
第一次调用失败 -> 重试延迟:1 + 1<<1 + rand[0,2)ms -> 第二次调用失败 -> 重试延迟:min(1 + 1<<2 + rand[0,2) , 6)ms -> 第三次调用成功/失败
*/
retry.WithDelayPolicy(retry.CombineDelay(retry.FixedDelayPolicy, retry.BackOffDelayPolicy, retry.RandomDelayPolicy)),
),
)
}
Retry 条件配置
如果你想要自定义配置重试发生的条件,你可以使用 client.SetRetryIfFunc() 配置,该函数的参数是一个函数,签名为:
func(req *protocol.Request, resp *protocol.Response, err error) bool
相关参数包括 Hertz 请求中的 req、resp 和 err 字段,你可以通过这些参数,判断这个请求该不该重试。
在如下例子中,当请求返回的状态码不是 200 或者调用过程中 err != nil 时我们返回 true,即进行重试。
cli.SetRetryIfFunc(func(req *protocol.Request, resp *protocol.Response, err error) bool {
return resp.StatusCode() != 200 || err != nil
})
需要注意的是,如果你没有设置 client.SetRetryIfFunc()。
我们将会按照 Hertz 默认的重试发生条件进行判断,即判断请求是否满足下面的 DefaultRetryIf() 函数并且判断该调用是否是幂等调用(幂等调用:即 pkg/protocol/http1/client.go::Do() 和 pkg/protocol/http1/client.go::doNonNilReqResp() 中 canIdempotentRetry 为 true 的 情况)
// DefaultRetryIf Default retry condition, mainly used for idempotent requests.
// If this cannot be satisfied, you can implement your own retry condition.
func DefaultRetryIf(req *protocol.Request, resp *protocol.Response, err error) bool {
// cannot retry if the request body is not rewindable
if req.IsBodyStream() {
return false
}
if isIdempotent(req, resp, err) {
return true
}
// Retry non-idempotent requests if the server closes
// the connection before sending the response.
//
// This case is possible if the server closes the idle
// keep-alive connection on timeout.
//
// Apache and nginx usually do this.
if err == io.EOF {
return true
}
return false
}
func isIdempotent(req *protocol.Request, resp *protocol.Response, err error) bool {
return req.Header.IsGet() ||
req.Header.IsHead() ||
req.Header.IsPut() ||
req.Header.IsDelete() ||
req.Header.IsOptions() ||
req.Header.IsTrace()
}
日志
Hertz 提供的日志能力。
Hertz 提供打印日志的方式,默认打在标准输出。实现在 pkg/common/hlog 中,Hertz 同时也提供了若干全局函数
例如 hlog.Info、hlog.Errorf、hlog.CtxTracef 等,用于调用默认 logger 的相应方法。
如何打印日志
hertz 中可以直接调用 pkg/common/hlog 包下的方法打日志,该方法会调用 defaultLogger 上对应的方法。
如实现一个打印 AccessLog 的中间件。
func AccessLog() app.HandlerFunc {
return func(ctx context.Context, c *app.RequestContext) {
start := time.Now()
c.Next(ctx)
end := time.Now()
latency := end.Sub(start).Microseconds
hlog.CtxTracef(ctx, "status=%d cost=%d method=%s full_path=%s client_ip=%s host=%s",
c.Response.StatusCode(), latency,
c.Request.Header.Method(), c.Request.URI().PathOriginal(), c.ClientIP(), c.Request.Host())
}
}
重定向默认 logger 的输出
可以使用 hlog.SetOutput 来重定向 hlog 提供的默认 logger 的输出。
例如,要把默认 logger 的输出重定向到启动路径下的 ./output.log,可以这样实现:
package main
import (
"os"
"github.com/cloudwego/hertz/pkg/common/hlog"
)
func main() {
f, err := os.OpenFile("./output.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
panic(err)
}
defer f.Close()
hlog.SetOutput(f)
... // continue to set up your server
}
logger 的默认输出位置为 os.stdout,在重定向后将不会在终端输出。
如果想同时输出到终端和路径,可参考以下代码:
package main
import (
"io"
"os"
"github.com/cloudwego/hertz/pkg/common/hlog"
)
func main() {
f, err := os.OpenFile("./output.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
panic(err)
}
defer f.Close()
fileWriter := io.MultiWriter(f,os.Stdout)
hlog.SetOutput(fileWriter)
... // continue to set up your server
}
设置 logLevel
可以使用 hlog.SetLevel 来设置日志等级,高于该日志等级的日志才能够被打印出来。
hlog.SetLevel(hlog.LevelInfo)
目前支持的日志等级有
LevelTrace
LevelDebug
LevelInfo
LevelNotice
LevelWarn
LevelError
LevelFatal
关闭 Engine 错误日志
在生产环境中可能会遇到 error when reading request headers 类似的错误,这类错误往往由于 client 侧不规范的行为导致。
对于 server 来说除了通过 client IP 定位到具体 client 并告知其整改(如果可以的话)以外,能够做的并不多。
因此 Hertz 提供了一个配置,在初始化时添加如下配置即可关闭这些日志
hlog.SetSilentMode(true)
本文来自博客园,作者:厚礼蝎,转载请注明原文链接:https://www.cnblogs.com/guangdelw/p/18745830

浙公网安备 33010602011771号