lazy_rate_limiter

package module
v0.0.0-...-31667af Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 23, 2021 License: MIT Imports: 8 Imported by: 0

README

Lazy Rate Limiter

Lazy rate limiter is a simple sliding log rate limiter, with lazy state sync from Redis. It's useful for use-cases where you have a need to apply a sliding-log time-based rate-limiting logic, that is shared by multiple processes (for example API servers), and are willing to compromise state accuracy for speed.

Components

The library exposes two structures - a Bucket which encapsulates rate-limiting logic for a distinct key, and a Limiter which manages buckets and syncs their state to and from Redis.

Bucket state is synced to Redis every time available capacity is taken from it. State is synced from Redis to all buckets periodically, to avoid having to query Redis every time capacity is needed. This may result in a slight out-of-sync state between buckets running in two separate processes (depending on the Limiter's sync interval), but adds the benefit of not having to perform I/O operations to Redis synchronously.

Buckets

Rate limiting logic is encapsulated in buckets. Each bucket has a unique key (for example, your API request path), a max limit, and a time interval. For example, a bucket with the key /api/v1/notes, a limit of 10 and an interval of 1 * time.Minute will let you limit requests to the above key to 10 requests per minute.

Limiter

Buckets are managed by a Limiter struct, that is responsible for the following:

  1. Accessing buckets and asking for capacity
  2. Syncing local bucket capacity to Redis
  3. Periodically syncing state from Redis to each bucket

Implementation

Start by installing the module in your code (Go Modules are assumed) - go get -u bitbucket.org/quicklizard/lazy_rate_limiter

In your code base add the following:

package main

import (
	lrl "bitbucket.org/quicklizard/lazy_rate_limiter"
	"errors"
	"fmt"
	"net/http"
	"time"
)

var rateLimiter *lrl.Limiter

func setup() {
	opts := lrl.Options{
		RedisURL:     "redis://localhost:6379/0",
		SyncInterval: 1 * time.Second,
	}
	rateLimiter = lrl.New(opts)
	rateLimiter.Start()
	rateLimiter.AddBucket("/hello", 1*time.Minute, 10)
}

func hello(w http.ResponseWriter, req *http.Request) {
	allowed, _ := rateLimiter.Take("/hello")
	if allowed {
		fmt.Fprintf(w, "hello\n")
	} else {
		err := errors.New("max capacity reached")
		http.Error(w, err.Error(), http.StatusTooManyRequests)
	}
}

func main() {
    setup()
	http.HandleFunc("/hello", hello)
	http.ListenAndServe(":8080", nil)
    // rateLimiter.Stop() should be called before your code exits
}

For a complete example, see examples/server.go

Credits

I used the basic Redis sorted-set logic from Implementing a sliding log rate limiter with Redis and Golang as the inspiration for this library.

License

This code is available under MIT License.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Bucket

type Bucket struct {
	Interval time.Duration
	Limit    int64
	Key      string
	// contains filtered or unexported fields
}

Bucket holds rate-limiting capabilities for a given unique key, with defined limit in duration. For example, a bucket for the key "TEST" with a limit of 10 in a time interval of 1 minute will allow 10 operations (Takes) in 1 minute.

type Limiter

type Limiter struct {
	// contains filtered or unexported fields
}

Limiter manages buckets, and syncs their state to and from Redis. It is used as the main entry point to the rate-limiting functionality and encapsulates bucket management and bucket-capacity access. Buckets are managed in Redis as sorted sets with time-based scores (saved as UnixNano), and set members are expired based on their score, in relation to current time. This ensures a sliding-log behaviour where older entries are cleaned up based on the limit and time-interval of each bucket.

func New

func New(options Options) *Limiter

New creates a new Limiter object with options and returns it

func (*Limiter) AddBucket

func (l *Limiter) AddBucket(key string, interval time.Duration, limit int64) *Bucket

AddBucket creates a new rate-limiting bucket on the provided key, with the given time interval and maximum limit. If a bucket already exists for the given key, it is returned. Otherwise a new bucket is created and returned.

func (*Limiter) OnError

func (l *Limiter) OnError(c chan error)

OnError accepts an error channel that can be used to listen to internal errors that occur during Redis operations and handle them

func (*Limiter) Start

func (l *Limiter) Start() error

Start connects to Redis backend, and runs internal goroutine that executes async state syncing logic from and to Redis. It returns an error if Redis connection fails

func (*Limiter) Stop

func (l *Limiter) Stop()

Stop tears down the Limiter's execution flow by stopping async state sync goroutine and disconnecting from Redis

func (*Limiter) Take

func (l *Limiter) Take(key string) (bool, error)

Take will try to take a capacity of 1 from the bucket identified by key. If the bucket is found, Take will return a boolean flag indicating whether capacity if available (true) or not (false), along with nil error. If the bucket isn't found, Take will return false, along with an error indicating the bucket is missing.

func (*Limiter) TakeOrAdd

func (l *Limiter) TakeOrAdd(key string, interval time.Duration, limit int64) bool

TakeOrAdd will look for a bucket identified by key, creating it if it doesn't exist, using provided interval and limit, and returning a boolean flag indicating whether capacity is available on the bucket (true) or not (false).

type Options

type Options struct {
	RedisURL     string
	SyncInterval time.Duration
}

Options is a struct holding setup information for the Limiter object. RedisURL is used to set up a connection to a Redis backend. SyncInterval is used to periodically sync state from Redis to buckets

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL