gitee.com/quant1x/gox@v1.21.2/coroutine/rolling_once.go (about)

     1  package coroutine
     2  
     3  import (
     4  	"gitee.com/quant1x/gox/logger"
     5  	"gitee.com/quant1x/gox/runtime"
     6  	"gitee.com/quant1x/gox/timestamp"
     7  	"sync"
     8  	"sync/atomic"
     9  	"time"
    10  )
    11  
    12  const (
    13  	// 滑动窗口的锚点毫秒数, 默认0
    14  	rollingAnchorPoint = 0
    15  	// 窗口相对于锚点的偏移量
    16  	rollingWindow = timestamp.MillisecondsPerDay
    17  	// 相对于默认, 每天9点整
    18  	offsetWindow = timestamp.MillisecondsPerHour * 9
    19  )
    20  
    21  // 计算下一个时间窗口
    22  // 当前时间戳和当前观察点+偏移量比较
    23  // 下一个时间窗口observer+滑动窗口尺寸
    24  func nextTimeWindow(observer, rollingWindow int64) (next, current int64, canSwitch bool) {
    25  	now := timestamp.Now()
    26  	next = observer + rollingWindow
    27  	if now >= next {
    28  		canSwitch = true
    29  	}
    30  	elapsed := timestamp.SinceZero(now)
    31  	current = elapsed
    32  	return
    33  }
    34  
    35  // 获取当前观察点
    36  // 当日0的毫秒数zero + offsetMilliSeconds
    37  func getCurrentObserver(offsetMilliSeconds int64) int64 {
    38  	zero := timestamp.Today()
    39  	return zero + offsetMilliSeconds
    40  }
    41  
    42  // RollingOnce 周期性懒加载机制
    43  type RollingOnce struct {
    44  	done            uint32
    45  	m               sync.Mutex
    46  	once            sync.Once     // for ticker
    47  	ticker          *time.Ticker  // 定时器
    48  	currentWindow   int64         // 滑动窗口的毫秒数, 这里默认1天
    49  	currentOffset   atomic.Int64  // 相对于0点整的偏移毫秒数
    50  	currentObserver atomic.Int64  // 当前窗口期起点的毫秒数
    51  	lazyFunc        func()        // 懒加载函数指针
    52  	finished        chan struct{} // 关闭ticker的信号
    53  }
    54  
    55  // Close 资源关闭方法
    56  func (o *RollingOnce) Close() {
    57  	// 发送结束信号
    58  	o.finished <- struct{}{}
    59  	close(o.finished)
    60  }
    61  
    62  // GetCurrentAnchorPoint 获取当前时间窗口期的锚点
    63  func (o *RollingOnce) GetCurrentAnchorPoint() int64 {
    64  	return o.currentObserver.Load()
    65  }
    66  
    67  func (o *RollingOnce) initTicker() {
    68  	// 1. 设置窗口期
    69  	o.currentWindow = rollingWindow
    70  	// 2. 第一步初始化offset, // 偏移默认是常量offsetWindows
    71  	o.currentOffset.CompareAndSwap(0, offsetWindow)
    72  	// 3. 第二步初始化当前时间窗口观察点
    73  	o.currentObserver.CompareAndSwap(0, getCurrentObserver(o.currentOffset.Load()))
    74  	o.finished = make(chan struct{})
    75  	if o.ticker == nil {
    76  		go o.runTicker()
    77  	}
    78  }
    79  
    80  // SetOffsetTime 用小时数,分钟数设置滑动窗口的偏移量
    81  func (o *RollingOnce) SetOffsetTime(hour, minute int) {
    82  	offset := timestamp.MillisecondsPerHour * hour
    83  	offset += timestamp.MillisecondsPerMinute * minute
    84  	o.SetOffsetForZero(int64(offset))
    85  }
    86  
    87  // SetOffsetForZero 设置时间窗口变化的偏移量
    88  //
    89  //	为非默认9点整重置done预留的功能性方法
    90  func (o *RollingOnce) SetOffsetForZero(offsetMilliSeconds int64) {
    91  	o.currentOffset.Store(offsetMilliSeconds)
    92  	o.updateObserverOfWindow()
    93  }
    94  
    95  // 更新窗口期的观察点
    96  func (o *RollingOnce) updateObserverOfWindow() {
    97  	o.currentObserver.Store(getCurrentObserver(o.currentOffset.Load()))
    98  }
    99  
   100  // WindowIsExpired 检查当前窗口期的是否过期
   101  func (o *RollingOnce) WindowIsExpired() bool {
   102  	_, _, canSwitch := nextTimeWindow(o.currentObserver.Load(), o.currentWindow)
   103  	return canSwitch
   104  }
   105  
   106  func (o *RollingOnce) runTicker() {
   107  	funcName := runtime.FuncName(o.lazyFunc)
   108  	o.ticker = time.NewTicker(100 * time.Millisecond)
   109  	defer o.ticker.Stop()
   110  	for {
   111  		select {
   112  		case <-o.ticker.C:
   113  			// 检查滑动窗口期是否过时
   114  			if o.WindowIsExpired() {
   115  				if runtime.Debug() {
   116  					logger.Infof("RollingOnce[%s]: reset begin", funcName)
   117  				}
   118  				o.Reset()
   119  				if runtime.Debug() {
   120  					logger.Infof("RollingOnce[%s]: reset end", funcName)
   121  				}
   122  			}
   123  		case <-o.finished:
   124  			// 收到结束信号, 退出循环
   125  			break
   126  		}
   127  	}
   128  }
   129  
   130  func (o *RollingOnce) Do(f func()) {
   131  	if o.lazyFunc == nil {
   132  		o.lazyFunc = f
   133  	}
   134  	o.once.Do(o.initTicker)
   135  	if atomic.LoadUint32(&o.done) == 0 {
   136  		o.doSlow(f)
   137  	}
   138  }
   139  
   140  func (o *RollingOnce) doSlow(f func()) {
   141  	o.m.Lock()
   142  	defer o.m.Unlock()
   143  	if o.done == 0 {
   144  		defer atomic.StoreUint32(&o.done, 1)
   145  		f()
   146  	}
   147  }
   148  
   149  // Reset 被动的方式重置初始化done标志
   150  func (o *RollingOnce) Reset() {
   151  	if atomic.LoadUint32(&o.done) == 1 {
   152  		o.resetSlow()
   153  	}
   154  }
   155  
   156  func (o *RollingOnce) resetSlow() {
   157  	o.m.Lock()
   158  	defer o.m.Unlock()
   159  	if o.done == 1 {
   160  		atomic.StoreUint32(&o.done, 0)
   161  		// 重置观察点
   162  		o.updateObserverOfWindow()
   163  	}
   164  }