package golimiter import ( "fmt" "sync" "time" ) const expiredCycCount = 3 type Bucket struct { refreshedAt time.Time token int } func NewBucket(token int) *Bucket { return &Bucket{ refreshedAt: time.Now(), token: token, } } func (b *Bucket) Access(cyc, incr, decr int) bool { now := time.Now() if decr > incr { return false } else if b.token >= decr { b.token -= decr return true } if b.refreshedAt. Add(time.Duration(cyc) * time.Millisecond). After(now) { return false } b.token = incr - decr b.refreshedAt = now return true } type Limiter struct { buckets map[string]*Bucket cap int cyc int cleanBatch int mtx *sync.RWMutex } func New(cap, cyc int) *Limiter { if cap <= 0 { panic("limiter: invalid cap <= 0") } if cyc <= 0 { panic(fmt.Sprintf("limiter: invalid cyc=%d", cyc)) } return &Limiter{ buckets: make(map[string]*Bucket), cap: cap, cyc: cyc, cleanBatch: 10, mtx: &sync.RWMutex{}, } } // func NewWithcleanBatch(cap, cyc, cleanBatch int64, refill int) *Limiter { // limiter := New(cap, cyc, refill) // limiter.cleanBatch = cleanBatch // return limiter // } func (l *Limiter) Access(id string, incr, decr int) bool { l.mtx.Lock() defer l.mtx.Unlock() b, ok := l.buckets[id] if !ok { size := len(l.buckets) if size > l.cap/2 { l.clean() } size = len(l.buckets) if size+1 > l.cap || incr < decr { return false } l.buckets[id] = NewBucket(incr - decr) return true } return b.Access(l.cyc, incr, decr) } func (l *Limiter) clean() { count := 0 for key, bucket := range l.buckets { if bucket.refreshedAt. Add(time.Duration(l.cyc*expiredCycCount) * time.Millisecond). Before(time.Now()) { delete(l.buckets, key) } if count++; count >= 10 { break } } } func (l *Limiter) GetCap() int { l.mtx.RLock() defer l.mtx.RUnlock() return l.cap } func (l *Limiter) GetCyc() int { l.mtx.RLock() defer l.mtx.RUnlock() return l.cyc }