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 }