Implementation of the Token Bucket algorithm in PHP.

Overview

Token Bucket

This is a threadsafe implementation of the Token Bucket algorithm in PHP. You can use a token bucket to limit an usage rate for a resource (e.g. a stream bandwidth or an API usage).

The token bucket is an abstract metaphor which doesn't have a direction of the resource consumption. I.e. you can limit a rate for consuming or producing. E.g. you can limit the consumption rate of a third party API service, or you can limit the usage rate of your own API service.

Installation

Use Composer:

composer require bandwidth-throttle/token-bucket

Usage

The package is in the namespace bandwidthThrottle\tokenBucket.

Example

This example will limit the rate of a global resource to 10 requests per second for all requests.

use bandwidthThrottle\tokenBucket\Rate;
use bandwidthThrottle\tokenBucket\TokenBucket;
use bandwidthThrottle\tokenBucket\storage\FileStorage;

$storage = new FileStorage(__DIR__ . "/api.bucket");
$rate    = new Rate(10, Rate::SECOND);
$bucket  = new TokenBucket(10, $rate, $storage);
$bucket->bootstrap(10);

if (!$bucket->consume(1, $seconds)) {
    http_response_code(429);
    header(sprintf("Retry-After: %d", floor($seconds)));
    exit();
}

echo "API response";

Note: In this example TokenBucket::bootstrap() is part of the code. This is not recommended for production, as this is producing unnecessary storage communication. TokenBucket::bootstrap() should be part of the application's bootstrap or deploy process.

Scope of the storage

First you need to decide the scope of your resource. I.e. do you want to limit it per request, per user or amongst all requests? You can do this by choosing a Storage implementation of the desired scope:

  • The RequestScope limits the rate only within one request. E.g. to limit the bandwidth of a download. Each requests will have the same bandwidth limit.

  • The SessionScope limits the rate of a resource within a session. The rate is controlled over all requests of one session. E.g. to limit the API usage per user.

  • The GlobalScope limits the rate of a resource for all processes (i.e. requests). E.g. to limit the aggregated download bandwidth of a resource over all processes. This scope permits race conditions between processes. The TokenBucket is therefore synchronized on a shared mutex.

TokenBucket

When you have your storage you can finally instantiate a TokenBucket. The first parameter is the capacity of the bucket. I.e. there will be never more tokens available. This also means that consuming more tokens than the capacity is invalid.

The second parameter is the token-add-Rate. It determines the speed for filling the bucket with tokens. The rate is the amount of tokens added per unit, e.g. new Rate(100, Rate::SECOND) would add 100 tokens per second.

The third parameter is the storage, which is used to persist the token amount of the bucket. The storage does determine the scope of the bucket.

Bootstrapping

A token bucket needs to be bootstrapped. While the method TokenBucket::bootstrap() doesn't have any side effects on an already bootstrapped bucket, it is not recommended do call it for every request. Better include that in your application's bootstrap or deploy process.

Consuming

Now that you have a bootstrapped bucket, you can start consuming tokens. The method TokenBucket::consume() will either return true if the tokens were consumed or false else. If the tokens were consumed your application can continue to serve the resource.

Else if the tokens were not consumed you should not serve the resource. In that case consume() did write a duration of seconds into its second parameter (which was passed by reference). This is the duration until sufficient tokens would be available.

BlockingConsumer

In the first example we did either serve the request or fail with the HTTP status code 429. This is actually a very resource efficient way of throtteling API requests as it doesn't reserve resources on your server.

However sometimes it is desirable not to fail but instead wait a little bit and then continue serving the requests. You can do this by consuming the token bucket with a BlockingConsumer.

use bandwidthThrottle\tokenBucket\Rate;
use bandwidthThrottle\tokenBucket\TokenBucket;
use bandwidthThrottle\tokenBucket\BlockingConsumer;
use bandwidthThrottle\tokenBucket\storage\FileStorage;

$storage  = new FileStorage(__DIR__ . "/api.bucket");
$rate     = new Rate(10, Rate::SECOND);
$bucket   = new TokenBucket(10, $rate, $storage);
$consumer = new BlockingConsumer($bucket);
$bucket->bootstrap(10);

// This will block until one token is available.
$consumer->consume(1);

echo "API response";

This will effectively limit the rate to 10 requests per seconds as well. But in this case the client has not to bother with the 429 error. Instead the connection is just delayed to the desired rate.

License and authors

This project is free and under the WTFPL. Responsible for this project is Markus Malkusch [email protected].

Donations

If you like this project and feel generous donate a few Bitcoins here: 1335STSwu9hST4vcMRppEPgENMHD2r1REK

Build Status

Comments
  • Timeout of 3 seconds exceeded

    Timeout of 3 seconds exceeded

    I get this error report everytime after some problem happened.Then I review the code in "vendor\malkusch\lock"and find some bug for Redis:If u set a lock for the bucket and the client broke,this lock will never be release so that every request will be stoped by the "setnx".I wonder if u have some solution for this bug,and sorry for my English :p by zhangxiaohou

    opened by zhangxiaohou 7
  • MemcachedStorage CASMutex TimeoutException

    MemcachedStorage CASMutex TimeoutException

    The current implementation of MemcachedStorage doesn't work.

    When the consume method is called and "$delta < 0", then $this->storage->getMutex()->synchronized keeps looping the "callable $code". This is because "$this->storage->getMutex()->notify()" is never called when "$delta < 0", so "$this->loop->execute($code)" (CASMutex) keeps running and will reach the timeout "malkusch\lock\exception\TimeoutException" "Timeout of 3 seconds exceeded.".

    The current implementation of MemcachedStorage uses CAS, where the MemcacheStorage (without D) implementation doesn't. I've created a MemcachedStorage implementation without CAS (also using "MemcachedMutex" instead of "CASMutex") and that seems to work fine. What was the reason to use the CAS tokens?

    opened by davyrolink 2
  • BlockingConsumer: timeout parameter?

    BlockingConsumer: timeout parameter?

    Hey. I'm not sure whether a complete deadlock during blocking consumption could somehow occur or not, but isn't it generally healthy to have a failsafe for a blocking operation - such as a configurable timeout setting?

    opened by lkraav 1
  • Is is possible to use SessionScope to throttle requests by IP address?

    Is is possible to use SessionScope to throttle requests by IP address?

    Is it possible to use SessionScope to limit requests by an unique identifier such as their IP address?

    Ideally something that would be able to 'kick' requests if an IP has made over a set allocation within a set time frame, e.g if the limit is 1000 requests per hour, once they make the 1001th request within that window rather than sleep it will just discard the request until they allowed to make requests again in the next window.

    question 
    opened by gkimpson 1
  • Feature request: refund tokens

    Feature request: refund tokens

    Thanks for such a nice pkg! I wonder if there is any method like refund or something that can return the tokens to the bucket.

    I used this feature mainly for trade amount limitation. In such a case:

    // ...
    // consume when an order creating.
    $bucket->consume($order->total);
    $order->create();
    // then order turns to failed.
    $order->failed();
    $bucket->refund($order->total);
    

    For now I implement this feature on my own. But I think that would be better if there is official support :)

    opened by yuchanns 0
  • Support for zendframework/zend-cache

    Support for zendframework/zend-cache

    I've written a zend-cache storage type ZendStorage for a project where I have no direct access to our memcache(d) instance. This allows any storage medium that this package defines. Is this aggregate storage a thing you would like to see in your package? If so I can finish this PR up with some tests.

    opened by villermen 0
  • Disallow inheritance

    Disallow inheritance

    I didn't declare classes as final and people do at least think about inheriting. I don't support that, as I had inheritance never in mind while designing those classes.

    This is a breaking change and will require a major version bump.

    See also: #13

    opened by malkusch 0
  • Implicit -> explicit to int

    Implicit -> explicit to int

    fix Implicit conversion from float to int, by making it explicit.

    Gives depricates warning in Php 8.1 "Implicit incompatible float to int conversion is deprecated." https://www.php.net/releases/8.1/en.php

    opened by puggan 0
  • Fix File Storage not working on Windows

    Fix File Storage not working on Windows

    Opening bucket in bootstrap method causes fwrite to fail with error: write of 8 bytes failed with errno=13 Permission denied

    https://github.com/bandwidth-throttle/token-bucket/issues/21

    opened by emmanuelmahove 0
  • Rate limiting API requests within multiple workers

    Rate limiting API requests within multiple workers

    My use case is :

    • A set of 5 (rabbitmq) workers consume messages, doing one ore more API request per message
    • No more than 10 API requests should be globally (throughout all workers) done for 1 second

    Would this repo implementation be fine with this distributed constraint ? Would this rate of 10 be an absolute limit or an average ?

    opened by pdoreau 1
  • Does it support redis cluster?

    Does it support redis cluster?

    An error happens when I use redis cluster, Uncaught TypeError: Argument 2 passed to bandwidthThrottle\tokenBucket\storage\PHPRedisStorage::__construct() must be an instance of Redis, instance of RedisCluster given

    opened by kenny1206 0
Owner
null
Save items in a bucket, retrieve them later.

Bucket Save items in a bucket, retrieve them later. use Laragear\Bucket\Facades\Buckets; use App\Models\Message; public function send(Message $messag

Laragear 2 Jun 3, 2022
PHP implementation of Rapid Automatic Keyword Exraction algorithm (RAKE) for extracting multi-word phrases from text

PHP implementation of Rapid Automatic Keyword Exraction algorithm (RAKE) for extracting multi-word phrases from text.

Assisted Mindfulness 7 Oct 19, 2022
Ip2region is a offline IP location library with accuracy rate of 99.9% and 0.0x millseconds searching performance. DB file is ONLY a few megabytes with all IP address stored. binding for Java,PHP,C,Python,Nodejs,Golang,C#,lua. Binary,B-tree,Memory searching algorithm

Ip2region是什么? ip2region - 准确率99.9%的离线IP地址定位库,0.0x毫秒级查询,ip2region.db数据库只有数MB,提供了java,php,c,python,nodejs,golang,c#等查询绑定和Binary,B树,内存三种查询算法。 Ip2region特性

Lion 12.6k Dec 30, 2022
XXTEA encryption algorithm library for PHP.

XXTEA for PHP Introduction XXTEA is a fast and secure encryption algorithm. This is a XXTEA library for PHP. It is different from the original XXTEA e

xxtea 107 Dec 8, 2022
Laravel package to generate sweepstakes using the Round Robin algorithm

Laravel package to generate sweepstakes using the Round Robin algorithm. Supports any number of teams, as long as they are greater than a minimum value specified in the configuration file. Built with Laravel Collections for better handling of arrays.

TonyStore 6 Aug 3, 2022
This package implements 0-1 Knapsack Problem algorithm i.e. allows to find the best way to fill a knapsack of a specified volume with items of a certain volume and value.

This package implements "0-1 Knapsack Problem" algorithm i.e. allows to find the best way to fill a knapsack of a specified volume with items of a certain volume and value.

Alexander Makarov 9 Sep 8, 2022
🦭 Kirby, but headless only – KQL with bearer token, Express-esque middlewares & more

Kirby Headless Starter ℹ️ Send a Bearer test authorization header with a request to the live playground to test this headless starter. This starter ki

Johann Schopplich 36 Dec 28, 2022
A pure PHP implementation of the MessagePack serialization format / msgpack.org[PHP]

msgpack.php A pure PHP implementation of the MessagePack serialization format. Features Fully compliant with the latest MessagePack specification, inc

Eugene Leonovich 368 Dec 19, 2022
Php-rpc-server - JSON RPC server implementation for PHP.

JSON RPC Server implementation for PHP. The json-rpc is a very simple protocol. You can see this by reading the protocol specification. This library i

null 4 Sep 28, 2022
A pure PHP implementation of the open Language Server Protocol. Provides static code analysis for PHP for any IDE.

A pure PHP implementation of the open Language Server Protocol. Provides static code analysis for PHP for any IDE.

Felix Becker 1.1k Jan 4, 2023
PHP implementation of circuit breaker pattern.

What is php-circuit-breaker A component helping you gracefully handle outages and timeouts of external services (usually remote, 3rd party services).

ArturEjsmont 169 Jul 28, 2022
A PHP implementation of the Unleash protocol aka Feature Flags in GitLab.

A PHP implementation of the Unleash protocol aka Feature Flags in GitLab. This implementation conforms to the official Unleash standards and implement

Dominik Chrástecký 2 Aug 18, 2021
An implementation of the Minecraft: Bedrock Edition protocol in PHP

BedrockProtocol An implementation of the Minecraft: Bedrock Edition protocol in PHP This library implements all of the packets in the Minecraft: Bedro

PMMP 94 Jan 6, 2023
PHP Implementation of PASERK

PASERK (PHP) Platform Agnostic SERialized Keys. Requires PHP 7.1 or newer. PASERK Specification The PASERK Specification can be found in this reposito

Paragon Initiative Enterprises 9 Nov 22, 2022
A minimalistic implementation of Promises for PHP

libPromise A minimalistic implementation of Promises for PHP. Installation via DEVirion Install the DEVirion plugin and start your server. This will c

null 8 Sep 27, 2022
PHP's Promse implementation depends on the Swoole module.

php-promise-swoole PHP's Promse implementation depends on the Swoole module. Promise::allsettled([ /** Timer 调用 */ /** Timer call */

拓荒者 3 Mar 15, 2022
A circular buffer implementation in PHP

Circular Buffer Installation ?? This is a great place for showing how to install the package, see below: Run $ composer require lctrs/circular-buffer

null 1 Jan 11, 2022
This package contains a PHP implementation to solve 3D bin packing problems.

3D Bin Packager This package contains a PHP implementation to solve 3d bin packing problems based on gedex implementation on Go and enzoruiz implement

Farista Latuconsina 7 Nov 21, 2022
PHP implementation for reading and writing Apache Parquet files/streams

php-parquet This is the first parquet file format reader/writer implementation in PHP, based on the Thrift sources provided by the Apache Foundation.

null 17 Oct 25, 2022