介绍
lancet
lancet(柳叶刀)是一个全面、高效、可复用的go语言工具函数库。 lancet受到了java apache common包和lodash.js的启发。本篇仅针对其中的retry功能进行解读。
快速图解
功能解读
重试在微服务调用的过程中可以有效的应对上游接口不稳定,网络状态差等问题,为下游提供稳定可靠的服务。也算是一种降级处理的方式
Lancet Retry
Lancet 中的 retry 包中关于 重试的逻辑进行的抽象,提供了不同的重试策略, 线性避退 或 指数避退 ,并在每次重试间隔加上一定的抖动间隔,确保不会触发上游接口的限流策略。
代码解析
Lancet Retry
重试策略配置
1
2
3
4
5
6
7
8
type RetryConfig struct {
// 外部取消重试动作的上下文
context context.Context
// 最大重试次数
retryTimes uint
// 重试间隔策略
backoffStrategy BackoffStrategy
}
重试间隔接口
对比不同的重试间隔策略,其实就是每次重试间隔不同,所以只需要提供一个下次重试间隔的接口就可以了
1
2
3
4
5
// BackoffStrategy is an interface that defines a method for calculating backoff intervals.
type BackoffStrategy interface {
// CalculateInterval returns the time.Duration after which the next retry attempt should be made.
CalculateInterval() time.Duration
}
重试功能
RetryFunc 为重试的具体实现
Option 是选项设置模式,用于灵活的生成对象,具体的可以看话唠区/Option模式好处
其他的部分就比较简单了,直接根据执行的结果的成功失败,失败的话则根据 backoffStrategy.CalculateInterval
计算出来的时间间隔进行避退,并根据ctx.Done()
及时取消重试。
还有一段比较有意思的函数段是用于获取 函数指针指向的具体函数的名称:
1
2
3
4
5
type RetryFunc func() error
funcPath := runtime.FuncForPC(reflect.ValueOf(retryFunc).Pointer()).Name()
lastSlash := strings.LastIndex(funcPath, "/")
funcName := funcPath[lastSlash+1:]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// RetryFunc is function that retry executes
type RetryFunc func() error
// Option is for adding retry config
type Option func(*RetryConfig)
func Retry(retryFunc RetryFunc, opts ...Option) error {
config := &RetryConfig{
retryTimes: DefaultRetryTimes,
context: context.TODO(),
}
for _, opt := range opts {
opt(config)
}
if config.backoffStrategy == nil {
config.backoffStrategy = &linear{
interval: DefaultRetryLinearInterval,
}
}
var i uint
for i < config.retryTimes {
err := retryFunc()
if err != nil {
select {
case <-time.After(config.backoffStrategy.CalculateInterval()):
case <-config.context.Done():
return errors.New("retry is cancelled")
}
} else {
return nil
}
i++
}
funcPath := runtime.FuncForPC(reflect.ValueOf(retryFunc).Pointer()).Name()
lastSlash := strings.LastIndex(funcPath, "/")
funcName := funcPath[lastSlash+1:]
return fmt.Errorf("function %s run failed after %d times retry", funcName, i)
}
重试间隔实现
线性重试
比较简单,每次返回的重试间隔是一样的
1
2
3
4
5
6
7
8
9
10
// linear is a struct that implements the BackoffStrategy interface using a linear backoff strategy.
type linear struct {
// interval specifies the fixed duration to wait between retry attempts.
interval time.Duration
}
// CalculateInterval calculates the next interval returns a constant.
func (l *linear) CalculateInterval() time.Duration {
return l.interval
}
指数重试
可以看到每次都是以base
为底数,指数每次重试迭代+1 最大为 重试次数 ,并扩大 interval
倍 的重试间隔
1
2
3
4
5
6
7
8
9
10
11
12
13
// exponentialWithJitter is a struct that implements the BackoffStrategy interface using a exponential backoff strategy.
type exponentialWithJitter struct {
base time.Duration // base is the multiplier for the exponential backoff.
interval time.Duration // interval is the current backoff interval, which will be adjusted over time.
maxJitter time.Duration // maxJitter is the maximum amount of jitter to apply to the backoff interval.
}
// CalculateInterval calculates the next backoff interval with jitter and updates the interval.
func (e *exponentialWithJitter) CalculateInterval() time.Duration {
current := e.interval
e.interval = e.interval * e.base
return current + jitter(e.maxJitter)
}
位移间隔重试
这个间隔的避加以interval
为操作数,左移操作shifter
位
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// shiftExponentialWithJitter is a struct that implements the BackoffStrategy interface using a exponential backoff strategy.
type shiftExponentialWithJitter struct {
interval time.Duration // interval is the current backoff interval, which will be adjusted over time.
maxJitter time.Duration // maxJitter is the maximum amount of jitter to apply to the backoff interval.
shifter uint64 // shift by n faster than multiplication
}
// CalculateInterval calculates the next backoff interval with jitter and updates the interval.
// Uses shift instead of multiplication
func (e *shiftExponentialWithJitter) CalculateInterval() time.Duration {
current := e.interval
e.interval = e.interval << e.shifter
return current + jitter(e.maxJitter)
}
话唠区
Option模式好处
当创建对象需要新增一个可选配置选项时-潜台词就是如果不设置就会有默认值,只需要创建新参数对应的的Option就可以了。
可以简单对比下使用和不使用Option的情况下,对于调用方的影响
Bad Case
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//
//Old
type NewCacheConfig struct {
MaxSize int64
}
func NewCacheConfig(maxSize int64)*NewCacheConfig{
return &NewCacheConfig{
MaxSize: maxSize,
}
}
NewCacheConfig(20)
//New
type NewCacheConfig struct {
MaxSize int64
MaxLifeTime time.Duration
}
func NewCacheConfig(maxSize int64,maxLifeTime time.Duration)*NewCacheConfig{
return &NewCacheConfig{
MaxSize: maxSize,
MaxLifeTime: maxLifeTime,
}
}
//caller need to be changed
NewCacheConfig(20,2*time.Second)
Good Case
可以发现调用方在调用创建新版本对象的时候,可以不用改动,也可以使用WithMaxLifeTime
进行设置,这也符合可选配置选项的含义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//Old
type Option func(*CacheConfig)
type CacheConfig struct {
MaxSize int64
}
func NewCacheConfig(maxSize int64,opts ...Option)*CacheConfig{
cc := &CacheConfig{
MaxSize: maxSize,
}
for _,opt := range opts {
opt(cc)
}
return cc
}
NewCacheConfig(20)
//New
type Cacher struct {
MaxSize int64
MaxLifeTime time.Duration
}
func WithMaxLifeTime(maxLifeTime time.Duration)Option{
return func(cc *CacheConfig){
cc.MaxLifeTime = maxLifeTime
}
}
func NewCacheConfig(maxSize int64,maxLifeTime time.Duration)*CacheConfig{
cc := &CacheConfig{
MaxSize: maxSize,
MaxLifeTime: 3*time.Second,
}
for _,opt := range opts {
opt(cc)
}
return cc
}
//caller no need to be changed
NewCacheConfig(20) //output &{20 3s}
NewCacheConfig(20,WithMaxLifeTime(1*time.Second)) //output &{20 1s}
如果你看不到评论,那么就真的看不到评论w(゜Д゜)w