github.com/songzhibin97/gkit@v1.2.13/window/leap_array.go (about)

     1  package window
     2  
     3  import (
     4  	"errors"
     5  	"runtime"
     6  	"sync/atomic"
     7  
     8  	"github.com/songzhibin97/gkit/internal/clock"
     9  	"github.com/songzhibin97/gkit/internal/sys/mutex"
    10  )
    11  
    12  var (
    13  	ErrBucketBuilderIsNil    = errors.New("invalid parameters, Builder is nil")
    14  	ErrWindowNotSegmentation = errors.New("invalid parameters,window is not segmentation")
    15  	ErrTimeBehindStart       = errors.New("time already behind bucketStart")
    16  )
    17  
    18  // TimeChick 自定义校验 时间戳 函数
    19  type TimeChick func(uint64) bool
    20  
    21  // LeapArray 基于 Bucket 实现的 leap array
    22  // 例如: bucketSize == 200ms,intervalSize == 1000ms,所以n = 5
    23  // 假设当前是 时间是888ms 构建下图
    24  //   B0       B1      B2     B3      B4
    25  //   |_______|_______|_______|_______|_______|
    26  //  1000    1200    1400    1600    800    (1000)
    27  //                                        ^
    28  //                                      time=888
    29  type LeapArray struct {
    30  	// lock: 互斥自旋锁
    31  	mu mutex.Mutex
    32  
    33  	// bucketSize: 桶的长度
    34  	bucketSize uint64
    35  
    36  	// intervalSize: array间隔大小
    37  	intervalSize uint64
    38  
    39  	// n: 代表桶的个数
    40  	n uint64
    41  
    42  	// array: 底层数组
    43  	array *AtomicArray
    44  }
    45  
    46  // getTimeIndex 获取当前时间 命中的index索引
    47  func (s *LeapArray) getTimeIndex(now uint64) uint64 {
    48  	id := now / s.bucketSize
    49  	return id % s.array.length
    50  }
    51  
    52  // getBucketOfTime 根据时间戳获取到对应的桶
    53  func (s *LeapArray) getBucketOfTime(now uint64, builder BucketBuilder) (*Bucket, error) {
    54  	index := s.getTimeIndex(now)
    55  	start := calculateStartTime(now, s.bucketSize)
    56  
    57  	for {
    58  		// 获取当前的 bucket
    59  		old := s.array.getBucket(index)
    60  		if old == nil {
    61  			// 懒加载
    62  			b := &Bucket{
    63  				Start: start,
    64  				Value: atomic.Value{},
    65  			}
    66  			b.Value.Store(builder.NewEmptyBucket())
    67  			if s.array.compareAndSwap(index, nil, b) {
    68  				return b, nil
    69  			}
    70  			runtime.Gosched()
    71  		} else if start == atomic.LoadUint64(&old.Start) {
    72  			// 在时间范围内
    73  			return old, nil
    74  		} else if start > atomic.LoadUint64(&old.Start) {
    75  			// 命中下一个周期
    76  
    77  			// 尝试获取锁
    78  			if s.mu.TryLock() {
    79  				// 重置
    80  				old = builder.Reset(old, start)
    81  				s.mu.Unlock()
    82  				return old, nil
    83  			}
    84  			runtime.Gosched()
    85  		} else if start < atomic.LoadUint64(&old.Start) {
    86  			if s.n == 1 {
    87  				// 如果在跳转数组中 n == 1,则在并发情况下,这种情况是可能的
    88  				return old, nil
    89  			}
    90  			return nil, ErrTimeBehindStart
    91  		}
    92  	}
    93  }
    94  
    95  // GetBucket 获取桶,封装 getBucketOfTime
    96  func (s *LeapArray) GetBucket(builder BucketBuilder) (*Bucket, error) {
    97  	return s.getBucketOfTime(clock.GetTimeMillis(), builder)
    98  }
    99  
   100  // isDisable 判断当前桶是否被弃用
   101  func (s *LeapArray) isDisable(now uint64, b *Bucket) bool {
   102  	ws := atomic.LoadUint64(&b.Start)
   103  	return (now - ws) > s.intervalSize
   104  }
   105  
   106  // getValueOfTime 通过 now 为基点 获取array所有桶
   107  func (s *LeapArray) getValueOfTime(now uint64) []*Bucket {
   108  	ret := make([]*Bucket, 0, s.array.length)
   109  	for i := (uint64)(0); i < s.array.length; i++ {
   110  		b := s.array.getBucket(i)
   111  		if b == nil || s.isDisable(now, b) {
   112  			continue
   113  		}
   114  		ret = append(ret, b)
   115  	}
   116  	return ret
   117  }
   118  
   119  // Values getValueOfTime 封装
   120  func (s *LeapArray) Values() []*Bucket {
   121  	return s.getValueOfTime(clock.GetTimeMillis())
   122  }
   123  
   124  // ValuesChick 加入自定义 TimeChick 过滤value
   125  func (s *LeapArray) ValuesChick(now uint64, chick TimeChick) []*Bucket {
   126  	ret := make([]*Bucket, 0, s.array.length)
   127  	for i := (uint64)(0); i < s.array.length; i++ {
   128  		b := s.array.getBucket(i)
   129  		if b == nil || s.isDisable(now, b) || !chick(atomic.LoadUint64(&b.Start)) {
   130  			continue
   131  		}
   132  		ret = append(ret, b)
   133  	}
   134  	return ret
   135  }
   136  
   137  // NewLeapArray 初始化 leap array
   138  func NewLeapArray(n uint64, intervalSize uint64, builder BucketBuilder) (*LeapArray, error) {
   139  	if builder == nil {
   140  		return nil, ErrBucketBuilderIsNil
   141  	}
   142  	if intervalSize%n != 0 {
   143  		return nil, ErrWindowNotSegmentation
   144  	}
   145  	bucketSize := intervalSize / n
   146  	return &LeapArray{
   147  		bucketSize:   bucketSize,
   148  		intervalSize: intervalSize,
   149  		n:            n,
   150  		array:        NewAtomicArray(n, bucketSize, builder),
   151  	}, nil
   152  }