Skip to content

Commit

Permalink
Added package documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
Narasimha1997 committed Oct 3, 2021
1 parent 022c1ee commit e1b2b8d
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 17 deletions.
42 changes: 27 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# ratelimiter

![Tests](https:/Narasimha1997/ratelimiter/actions/workflows/test.yml/badge.svg)

A generic concurrent rate limiter library for Golang based on Sliding-window rate limitng algorithm.

The implementation of rate-limiter algorithm is based on Scalable Distributed Rate Limiter algorithm used in Kong API gateway. Read [this blog](https://konghq.com/blog/how-to-design-a-scalable-rate-limiting-algorithm/) for more details.
Expand All @@ -20,27 +23,16 @@ There are two types of rate-limiters used.
1. **Generic rate-limiter**:
```go
/* creates an instance of DefaultLimiter and returns it's pointer.
Parameters:
limit: The number of tasks to be allowd
size: duration
*/
func NewDefaultLimiter(limit uint64, size time.Duration) *DefaultLimiter

```

2. **On-demand rate-limiter**
```go
/* creates an instance of DefaultLimiter and returns it's pointer.
Parameters:
Parameters:
limit: The number of tasks to be allowd
size: duration
*/
func NewSyncLimiter(limit uint64, size time.Duration) *SyncLimiter
func NewDefaultLimiter(limit uint64, size time.Duration) *DefaultLimiter

/*
Kill the limiter, returns error if the limiter has been killed already.
*/
func (s *SyncLimiter) Kill() error
func (s *DefaultLimiter) Kill() error

/*
Makes decison whether n tasks can be allowed or not.
Expand All @@ -51,7 +43,22 @@ There are two types of rate-limiters used.
if limiter is inactive (or it is killed), returns an error
the boolean flag is either true - i.e n tasks can be allowed or false otherwise.
*/
func (s *SyncLimiter) ShouldAllow(n uint64) (bool, error)
func (s *DefaultLimiter) ShouldAllow(n uint64) (bool, error)

/*
Kill the limiter, returns error if the limiter has been killed already.
*/
func (s *DefaultLimiter) Kill() error
```

2. **On-demand rate-limiter**
```go
/* creates an instance of SyncLimiter and returns it's pointer.
Parameters:
limit: The number of tasks to be allowd
size: duration
*/
func NewSyncLimiter(limit uint64, size time.Duration) *SyncLimiter

/*
Kill the limiter, returns error if the limiter has been killed already.
Expand All @@ -68,6 +75,11 @@ There are two types of rate-limiters used.
the boolean flag is either true - i.e n tasks can be allowed or false otherwise.
*/
func (s *SyncLimiter) ShouldAllow(n uint64) (bool, error)

/*
Kill the limiter, returns error if the limiter has been killed already.
*/
func (s *SyncLimiter) Kill() error
```

3. **Attribute based Rate Limiter**
Expand Down
46 changes: 46 additions & 0 deletions attribute_limiter.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,42 @@ import (
"time"
)

// AttributeMap is a custom map type of string key and Limiter instance as value
type AttributeMap map[string]Limiter

// AttributeBasedLimiter is an instance that can manage multiple rate limiter instances
// with different configutations.
type AttributeBasedLimiter struct {
attributeMap AttributeMap
m sync.Mutex
syncMode bool
}

// Check if AttributeBasedLimiter has a limiter for the key.
//
// Parameters:
//
// 1. key: a unique key string, example: IP address, token, uuid etc
//
// Returns a boolean flag, if true, the key is already present, false otherwise.
func (a *AttributeBasedLimiter) HasKey(key string) bool {
a.m.Lock()
_, ok := a.attributeMap[key]
a.m.Unlock()
return ok
}

// Create a new key-limiter assiociation.
//
// Parameters:
//
// 1. key: a unique key string, example: IP address, token, uuid etc
//
// 2. limit: The number of tasks to be allowd
//
// 3. size: duration
//
// Returns error if the key already exists.
func (a *AttributeBasedLimiter) CreateNewKey(key string, limit uint64, size time.Duration) error {
a.m.Lock()
defer a.m.Unlock()
Expand All @@ -40,6 +61,18 @@ func (a *AttributeBasedLimiter) CreateNewKey(key string, limit uint64, size time
return nil
}

// Makes decison whether n tasks can be allowed or not.
//
// Parameters:
//
// key: a unique key string, example: IP address, token, uuid etc
//
// n: number of tasks to be processed, set this as 1 for a single task.
// (Example: An HTTP request)
//
// Returns (bool, error).
// (false, error) when limiter is inactive (or it is killed) or key is not present.
// (true/false, nil) if key exists and n tasks can be allowed or not.
func (a *AttributeBasedLimiter) ShouldAllow(key string, n uint64) (bool, error) {
a.m.Lock()
defer a.m.Unlock()
Expand All @@ -52,6 +85,13 @@ func (a *AttributeBasedLimiter) ShouldAllow(key string, n uint64) (bool, error)
return false, fmt.Errorf("key %s not found", key)
}

// Remove the key and kill its underlying limiter.
//
// Parameters:
//
// 1.key: a unique key string, example: IP address, token, uuid etc
//
// Returns an error if the key is not present.
func (a *AttributeBasedLimiter) DeleteKey(key string) error {

a.m.Lock()
Expand All @@ -69,6 +109,12 @@ func (a *AttributeBasedLimiter) DeleteKey(key string) error {
return fmt.Errorf("key %s not found", key)
}

// Creates an instance of AttributeBasedLimiter and returns it's pointer.
//
// Parameters:
//
// 1. backgroundSliding: if set to true, DefaultLimiter will be used as an underlying limiter,
// else, SyncLimiter will be used.
func NewAttributeBasedLimiter(backgroundSliding bool) *AttributeBasedLimiter {
return &AttributeBasedLimiter{
attributeMap: make(AttributeMap),
Expand Down
37 changes: 35 additions & 2 deletions limiter.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import (
"time"
)

// Limiter is an interface that is implemented by DefaultLimiter and SyncLimiter
type Limiter interface {
// limiter interface is simple,
// Kill() and ShouldAllow() these are the functions you need.
Kill() error
ShouldAllow(n uint64) (bool, error)
}

// DefaultLimiter maintains all the structures used for rate limting using a background goroutine.
type DefaultLimiter struct {
previous *Window
current *Window
Expand All @@ -25,6 +25,14 @@ type DefaultLimiter struct {
cancelFn func()
}

// Makes decison whether n tasks can be allowed or not.
//
// Parameters:
//
// 1. n: number of tasks to be processed, set this as 1 for a single task. (Example: An HTTP request)
//
// Returns (bool, error). (false, error) if limiter is inactive (or it is killed). Otherwise,
// (true/false, nil) depending on whether n tasks can be allowed or not.
func (l *DefaultLimiter) ShouldAllow(n uint64) (bool, error) {

l.lock.Lock()
Expand Down Expand Up @@ -67,6 +75,7 @@ func (l *DefaultLimiter) progressiveWindowSlider() {
}
}

// Kill the limiter, returns error if the limiter has been killed already.
func (l *DefaultLimiter) Kill() error {

l.lock.Lock()
Expand All @@ -81,6 +90,13 @@ func (l *DefaultLimiter) Kill() error {
return nil
}

// Creates an instance of DefaultLimiter and returns it's pointer.
//
// Parameters:
//
// 1. limit: The number of tasks to be allowd
//
// 2. size: duration
func NewDefaultLimiter(limit uint64, size time.Duration) *DefaultLimiter {
previous := NewWindow(0, time.Unix(0, 0))
current := NewWindow(0, time.Unix(0, 0))
Expand All @@ -102,6 +118,7 @@ func NewDefaultLimiter(limit uint64, size time.Duration) *DefaultLimiter {
return limiter
}

// SyncLimiter maintains all the structures used for rate limting on demand.
type SyncLimiter struct {
previous *Window
current *Window
Expand All @@ -118,6 +135,14 @@ func (s *SyncLimiter) getNSlidesSince(now time.Time) (time.Duration, time.Time)
return timeSinceStart / s.size, sizeAlignedTime
}

// Makes decison whether n tasks can be allowed or not.
//
// Parameters:
//
// 1. n: number of tasks to be processed, set this as 1 for a single task. (Example: An HTTP request)
//
// Returns (bool, error). (false, error) if limiter is inactive (or it is killed). Otherwise,
// (true/false, error) depending on whether n tasks can be allowed or not.
func (s *SyncLimiter) ShouldAllow(n uint64) (bool, error) {
s.lock.Lock()
defer s.lock.Unlock()
Expand Down Expand Up @@ -166,6 +191,7 @@ func (s *SyncLimiter) ShouldAllow(n uint64) (bool, error) {
return true, nil
}

// Kill the limiter, returns error if the limiter has been killed already.
func (s *SyncLimiter) Kill() error {
s.lock.Lock()
defer s.lock.Unlock()
Expand All @@ -180,6 +206,13 @@ func (s *SyncLimiter) Kill() error {
return nil
}

// Creates an instance of SyncLimiter and returns it's pointer.
//
// Parameters:
//
// 1. limit: The number of tasks to be allowd
//
// 2. size: duration
func NewSyncLimiter(limit uint64, size time.Duration) *SyncLimiter {

current := NewWindow(0, time.Unix(0, 0))
Expand Down
8 changes: 8 additions & 0 deletions window.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"time"
)

// Window represents the structure of timing-window at given point of time.
type Window struct {
count uint64
startTime time.Time
Expand Down Expand Up @@ -32,6 +33,13 @@ func (w *Window) setToState(startTime time.Time, count uint64) {
w.count = count
}

// Creates and returns a pointer to the new Window instance.
//
// Parameters:
//
// 1. count: The initial count of the window.
//
// 2. startTime: The initial starting time of the window.
func NewWindow(count uint64, startTime time.Time) *Window {

return &Window{
Expand Down

0 comments on commit e1b2b8d

Please sign in to comment.