目前有三類重試:超時(shí)重試、Backup Request,建連失敗重試(默認(rèn))。其中建連失敗是網(wǎng)絡(luò)層面問(wèn)題,由于請(qǐng)求未發(fā)出,框架會(huì)默認(rèn)重試。 本文檔介紹前兩類重試的使用:
因?yàn)楹芏嗟臉I(yè)務(wù)請(qǐng)求不具有冪等性,這兩類重試不會(huì)作為默認(rèn)策略。
超時(shí)重試和 Backup Request 策略只能配置其中之一。
配置項(xiàng) | 默認(rèn)值 | 說(shuō)明 | 限制 |
---|---|---|---|
MaxRetryTimes
|
2 | 最大重試次數(shù),不包含首次請(qǐng)求。如果配置為 0 表示停止重試。 | 合法值:[0-5] |
MaxDurationMS
|
0 | 累計(jì)最大耗時(shí),包括首次失敗請(qǐng)求和重試請(qǐng)求耗時(shí),如果耗時(shí)達(dá)到了限制的時(shí)間則停止后續(xù)的重試。0 表示無(wú)限制。注意:如果配置,該配置項(xiàng)必須大于請(qǐng)求超時(shí)時(shí)間。 | |
EERThreshold
|
10% | 重試熔斷錯(cuò)誤率閾值, 方法級(jí)別請(qǐng)求錯(cuò)誤率超過(guò)閾值則停止重試。 | 合法值:(0-30%] |
ChainStop
|
- | 鏈路中止, 默認(rèn)啟用。如果上游請(qǐng)求是重試請(qǐng)求,超時(shí)后不會(huì)重試。 | >= v0.0.5 后作為默認(rèn)策略 |
DDLStop
|
false | 鏈路超時(shí)中止,該策略是從鏈路的超時(shí)時(shí)間判斷是否需要重試。注意,Kitex 未內(nèi)置該實(shí)現(xiàn),需通過(guò) retry.RegisterDDLStop(ddlStopFunc) 注冊(cè) DDL func,結(jié)合鏈路超時(shí)判斷,實(shí)現(xiàn)上建議基于上游的發(fā)起調(diào)用的時(shí)間戳和超時(shí)時(shí)間判斷。?? | |
BackOff
|
None | 重試等待策略,默認(rèn)立即重試(NoneBackOff )??蛇x:固定時(shí)長(zhǎng)退避 (FixedBackOff )、隨機(jī)時(shí)長(zhǎng)退避 (RandomBackOff )。 |
|
RetrySameNode
|
false | 框架默認(rèn)選擇其他節(jié)點(diǎn)重試,若需要同節(jié)點(diǎn)重試,可配置為 true。 |
配置項(xiàng) | 默認(rèn)值 | 說(shuō)明 | 限制 |
---|---|---|---|
RetryDelayMS
|
- | Backup Request 的等待時(shí)間,若該時(shí)間內(nèi)若請(qǐng)求未返回,會(huì)發(fā)送新的請(qǐng)求。必須手動(dòng)配置,建議參考 TP99。 | |
MaxRetryTimes
|
1 | 最大重試次數(shù),不包含首次請(qǐng)求。 如果配置為 0 表示停止重試。 | 合法值:[0-2] |
EERThreshold
|
10% | 重試熔斷錯(cuò)誤率閾值,方法級(jí)別請(qǐng)求錯(cuò)誤率超過(guò)閾值則停止重試。 | 合法值:(0-30%] |
ChainStop
|
- | 鏈路中止, 默認(rèn)啟用。如果上游請(qǐng)求是重試請(qǐng)求,不會(huì)發(fā)送 Backup Request。 | >= v0.0.5 后作為默認(rèn)策略 |
RetrySameNode
|
false | 框架默認(rèn)選擇其他節(jié)點(diǎn)重試,若需要同節(jié)點(diǎn)重試,可配置為 true |
注意:若通過(guò)代碼配置開(kāi)啟重試,動(dòng)態(tài)配置 (見(jiàn) 3.3) 則無(wú)法生效。
// import "github.com/cloudwego/kitex/pkg/retry"
fp := retry.NewFailurePolicy()
fp.WithMaxRetryTimes(3) // 配置最多重試3次
xxxCli := xxxservice.NewClient("destServiceName", client.WithFailureRetry(fp))
fp := retry.NewFailurePolicy()
// 重試次數(shù), 默認(rèn)2,不包含首次請(qǐng)求
fp.WithMaxRetryTimes(xxx)
// 總耗時(shí),包括首次失敗請(qǐng)求和重試請(qǐng)求耗時(shí)達(dá)到了限制的duration,則停止后續(xù)的重試。
fp.WithMaxDurationMS(xxx)
// 關(guān)閉鏈路中止
fp.DisableChainRetryStop()
// 開(kāi)啟DDL中止
fp.WithDDLStop()
// 退避策略,默認(rèn)無(wú)退避策略
fp.WithFixedBackOff(fixMS int) // 固定時(shí)長(zhǎng)退避
fp.WithRandomBackOff(minMS int, maxMS int) // 隨機(jī)時(shí)長(zhǎng)退避
// 開(kāi)啟重試熔斷
fp.WithRetryBreaker(errRate float64)
// 同一節(jié)點(diǎn)重試
fp.WithRetrySameNode()
建議配置為 TP99,則 1% 請(qǐng)求會(huì)觸發(fā) Backup Request。
// 首次請(qǐng)求 xxx ms未返回,發(fā)起 backup 請(qǐng)求,并開(kāi)啟鏈路中止
bp := retry.NewBackupPolicy(xxx)
xxxCli := xxxservice.NewClient("destServiceName", client.WithBackupRequest(bp))
bp := retry.NewBackupPolicy(xxx)
// 重試次數(shù), 默認(rèn)1,不包含首次請(qǐng)求
bp.WithMaxRetryTimes(xxx)
// 關(guān)閉鏈路中止
bp.DisableChainRetryStop()
// 開(kāi)啟重試熔斷
bp.WithRetryBreaker(errRate float64)
// 同一節(jié)點(diǎn)重試
bp.WithRetrySameNode()
當(dāng)開(kāi)啟了服務(wù)的熔斷配置可以復(fù)用熔斷的統(tǒng)計(jì)減少額外的 CPU 消耗,注意重試的熔斷閾值須低于服務(wù)的熔斷閾值,使用如下:
// 1. 初始化 kitex 內(nèi)置的 cbsuite
cbs := circuitbreak.NewCBSuite(circuitbreak.RPCInfo2Key)
// 2. 初始化 retryContainer,傳入ServiceControl和ServicePanel
retryC := retry.NewRetryContainerWithCB(cs.cbs.ServiceControl(), cs.cbs.ServicePanel())
var opts []client.Option
// 3. 配置 retryContainer
opts = append(opts, client.WithRetryContainer(retryC))
// 4. 配置 Service circuit breaker
opts = append(opts, client.WithMiddleware(cbs.ServiceCBMW()))
// 5. 初始化 Client, 傳入配置 option
cli, err := xxxservice.NewClient(targetService, opts...)
若需要結(jié)合遠(yuǎn)程配置,動(dòng)態(tài)開(kāi)啟重試或運(yùn)行時(shí)調(diào)整策略,可以通過(guò) retryContainer 的 NotifyPolicyChange 方法生效,目前 Kitex 開(kāi)源版本暫未提供遠(yuǎn)程配置模塊,使用者可集成自己的配置中心。注意:若已通過(guò)代碼配置開(kāi)啟,動(dòng)態(tài)配置則無(wú)法生效。 使用示例:
retryC := retry.NewRetryContainer()
// demo
// 1. define your change func
// 2. exec yourChangeFunc in your config module
yourChangeFunc := func(key string, oldData, newData interface{}) {
newConf := newData.(*retry.Policy)
method := parseMethod(key)
retryC.NotifyPolicyChange(method, policy)
}
// configure retryContainer
cli, err := xxxservice.NewClient(targetService, client.WithRetryContainer(retryC))
Kitex 對(duì)重試的請(qǐng)求在 rpcinfo 中記錄了重試次數(shù)和之前請(qǐng)求的耗時(shí),可以在Client側(cè)的 metric 或日志中根據(jù) retry tag 區(qū)分上報(bào)或輸出。獲取方式:
var retryCount string
var lastCosts string
toInfo := rpcinfo.GetRPCInfo(ctx).To()
if retryTag, ok := toInfo.Tag(rpcinfo.RetryTag); ok {
retryCount = retryTag
if lastCostTag, ok := toInfo.Tag(rpcinfo.RetryLastCostTag); ok {
lastCosts = lastCostTag
}
}
如果使用 TTHeader 作為傳輸協(xié)議,下游 handler 可以通過(guò)如下方式判斷當(dāng)前是否是重試請(qǐng)求,自行決定是否繼續(xù)處理。
retryReqCount, exist := metainfo.GetPersistentValue(ctx,retry.TransitKey)
比如 retryReqCount = 2,表示第二次重試請(qǐng)求(不包括首次請(qǐng)求),則采取業(yè)務(wù)降級(jí)策略返回部分或 mock 數(shù)據(jù)返回(非重試請(qǐng)求沒(méi)有該信息)。
Q: 框架默認(rèn)開(kāi)啟鏈路中止,業(yè)務(wù)是否還有必要識(shí)別重試請(qǐng)求?
鏈路中止是指鏈路上的重試請(qǐng)求不會(huì)重試,比如 A->B->C,A 向 B 發(fā)送的是重試請(qǐng)求,如果 B->C 超時(shí)了或者配置了 Backup,則 B 不會(huì)再發(fā)送重試請(qǐng)求到 C。如果業(yè)務(wù)自行識(shí)別重試請(qǐng)求,可以直接決定是否繼續(xù)請(qǐng)求到 C。簡(jiǎn)言之鏈路中止避免了 B 向 C 發(fā)送重試請(qǐng)求導(dǎo)致重試放大,業(yè)務(wù)自己控制可以完全避免 B 到 C 的請(qǐng)求。
更多建議: