ratelimiter

package
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: Apr 25, 2022 License: Apache-2.0, MIT Imports: 7 Imported by: 0

README

ratelimiter

Features

  • Distributed
  • Atomicity
  • High-performance
  • Support redis cluster
  • Support memory storage

Installation

go get gitlab.oneitfarm.com/bifrost/ratelimiter

HTTP Server Demo

Try into gitlab.oneitfarm.com/bifrost/ratelimiter directory:

go run example/main.go

Visit: http://127.0.0.1:8080/

package main

import (
	"fmt"
	"html"
	"log"
	"net/http"
	"strconv"
	"time"

	redis "github.com/go-redis/redis"
	ratelimiter "gitlab.oneitfarm.com/bifrost/ratelimiter"
)

// Implements RedisClient for redis.ClusterClient
type clusterClient struct {
	*redis.ClusterClient
}

func (c *clusterClient) RateDel(key string) error {
	return c.Del(key).Err()
}

func (c *clusterClient) RateEvalSha(sha1 string, keys []string, args ...interface{}) (interface{}, error) {
	return c.EvalSha(sha1, keys, args...).Result()
}

func (c *clusterClient) RateScriptLoad(script string) (string, error) {
	var sha1 string
	err := c.ForEachMaster(func(client *redis.Client) error {
		res, err := client.ScriptLoad(script).Result()
		if err == nil {
			sha1 = res
		}
		return err
	})
	return sha1, err
}

func main() {
	// use memory
	// limiter := ratelimiter.New(ratelimiter.Options{
	// 	Max:      10,
	// 	Duration: time.Minute, // limit to 1000 requests in 1 minute.
	// })
	var redisOptions = redis.ClusterOptions{Addrs: []string{
		"192.168.2.80:9001",
	}}
	// or use redis
	client := redis.NewClusterClient(&redisOptions)
	limiter, err := ratelimiter.New(ratelimiter.Options{Client: &clusterClient{client}, Max: 10})
	if err != nil {
		log.Println(err)
		return
	}

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		res, err := limiter.Get(r.URL.Path)
		if err != nil {
			http.Error(w, err.Error(), 500)
			return
		}

		header := w.Header()
		header.Set("X-Ratelimit-Limit", strconv.FormatInt(int64(res.Total), 10))
		header.Set("X-Ratelimit-Remaining", strconv.FormatInt(int64(res.Remaining), 10))
		header.Set("X-Ratelimit-Reset", strconv.FormatInt(res.Reset.Unix(), 10))

		if res.Remaining >= 0 {
			w.WriteHeader(200)
			fmt.Fprintf(w, "Path: %q\n", html.EscapeString(r.URL.Path))
			fmt.Fprintf(w, "Remaining: %d\n", res.Remaining)
			fmt.Fprintf(w, "Total: %d\n", res.Total)
			fmt.Fprintf(w, "Duration: %v\n", res.Duration)
			fmt.Fprintf(w, "Reset: %v\n", res.Reset)
		} else {
			after := int64(res.Reset.Sub(time.Now())) / 1e9
			header.Set("Retry-After", strconv.FormatInt(after, 10))
			w.WriteHeader(429)
			fmt.Fprintf(w, "Rate limit exceeded, retry in %d seconds.\n", after)
		}
	})
	log.Fatal(http.ListenAndServe(":8081", nil))
}

Documentation

Overview

Package ratelimiter provides the fastest abstract rate limiter, base on redis.

Index

Constants

View Source
const Version = "0.5.3"

Version is Ratelimiter's version

Variables

This section is empty.

Functions

This section is empty.

Types

type Limiter

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

Limiter struct.

func New

func New(ctx context.Context, opts Options) (*Limiter, error)

New returns a Limiter instance with given options. If options.Client omit, the limiter is a memory limiter

func (*Limiter) Get

func (l *Limiter) Get(ctx context.Context, id string, policy ...int) (Result, error)

Get get a limiter result for id. support custom limiter policy.

Get get a limiter result:

userID := "user-123456"
res, err := limiter.Get(userID)
if err == nil {
    fmt.Println(res.Reset)     // 2016-10-11 21:17:53.362 +0800 CST
    fmt.Println(res.Total)     // 100
    fmt.Println(res.Remaining) // 100
    fmt.Println(res.Duration)  // 1m
}

Get get a limiter result with custom limiter policy:

id := "id-123456"
policy := []int{100, 60000, 50, 60000, 50, 120000}
res, err := limiter.Get(id, policy...)

func (*Limiter) Remove

func (l *Limiter) Remove(ctx context.Context, id string) error

Remove remove limiter record for id

type Options

type Options struct {
	Max      int           // The max count in duration for no policy, default is 100.
	Duration time.Duration // Count duration for no policy, default is 1 Minute.
	Prefix   string        // Redis key prefix, default is "LIMIT:".
	Client   RedisClient   // Use a redis client for limiter, if omit, it will use a memory limiter.
}

Options for Limiter

type RedisClient

type RedisClient interface {
	RateDel(context.Context, string) error
	RateEvalSha(context.Context, string, []string, ...interface{}) (interface{}, error)
	RateScriptLoad(context.Context, string) (string, error)
}

RedisClient defines a redis client struct that ratelimiter need. Examples: https://github.com/teambition/ratelimiter-go/blob/master/ratelimiter_test.go#L18

Implements RedisClient for a simple redis client:

import "gopkg.in/redis.v4"

type redisClient struct {
    *redis.Client
}

func (c *redisClient) RateDel(key string) error {
    return c.Del(key).Err()
}
func (c *redisClient) RateEvalSha(sha1 string, keys []string, args ...interface{}) (interface{}, error) {
    return c.EvalSha(sha1, keys, args...).Result()
}
func (c *redisClient) RateScriptLoad(script string) (string, error) {
    return c.ScriptLoad(lua).Result()
}

Implements RedisClient for a cluster redis client:

import "gopkg.in/redis.v4"

type clusterClient struct {
    *redis.ClusterClient
}

func (c *clusterClient) RateDel(key string) error {
    return c.Del(key).Err()
}
func (c *clusterClient) RateEvalSha(sha1 string, keys []string, args ...interface{}) (interface{}, error) {
    return c.EvalSha(sha1, keys, args...).Result()
}
func (c *clusterClient) RateScriptLoad(script string) (string, error) {
    var sha1 string
    err := c.ForEachMaster(func(client *redis.Client) error {
        res, err := client.ScriptLoad(script).Result()
        if err == nil {
            sha1 = res
        }
        return err
    })
    return sha1, err
}

Uses it:

client := redis.NewClient(&redis.Options{
    Addr: "localhost:6379",
})
limiter := ratelimiter.New(ratelimiter.Options{Client: redisClient{client}})

type Result

type Result struct {
	Total     int           // It Equals Options.Max, or policy max
	Remaining int           // It will always >= -1
	Duration  time.Duration // It Equals Options.Duration, or policy duration
	Reset     time.Time     // The limit record reset time
}

Result of limiter.Get

Jump to

Keyboard shortcuts

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