Current limiting scheme of PHP+laravel high parallel downlink interface

1, What is interface current limiting
So what is current limiting? As the name suggests, current limiting is to limit traffic, including concurrent traffic and total traffic within a certain period of time. Just like your broadband package has 1 G of traffic, it will disappear when it is used up, so control your use frequency and the total consumption of a single use.

Through current limiting, we can well control the qps of the system, so as to protect the stability of the system or interface server.

2, Common interface current limiting algorithms
1. Counter method
2. Leaky bucket algorithm
3. Token bucket algorithm

Today, token bucket algorithm is mainly used for interface current limiting (based on laravel framework)

First, we have a bucket with a fixed capacity in which tokens are stored. The bucket is empty at first (the number of available tokens is 0). Tokens are filled into the bucket at a fixed rate r until the capacity of the bucket is reached, and the excess tokens will be discarded. Every time a request comes, it will try to remove a token from the bucket. If there is no token, the request cannot pass.

Another advantage of the token bucket is that the speed can be easily changed. Once the rate needs to be increased, the rate of tokens put into the bucket will be increased as needed. Generally, a certain number of tokens will be added to the bucket regularly (such as 100ms). Some variant algorithms calculate the number of tokens that should be added in real time

Code implementation:
1. Add tokens to the token bucket at a constant speed through timed tasks

<?php

namespace App\Console\Commands;

use App\Http\Controllers\Api\V1\TokenBucketController;
use Illuminate\Console\Command;


class CrontabAddTokens extends Command
{
    /**
     * Add tokens to the token bucket regularly
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'CrontabTokens:add';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Automatically increase the number of tokens regularly';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {

        // Token bucket container
        $queue = 'mycontainer';

        // Maximum number of tokens
        $max = 100;

        // Number of tokens added per time interval
        $token_num = 10;

        // The time interval, preferably a number divisible by 60, is guaranteed to cover all the time in each minute
        $time_step = 1;

        // Number of executions
        $exec_num = (int)(60/$time_step);

        // Create TokenBucketController object
        $tokenBucket = new TokenBucketController($queue, $max);

        for($i = 0; $i < $exec_num; $i++){
            $add_num = $tokenBucket->add($token_num);
            echo '['.date('Y-m-d H:i:s').'] add token num:'.$add_num.PHP_EOL;
            sleep($time_step);
        }

    }


}

2. Create a TokenBucketController object, and users can add and consume tokens and some initialization settings

<?php
namespace App\Http\Controllers\Api\V1;


use Illuminate\Support\Facades\Redis;


class TokenBucketController extends BaseController
{

    private $_queue;  // Token bucket
    private $_max;    // Maximum number of tokens

    /**
     * initialization
     * @param Array $config redis Connection settings
     */
    public function __construct($queue, $max = 10){

        $this->_queue = $queue;
        $this->_max = $max;
    }

    /**
     * Join token
     * @param  Int $num Number of tokens added
     * @return Int Quantity added
     */
    public function add($num=0)
    {

        // Current number of tokens remaining
        $curnum = intval(Redis::Llen($this->_queue));


        // Maximum number of tokens
        $maxnum = intval($this->_max);

        // Calculate the maximum number of tokens that can be added, which cannot exceed the maximum number of tokens
        $num = $maxnum >= $curnum + $num ? $num : $maxnum - $curnum;

        // Join token
        if($num>0){
            $token = array_fill(0, $num, 1);
            Redis::lPush($this->_queue, ...$token);
            return $num;
        }

        return 0;

    }

    /**
     * Get token
     * @return Boolean
     */
    public function get(){
        return Redis::rPop($this->_queue)? true : false;
    }

    /**
     * Reset the token bucket to fill the token
     */
    public function reset(){
        Redis::del($this->_queue);
        $this->add($this->_max);
    }

}

3. Impersonate user request consumption token

<?php
namespace App\Http\Controllers\Api\V1;


use App\Component\ApiReturn;
use App\Component\Logic\GoodsLogic;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Response;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;


class TokenConsumeController extends BaseController
{

    public function consume()
    {
		$mycontainer = 'mycontainer';
        //Get the current token bucket status, whether to get the token
        $status = $this->getMycontainerStatus($mycontainer );
        if($status){
            //If the token is obtained, the data is queried
            $query = (new GoodsLogic())->getInfo(['id' => 1]);
            return ApiReturn::success($query);
        }else{
            //If no token is obtained, the service is busy
            return Response::json([
                'msg' => 'The server is busy. Please try again later'
            ], 500); // Status code here
        }


    }
    
    /**
     * Get token bucket token status
     * @param string $queue
     * @return bool
     */
    public function getMycontainerStatus($queue = 'mycontainer')
    {
        // Token bucket container
//        $queue = 'mycontainer';
        // Create TokenBucketController object
        $tokenBucket = new TokenBucketController($queue);

        $status = $tokenBucket->get();
        return $status;
    }


}

Keywords: PHP Laravel Redis

Added by andycole on Wed, 10 Nov 2021 11:35:02 +0200