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  }