github.com/cloudwego/kitex@v0.9.0/pkg/retry/policy.go (about) 1 /* 2 * Copyright 2021 CloudWeGo Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package retry 18 19 import ( 20 "fmt" 21 22 "github.com/cloudwego/kitex/pkg/rpcinfo" 23 ) 24 25 // Type is retry type include FailureType, BackupType 26 type Type int 27 28 // retry types 29 const ( 30 FailureType Type = iota 31 BackupType 32 ) 33 34 // String prints human readable information. 35 func (t Type) String() string { 36 switch t { 37 case FailureType: 38 return "Failure" 39 case BackupType: 40 return "Backup" 41 } 42 return "" 43 } 44 45 // BuildFailurePolicy is used to build Policy with *FailurePolicy 46 func BuildFailurePolicy(p *FailurePolicy) Policy { 47 if p == nil { 48 return Policy{} 49 } 50 return Policy{Enable: true, Type: FailureType, FailurePolicy: p} 51 } 52 53 // BuildBackupRequest is used to build Policy with *BackupPolicy 54 func BuildBackupRequest(p *BackupPolicy) Policy { 55 if p == nil { 56 return Policy{} 57 } 58 return Policy{Enable: true, Type: BackupType, BackupPolicy: p} 59 } 60 61 // Policy contains all retry policies 62 // DON'T FORGET to update Equals() and DeepCopy() if you add new fields 63 type Policy struct { 64 Enable bool `json:"enable"` 65 // 0 is failure retry, 1 is backup 66 Type Type `json:"type"` 67 // notice: only one retry policy can be enabled, which one depend on Policy.Type 68 FailurePolicy *FailurePolicy `json:"failure_policy,omitempty"` 69 BackupPolicy *BackupPolicy `json:"backup_policy,omitempty"` 70 } 71 72 func (p *Policy) DeepCopy() *Policy { 73 if p == nil { 74 return nil 75 } 76 return &Policy{ 77 Enable: p.Enable, 78 Type: p.Type, 79 FailurePolicy: p.FailurePolicy.DeepCopy(), 80 BackupPolicy: p.BackupPolicy.DeepCopy(), 81 } 82 } 83 84 // FailurePolicy for failure retry 85 // DON'T FORGET to update Equals() and DeepCopy() if you add new fields 86 type FailurePolicy struct { 87 StopPolicy StopPolicy `json:"stop_policy"` 88 BackOffPolicy *BackOffPolicy `json:"backoff_policy,omitempty"` 89 RetrySameNode bool `json:"retry_same_node"` 90 ShouldResultRetry *ShouldResultRetry `json:"-"` 91 92 // Extra is not used directly by kitex. It's used for better integrating your own config source. 93 // After loading FailurePolicy from your config source, `Extra` can be decoded into a user-defined schema, 94 // with which, more complex strategies can be implemented, such as modifying the `ShouldResultRetry`. 95 Extra string `json:"extra"` 96 } 97 98 // IsRespRetryNonNil is used to check if RespRetry is nil 99 func (p FailurePolicy) IsRespRetryNonNil() bool { 100 return p.ShouldResultRetry != nil && p.ShouldResultRetry.RespRetry != nil 101 } 102 103 // IsErrorRetryNonNil is used to check if ErrorRetry is nil 104 func (p FailurePolicy) IsErrorRetryNonNil() bool { 105 return p.ShouldResultRetry != nil && p.ShouldResultRetry.ErrorRetry != nil 106 } 107 108 // IsRetryForTimeout is used to check if timeout error need to retry 109 func (p FailurePolicy) IsRetryForTimeout() bool { 110 return p.ShouldResultRetry == nil || !p.ShouldResultRetry.NotRetryForTimeout 111 } 112 113 // BackupPolicy for backup request 114 // DON'T FORGET to update Equals() and DeepCopy() if you add new fields 115 type BackupPolicy struct { 116 RetryDelayMS uint32 `json:"retry_delay_ms"` 117 StopPolicy StopPolicy `json:"stop_policy"` 118 RetrySameNode bool `json:"retry_same_node"` 119 } 120 121 // StopPolicy is a group policies to decide when stop retry 122 type StopPolicy struct { 123 MaxRetryTimes int `json:"max_retry_times"` 124 MaxDurationMS uint32 `json:"max_duration_ms"` 125 DisableChainStop bool `json:"disable_chain_stop"` 126 DDLStop bool `json:"ddl_stop"` 127 CBPolicy CBPolicy `json:"cb_policy"` 128 } 129 130 const ( 131 defaultCBErrRate = 0.1 132 cbMinSample = 10 133 ) 134 135 // CBPolicy is the circuit breaker policy 136 type CBPolicy struct { 137 ErrorRate float64 `json:"error_rate"` 138 } 139 140 // BackOffPolicy is the BackOff policy. 141 // DON'T FORGET to update Equals() and DeepCopy() if you add new fields 142 type BackOffPolicy struct { 143 BackOffType BackOffType `json:"backoff_type"` 144 CfgItems map[BackOffCfgKey]float64 `json:"cfg_items,omitempty"` 145 } 146 147 // BackOffType means the BackOff type. 148 type BackOffType string 149 150 // all back off types 151 const ( 152 NoneBackOffType BackOffType = "none" 153 FixedBackOffType BackOffType = "fixed" 154 RandomBackOffType BackOffType = "random" 155 ) 156 157 // BackOffCfgKey represents the keys for BackOff. 158 type BackOffCfgKey string 159 160 // the keys of all back off configs 161 const ( 162 FixMSBackOffCfgKey BackOffCfgKey = "fix_ms" 163 MinMSBackOffCfgKey BackOffCfgKey = "min_ms" 164 MaxMSBackOffCfgKey BackOffCfgKey = "max_ms" 165 InitialMSBackOffCfgKey BackOffCfgKey = "initial_ms" 166 MultiplierBackOffCfgKey BackOffCfgKey = "multiplier" 167 ) 168 169 // ShouldResultRetry is used for specifying which error or resp need to be retried 170 type ShouldResultRetry struct { 171 ErrorRetry func(err error, ri rpcinfo.RPCInfo) bool 172 RespRetry func(resp interface{}, ri rpcinfo.RPCInfo) bool 173 // disable the default timeout retry in specific scenarios (e.g. the requests are not non-idempotent) 174 NotRetryForTimeout bool 175 } 176 177 // Equals to check if policy is equal 178 func (p Policy) Equals(np Policy) bool { 179 if p.Enable != np.Enable { 180 return false 181 } 182 if p.Type != np.Type { 183 return false 184 } 185 if !p.FailurePolicy.Equals(np.FailurePolicy) { 186 return false 187 } 188 if !p.BackupPolicy.Equals(np.BackupPolicy) { 189 return false 190 } 191 return true 192 } 193 194 // Equals to check if FailurePolicy is equal 195 func (p *FailurePolicy) Equals(np *FailurePolicy) bool { 196 if p == nil { 197 return np == nil 198 } 199 if np == nil { 200 return false 201 } 202 if p.StopPolicy != np.StopPolicy { 203 return false 204 } 205 if !p.BackOffPolicy.Equals(np.BackOffPolicy) { 206 return false 207 } 208 if p.RetrySameNode != np.RetrySameNode { 209 return false 210 } 211 if p.Extra != np.Extra { 212 return false 213 } 214 // don't need to check `ShouldResultRetry`, ShouldResultRetry is only setup by option 215 // in remote config case will always return false if check it 216 return true 217 } 218 219 func (p *FailurePolicy) DeepCopy() *FailurePolicy { 220 if p == nil { 221 return nil 222 } 223 return &FailurePolicy{ 224 StopPolicy: p.StopPolicy, 225 BackOffPolicy: p.BackOffPolicy.DeepCopy(), 226 RetrySameNode: p.RetrySameNode, 227 ShouldResultRetry: p.ShouldResultRetry, // don't need DeepCopy 228 Extra: p.Extra, 229 } 230 } 231 232 // Equals to check if BackupPolicy is equal 233 func (p *BackupPolicy) Equals(np *BackupPolicy) bool { 234 if p == nil { 235 return np == nil 236 } 237 if np == nil { 238 return false 239 } 240 if p.RetryDelayMS != np.RetryDelayMS { 241 return false 242 } 243 if p.StopPolicy != np.StopPolicy { 244 return false 245 } 246 if p.RetrySameNode != np.RetrySameNode { 247 return false 248 } 249 250 return true 251 } 252 253 func (p *BackupPolicy) DeepCopy() *BackupPolicy { 254 if p == nil { 255 return nil 256 } 257 return &BackupPolicy{ 258 RetryDelayMS: p.RetryDelayMS, 259 StopPolicy: p.StopPolicy, // not a pointer, will copy the value here 260 RetrySameNode: p.RetrySameNode, 261 } 262 } 263 264 // Equals to check if BackOffPolicy is equal. 265 func (p *BackOffPolicy) Equals(np *BackOffPolicy) bool { 266 if p == nil { 267 return np == nil 268 } 269 if np == nil { 270 return false 271 } 272 if p.BackOffType != np.BackOffType { 273 return false 274 } 275 if len(p.CfgItems) != len(np.CfgItems) { 276 return false 277 } 278 for k := range p.CfgItems { 279 if p.CfgItems[k] != np.CfgItems[k] { 280 return false 281 } 282 } 283 284 return true 285 } 286 287 func (p *BackOffPolicy) DeepCopy() *BackOffPolicy { 288 if p == nil { 289 return nil 290 } 291 return &BackOffPolicy{ 292 BackOffType: p.BackOffType, 293 CfgItems: p.copyCfgItems(), 294 } 295 } 296 297 func (p *BackOffPolicy) copyCfgItems() map[BackOffCfgKey]float64 { 298 if p.CfgItems == nil { 299 return nil 300 } 301 cfgItems := make(map[BackOffCfgKey]float64, len(p.CfgItems)) 302 for k, v := range p.CfgItems { 303 cfgItems[k] = v 304 } 305 return cfgItems 306 } 307 308 func checkCBErrorRate(p *CBPolicy) error { 309 if p.ErrorRate <= 0 || p.ErrorRate > 0.3 { 310 return fmt.Errorf("invalid retry circuit breaker rate, errRate=%0.2f", p.ErrorRate) 311 } 312 return nil 313 }