AtomicLimit
EasySwoole
提供了一個基于 Atomic 計數器的限流器。
原理
通過限制某一個時間周期內的總請求數,從而實現基礎限流。舉個例子,設置5秒內,允許的最大請求量為200,那么理論平均并發為40,峰值并發為200。
組件要求
- php: >= 7.1.0
- easyswoole/component: ^2.0
安裝方法
composer require easyswoole/atomic-limit
倉庫地址
在 EasySwoole 中使用
首先在 EasySwoole
全局的 mainServerCreate
事件(即項目根目錄的 EasySwooleEvent.php
的 mainServerCreate
函數) 中,進行限流器注冊
<?php
/**
* This file is part of EasySwoole.
*
* @link http://www.fe88.cn
* @document http://www.fe88.cn
* @contact http://www.fe88.cn/Preface/contact.html
* @license https://github.com/easy-swoole/easyswoole/blob/3.x/LICENSE
*/
namespace EasySwoole\EasySwoole;
use EasySwoole\AtomicLimit\AtomicLimit;
use EasySwoole\Component\Di;
use EasySwoole\EasySwoole\AbstractInterface\Event;
use EasySwoole\EasySwoole\Swoole\EventRegister;
class EasySwooleEvent implements Event
{
public static function initialize()
{
date_default_timezone_set('Asia/Shanghai');
}
public static function mainServerCreate(EventRegister $register)
{
###### 配置限流器 ######
$limit = new AtomicLimit();
/** 為方便測試,(全局的)限制設置為 10 */
$limit->setLimitQps(10);
$limit->attachServer(ServerManager::getInstance()->getSwooleServer());
Di::getInstance()->set('auto_limiter', $limit);
}
}
在 App\HttpController\Index.php
中調用限流器:
<?php
/**
* This file is part of EasySwoole.
*
* @link http://www.fe88.cn
* @document http://www.fe88.cn
* @contact http://www.fe88.cn/Preface/contact.html
* @license https://github.com/easy-swoole/easyswoole/blob/3.x/LICENSE
*/
namespace App\HttpController;
use EasySwoole\AtomicLimit\AtomicLimit;
use EasySwoole\Component\Di;
use EasySwoole\Http\AbstractInterface\Controller;
class Index extends Controller
{
/** @var AtomicLimit $autoLimiter */
private $autoLimiter;
protected function onRequest(?string $action): ?bool
{
$this->autoLimiter = Di::getInstance()->get('auto_limiter');
if ($action == 'test1') {
# 調用限流器對 http://127.0.0.1:9501/test1 請求限制流量
if ($this->autoLimiter->access($action, 1)) {
return true;
} else {
$this->writeJson(200, null, 'test1 refuse!');
return false;
}
} else if ($action == 'test2') {
# 調用限流器對 http://127.0.0.1:9501/test2 請求限制流量
if ($this->autoLimiter->access($action, 2)) {
return true;
} else {
$this->writeJson(200, null, 'test2 refuse!');
return false;
}
}
return parent::onRequest($action);
}
public function test1()
{
$this->writeJson(200, null, 'test1 success!');
}
public function test2()
{
$this->writeJson(200, null, 'test2 success!');
}
}
以上代碼表示,index/test1
這個限流器在每秒內允許的最大流量為 1
,而 index/test2
這個限流器的最大流量為 2
。
我們也可以在 EasySwoole
的 Base
控制器的 onRequest
方法中,進行請求攔截。例如在全局 onRequest
事件中,先進行流量檢驗,如果校驗通過,則進行下一步操作。
在 Swoole 中使用
以經典的暴力 CC
攻擊防護為例子。我們可以限制一個 ip-url
的 qps
訪問。
<?php
/**
* This file is part of EasySwoole.
*
* @link http://www.fe88.cn
* @document http://www.fe88.cn
* @contact http://www.fe88.cn/Preface/contact.html
* @license https://github.com/easy-swoole/easyswoole/blob/3.x/LICENSE
*/
// example url: http://127.0.0.1:9501/index.html?api=1
require_once __DIR__ . '/vendor/autoload.php';
use EasySwoole\AtomicLimit\AtomicLimit;
$http = new swoole_http_server("127.0.0.1", 9501);
###### 配置限流器 ######
$limit = new AtomicLimit();
/** 為方便測試,(全局的)限制設置為3 */
$limit->setLimitQps(3);
$limit->attachServer($http);
$http->on("request", function ($request, $response) use ($http, $limit) {
$ip = $http->getClientInfo($request->fd)['remote_ip'];
$requestUri = $request->server['request_uri'];
$token = $ip . $requestUri;
/** access 函數允許單獨對某個 token 指定qps */
if ($limit->access($token)) {
$response->write('request accept');
} else {
$response->write('request refuse');
}
$response->end();
});
$http->start();
注意,本例子是用一個自定義進程內加定時器來實現計數定時重置,實際上用一個進程來做這件事情有點不值得,因此實際生產可以指定一個 worker
,設置定時器來實現。