github.com/songzhibin97/gkit@v1.2.13/container/queue/codel/queue.go (about)

     1  package codel
     2  
     3  import (
     4  	"context"
     5  	"math"
     6  	"sync"
     7  	"sync/atomic"
     8  	"time"
     9  
    10  	"github.com/songzhibin97/gkit/options"
    11  	"github.com/songzhibin97/gkit/overload/bbr"
    12  )
    13  
    14  // package queue: 对列实现可控制延时算法
    15  // CoDel 可控制延时算法
    16  
    17  // config CoDel config
    18  type config struct {
    19  	// target: 对列延时(默认是20ms)
    20  	target int64
    21  
    22  	// internal: 滑动最小时间窗口宽度(默认是500ms)
    23  	internal int64
    24  }
    25  
    26  // Stat CoDel 状态信息
    27  type Stat struct {
    28  	Dropping bool
    29  	Packets  int64
    30  	FaTime   int64
    31  	DropNext int64
    32  }
    33  
    34  // packet:
    35  type packet struct {
    36  	ch chan bool
    37  	ts int64
    38  }
    39  
    40  // Queue CoDel buffer 缓冲队列
    41  type Queue struct {
    42  	// dropping: 是否处于降级状态
    43  	dropping bool
    44  
    45  	pool    sync.Pool
    46  	packets chan packet
    47  
    48  	mux      sync.RWMutex
    49  	conf     *config
    50  	count    int64 // 计数请求数量
    51  	faTime   int64
    52  	dropNext int64 // 丢弃请求的数量
    53  }
    54  
    55  // Reload 重新加载配置
    56  func (q *Queue) Reload(c *config) {
    57  	if c == nil || c.internal <= 0 || c.target <= 0 {
    58  		return
    59  	}
    60  	q.mux.Lock()
    61  	defer q.mux.Unlock()
    62  	q.conf = c
    63  }
    64  
    65  // Stat 返回CoDel状态信息
    66  func (q *Queue) Stat() Stat {
    67  	q.mux.Lock()
    68  	defer q.mux.Unlock()
    69  	return Stat{
    70  		Dropping: q.dropping,
    71  		FaTime:   q.faTime,
    72  		DropNext: q.dropNext,
    73  		Packets:  int64(len(q.packets)),
    74  	}
    75  }
    76  
    77  // Push 请求进入CoDel Queue
    78  // 如果返回错误为nil,则在完成请求处理后,调用方必须调用q.Done()
    79  func (q *Queue) Push(ctx context.Context) (err error) {
    80  	r := packet{
    81  		ch: q.pool.Get().(chan bool),
    82  		ts: time.Now().UnixNano() / int64(time.Millisecond),
    83  	}
    84  	select {
    85  	case q.packets <- r:
    86  	default:
    87  		// 如果缓冲区阻塞,直接将 err 赋值,并且将资源放回pool中
    88  		err = bbr.LimitExceed
    89  		q.pool.Put(r.ch)
    90  	}
    91  	// 判断是否发送到缓冲区
    92  	if err == nil {
    93  		select {
    94  		case drop := <-r.ch:
    95  			// r.ch = true
    96  			if drop {
    97  				err = bbr.LimitExceed
    98  			}
    99  			q.pool.Put(r.ch)
   100  		case <-ctx.Done():
   101  			err = ctx.Err()
   102  		}
   103  	}
   104  	return
   105  }
   106  
   107  // Pop 弹出 CoDel Queue 的请求
   108  func (q *Queue) Pop() {
   109  	for {
   110  		select {
   111  		case p := <-q.packets:
   112  			drop := q.judge(p)
   113  			select {
   114  			case p.ch <- drop:
   115  				if !drop {
   116  					return
   117  				}
   118  			default:
   119  				q.pool.Put(p.ch)
   120  			}
   121  		default:
   122  			return
   123  		}
   124  	}
   125  }
   126  
   127  // controlLaw CoDel 控制率
   128  func (q *Queue) controlLaw(now int64) int64 {
   129  	atomic.StoreInt64(&q.dropNext, now+int64(float64(q.conf.internal)/math.Sqrt(float64(q.count))))
   130  	return atomic.LoadInt64(&q.dropNext)
   131  }
   132  
   133  // judge 决定数据包是否丢弃
   134  // Core: CoDel
   135  func (q *Queue) judge(p packet) (drop bool) {
   136  	now := time.Now().UnixNano() / int64(time.Millisecond)
   137  	sojurn := now - p.ts
   138  	q.mux.Lock()
   139  	defer q.mux.Unlock()
   140  	if sojurn < q.conf.target {
   141  		atomic.StoreInt64(&q.faTime, 0)
   142  	} else if atomic.LoadInt64(&q.faTime) == 0 {
   143  		atomic.StoreInt64(&q.faTime, now+q.conf.internal)
   144  	} else if now >= atomic.LoadInt64(&q.faTime) {
   145  		drop = true
   146  	}
   147  	if q.dropping {
   148  		if !drop {
   149  			// sojourn time below target - leave dropping state
   150  			q.dropping = false
   151  		} else if now > atomic.LoadInt64(&q.dropNext) {
   152  			atomic.AddInt64(&q.count, 1)
   153  			q.controlLaw(atomic.LoadInt64(&q.dropNext))
   154  			drop = true
   155  			return
   156  		}
   157  	} else if drop && (now-atomic.LoadInt64(&q.dropNext) < q.conf.internal || now-atomic.LoadInt64(&q.faTime) >= q.conf.internal) {
   158  		q.dropping = true
   159  		// If we're in a drop cycle, the drop rate that controlled the queue
   160  		// on the last cycle is a good starting point to control it now.
   161  		if now-atomic.LoadInt64(&q.dropNext) < q.conf.internal {
   162  			if atomic.LoadInt64(&q.count) > 2 {
   163  				atomic.AddInt64(&q.count, -2)
   164  			} else {
   165  				atomic.StoreInt64(&q.count, 1)
   166  			}
   167  		} else {
   168  			atomic.StoreInt64(&q.count, 1)
   169  		}
   170  		q.controlLaw(now)
   171  		drop = true
   172  		return
   173  	}
   174  	return
   175  }
   176  
   177  // Default 默认配置CoDel Queue
   178  func Default() *Queue {
   179  	return NewQueue()
   180  }
   181  
   182  // defaultConfig 默认配置
   183  func defaultConfig() *config {
   184  	return &config{
   185  		target:   20,
   186  		internal: 500,
   187  	}
   188  }
   189  
   190  // Option
   191  
   192  // SetTarget 设置对列延时
   193  func SetTarget(target int64) options.Option {
   194  	return func(c interface{}) {
   195  		c.(*config).target = target
   196  	}
   197  }
   198  
   199  // SetInternal 设置滑动窗口最小时间宽度
   200  func SetInternal(internal int64) options.Option {
   201  	return func(c interface{}) {
   202  		c.(*config).internal = internal
   203  	}
   204  }
   205  
   206  // NewQueue 实例化 CoDel Queue
   207  func NewQueue(options ...options.Option) *Queue {
   208  	// new pool
   209  	q := &Queue{
   210  		packets: make(chan packet, 2048),
   211  		conf:    defaultConfig(),
   212  	}
   213  	for _, option := range options {
   214  		option(q.conf)
   215  	}
   216  	q.pool.New = func() interface{} {
   217  		return make(chan bool)
   218  	}
   219  	return q
   220  }