github.com/qiniu/go-sdk/v7@v7.27.0/internal/clientv2/interceptor_retry_simple.go (about)

     1  package clientv2
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"math/rand"
     7  	"net"
     8  	"net/http"
     9  	"sync"
    10  	"time"
    11  
    12  	clientv1 "github.com/qiniu/go-sdk/v7/client"
    13  	internal_io "github.com/qiniu/go-sdk/v7/internal/io"
    14  	"github.com/qiniu/go-sdk/v7/storagev2/backoff"
    15  	"github.com/qiniu/go-sdk/v7/storagev2/chooser"
    16  	"github.com/qiniu/go-sdk/v7/storagev2/resolver"
    17  	"github.com/qiniu/go-sdk/v7/storagev2/retrier"
    18  )
    19  
    20  type (
    21  	bufferResponseContextKey struct{}
    22  
    23  	SimpleRetryConfig struct {
    24  		RetryMax      int                  // 最大重试次数
    25  		RetryInterval func() time.Duration // 重试时间间隔 v1
    26  		Backoff       backoff.Backoff      // 重试时间间隔 v2,优先级高于 RetryInterval
    27  		ShouldRetry   func(req *http.Request, resp *http.Response, err error) bool
    28  		Resolver      resolver.Resolver // 主备域名解析器
    29  		Chooser       chooser.Chooser   // IP 选择器
    30  		Retrier       retrier.Retrier   // 重试器
    31  
    32  		BeforeResolve func(*http.Request)                                         // 域名解析前回调函数
    33  		AfterResolve  func(*http.Request, []net.IP)                               // 域名解析后回调函数
    34  		ResolveError  func(*http.Request, error)                                  // 域名解析错误回调函数
    35  		BeforeBackoff func(*http.Request, *retrier.RetrierOptions, time.Duration) // 退避前回调函数
    36  		AfterBackoff  func(*http.Request, *retrier.RetrierOptions, time.Duration) // 退避后回调函数
    37  		BeforeRequest func(*http.Request, *retrier.RetrierOptions)                // 请求前回调函数
    38  		AfterResponse func(*http.Response, *retrier.RetrierOptions, error)        // 请求后回调函数
    39  	}
    40  
    41  	simpleRetryInterceptor struct {
    42  		config SimpleRetryConfig
    43  	}
    44  )
    45  
    46  func (c *SimpleRetryConfig) init() {
    47  	if c == nil {
    48  		return
    49  	}
    50  
    51  	if c.RetryMax < 0 {
    52  		c.RetryMax = 0
    53  	}
    54  }
    55  
    56  func (c *SimpleRetryConfig) getRetryInterval(ctx context.Context, attempts int) time.Duration {
    57  	if bf := c.Backoff; bf != nil {
    58  		return bf.Time(ctx, &backoff.BackoffOptions{Attempts: attempts})
    59  	}
    60  	if ri := c.RetryInterval; ri != nil {
    61  		return ri()
    62  	}
    63  	return defaultRetryInterval()
    64  }
    65  
    66  var errorRetrier = retrier.NewErrorRetrier()
    67  
    68  func (c *SimpleRetryConfig) getRetryDecision(req *http.Request, resp *http.Response, err error, attempts int) retrier.RetryDecision {
    69  	if c.ShouldRetry != nil {
    70  		if c.ShouldRetry(req, resp, err) {
    71  			return retrier.RetryRequest
    72  		} else {
    73  			return retrier.DontRetry
    74  		}
    75  	} else {
    76  		r := errorRetrier
    77  		if c.Retrier != nil {
    78  			r = c.Retrier
    79  		}
    80  		return r.Retry(resp, err, &retrier.RetrierOptions{Attempts: attempts})
    81  	}
    82  }
    83  
    84  func NewSimpleRetryInterceptor(config SimpleRetryConfig) Interceptor {
    85  	return &simpleRetryInterceptor{config: config}
    86  }
    87  
    88  func (interceptor *simpleRetryInterceptor) Priority() InterceptorPriority {
    89  	return InterceptorPriorityRetrySimple
    90  }
    91  
    92  func (interceptor *simpleRetryInterceptor) Intercept(req *http.Request, handler Handler) (resp *http.Response, err error) {
    93  	var chosenIPs []net.IP
    94  
    95  	if interceptor == nil || req == nil {
    96  		return interceptor.callHandler(req, &retrier.RetrierOptions{Attempts: 0}, handler)
    97  	}
    98  
    99  	interceptor.config.init()
   100  
   101  	hostname := req.URL.Hostname()
   102  	resolvedIPs := interceptor.resolve(req, hostname, false)
   103  	resolvedIPsMap := make(map[string]struct{}, len(resolvedIPs))
   104  	addIPsToMap(resolvedIPsMap, resolvedIPs)
   105  
   106  	// 可能会被重试多次
   107  	for i := 0; ; i++ {
   108  		// Clone 防止后面 Handler 处理对 req 有污染
   109  		reqBefore := cloneReq(req)
   110  		req, chosenIPs = interceptor.ensureChoose(req, hostname, resolvedIPs, resolvedIPsMap)
   111  		resp, err = interceptor.callHandler(req, &retrier.RetrierOptions{Attempts: i}, handler)
   112  
   113  		if err == nil && resp.StatusCode/100 >= 4 {
   114  			err = clientv1.ResponseError(resp)
   115  		}
   116  
   117  		retryDecision := interceptor.config.getRetryDecision(reqBefore, resp, err, i)
   118  		if retryDecision == retrier.DontRetry {
   119  			interceptor.feedbackGood(req, hostname, chosenIPs)
   120  			return resp, err
   121  		}
   122  		interceptor.feedbackBad(req, hostname, chosenIPs)
   123  
   124  		req = reqBefore
   125  
   126  		if retryDecision == retrier.TryNextHost || i >= interceptor.config.RetryMax {
   127  			break
   128  		}
   129  
   130  		if req.Body != nil && req.GetBody != nil {
   131  			if closer, ok := req.Body.(io.Closer); ok {
   132  				closer.Close()
   133  			}
   134  			if req.Body, err = req.GetBody(); err != nil {
   135  				return
   136  			}
   137  		}
   138  
   139  		if resp != nil && resp.Body != nil {
   140  			_ = internal_io.SinkAll(resp.Body)
   141  			resp.Body.Close()
   142  		}
   143  
   144  		interceptor.wait(req, i)
   145  	}
   146  	return resp, err
   147  }
   148  
   149  func (interceptor *simpleRetryInterceptor) ensureChoose(req *http.Request, hostname string, resolvedIPs []net.IP, resolvedIPsMap map[string]struct{}) (*http.Request, []net.IP) {
   150  	var chosenIPs []net.IP
   151  beforeChoose:
   152  	req, chosenIPs = interceptor.choose(req, resolvedIPs, hostname, true)
   153  	for len(chosenIPs) == 0 {
   154  		newResolvedIPs := interceptor.resolve(req, hostname, true)
   155  		if len(newResolvedIPs) > 0 && !isAllIPsInMap(resolvedIPsMap, newResolvedIPs) { // 但凡能拿到新的 IP 就尝试新的 IP
   156  			addIPsToMap(resolvedIPsMap, newResolvedIPs)
   157  			resolvedIPs = mergeIPs(resolvedIPs, newResolvedIPs)
   158  			goto beforeChoose
   159  		} else { // 如果拿不到新的 IP,只能从已有的 IP 集合里选择
   160  			req, chosenIPs = interceptor.choose(req, resolvedIPs, hostname, false)
   161  			break
   162  		}
   163  	}
   164  	return req, chosenIPs
   165  }
   166  
   167  func (interceptor *simpleRetryInterceptor) callHandler(req *http.Request, options *retrier.RetrierOptions, handler Handler) (resp *http.Response, err error) {
   168  	if interceptor.config.BeforeRequest != nil {
   169  		interceptor.config.BeforeRequest(req, options)
   170  	}
   171  	resp, err = handler(req)
   172  	if interceptor.config.AfterResponse != nil {
   173  		interceptor.config.AfterResponse(resp, options, err)
   174  	}
   175  	return
   176  }
   177  
   178  var (
   179  	defaultResolver     resolver.Resolver
   180  	defaultResolverOnce sync.Once
   181  )
   182  
   183  func (interceptor *simpleRetryInterceptor) resolver() resolver.Resolver {
   184  	var r resolver.Resolver
   185  	if r = interceptor.config.Resolver; r == nil {
   186  		defaultResolverOnce.Do(func() {
   187  			defaultResolver, _ = resolver.NewCacheResolver(nil, nil)
   188  		})
   189  		r = defaultResolver
   190  	}
   191  	return r
   192  }
   193  
   194  func (interceptor *simpleRetryInterceptor) resolve(req *http.Request, hostname string, bypassCache bool) []net.IP {
   195  	var (
   196  		ctx context.Context
   197  		ips []net.IP
   198  		err error
   199  	)
   200  	if r := interceptor.resolver(); r != nil {
   201  		if interceptor.config.BeforeResolve != nil {
   202  			interceptor.config.BeforeResolve(req)
   203  		}
   204  		ctx = req.Context()
   205  		if bypassCache {
   206  			ctx = resolver.WithByPassResolverCache(ctx)
   207  		}
   208  		if ips, err = r.Resolve(ctx, hostname); err == nil {
   209  			if interceptor.config.AfterResolve != nil {
   210  				interceptor.config.AfterResolve(req, ips)
   211  			}
   212  		} else if interceptor.config.ResolveError != nil {
   213  			interceptor.config.ResolveError(req, err)
   214  		}
   215  	}
   216  	return ips
   217  }
   218  
   219  var (
   220  	defaultChooser     chooser.Chooser
   221  	defaultChooserOnce sync.Once
   222  )
   223  
   224  func (interceptor *simpleRetryInterceptor) chooser() chooser.Chooser {
   225  	var cs chooser.Chooser
   226  	if cs = interceptor.config.Chooser; cs == nil {
   227  		defaultChooserOnce.Do(func() {
   228  			defaultChooser = chooser.NewShuffleChooser(chooser.NewSmartIPChooser(nil))
   229  		})
   230  		cs = defaultChooser
   231  	}
   232  	return cs
   233  }
   234  
   235  func (interceptor *simpleRetryInterceptor) choose(req *http.Request, ips []net.IP, hostname string, failFast bool) (*http.Request, []net.IP) {
   236  	if len(ips) > 0 {
   237  		ips = interceptor.chooser().Choose(req.Context(), ips, &chooser.ChooseOptions{Domain: hostname, FailFast: failFast})
   238  		if len(ips) > 0 {
   239  			req = req.WithContext(clientv1.WithResolvedIPs(req.Context(), hostname, ips))
   240  		}
   241  	}
   242  	return req, ips
   243  }
   244  
   245  func (interceptor *simpleRetryInterceptor) feedbackGood(req *http.Request, hostname string, ips []net.IP) {
   246  	if len(ips) > 0 {
   247  		interceptor.resolver().FeedbackGood(req.Context(), hostname, ips)
   248  		interceptor.chooser().FeedbackGood(req.Context(), ips, &chooser.FeedbackOptions{Domain: hostname})
   249  	}
   250  }
   251  
   252  func (interceptor *simpleRetryInterceptor) feedbackBad(req *http.Request, hostname string, ips []net.IP) {
   253  	if len(ips) > 0 {
   254  		interceptor.resolver().FeedbackBad(req.Context(), hostname, ips)
   255  		interceptor.chooser().FeedbackBad(req.Context(), ips, &chooser.FeedbackOptions{Domain: hostname})
   256  	}
   257  }
   258  
   259  func (interceptor *simpleRetryInterceptor) wait(req *http.Request, attempts int) {
   260  	retryInterval := interceptor.config.getRetryInterval(req.Context(), attempts)
   261  	if interceptor.config.BeforeBackoff != nil {
   262  		interceptor.config.BeforeBackoff(req, &retrier.RetrierOptions{Attempts: attempts}, retryInterval)
   263  	}
   264  	if retryInterval >= time.Microsecond {
   265  		time.Sleep(retryInterval)
   266  	}
   267  	if interceptor.config.AfterBackoff != nil {
   268  		interceptor.config.AfterBackoff(req, &retrier.RetrierOptions{Attempts: attempts}, retryInterval)
   269  	}
   270  }
   271  
   272  func bufferResponse(resp *http.Response) error {
   273  	buffer, err := internal_io.ReadAll(resp.Body)
   274  	if err != nil {
   275  		return err
   276  	}
   277  	resp.Body = internal_io.NewBytesNopCloser(buffer)
   278  	return nil
   279  }
   280  
   281  func defaultRetryInterval() time.Duration {
   282  	return time.Duration(50+rand.Int()%50) * time.Millisecond
   283  }
   284  
   285  func addIPsToMap(m map[string]struct{}, ips []net.IP) {
   286  	for _, ip := range ips {
   287  		m[string(ip.To16())] = struct{}{}
   288  	}
   289  }
   290  
   291  func isAllIPsInMap(m map[string]struct{}, ips []net.IP) bool {
   292  	for _, ip := range ips {
   293  		if _, ok := m[string(ip.To16())]; !ok {
   294  			return false
   295  		}
   296  	}
   297  	return true
   298  }
   299  
   300  func mergeIPs(ips1, ips2 []net.IP) []net.IP {
   301  	newIPs := make([]net.IP, 0, len(ips1)+len(ips2))
   302  	newIPs = append(newIPs, ips1...)
   303  loop:
   304  	for _, ip2 := range ips2 {
   305  		for _, ip1 := range ips1 {
   306  			if ip1.Equal(ip2) {
   307  				continue loop
   308  			}
   309  		}
   310  		newIPs = append(newIPs, ip2)
   311  	}
   312  	return newIPs
   313  }