高并发之Qos问题–php与redis应用实验

#2019.03.29

今天研究了下yii2的模块,有个限流,对比来看我的办法有点笨,关键代码如下:

$allowance += (int) (($current - $timestamp) * $limit / $window);
if ($allowance > $limit) {
    $allowance = $limit;
}

说明在此:https://www.yiiframework.com/doc/guide/2.0/zh-cn/rest-rate-limiting

当前允许请求的次数+=请求时间段×限制速率

#end

问题:在动态的一分钟内限定用户操作次数为10次
分析:类Qos问题
php常规做法(YII2框架,相关配置略)

$request_time = time();
$id = (int) Yii::$app->request->get('id');
$k1 = 'v_'.$id;
if($id <= 0)
{
    echo 'No id!';
    return null;
}
$redis = Yii::$app->redis;
//检查队列状态
while ($value = $redis->rpop($k1)) {
    if($diff_time > 60) {
        continue;
    } else {
        $redis->rpush($k1, $value);
        break;
    }
}
//判断队列长度
$len = $redis->llen($k1);
if($len < 10) {
    $redis->lpush($k1, $request_time);
    echo 'OK:'.$len;
} else {
    echo 'Forbidden:'.$len;
}

然后用siege压测

$ cat test.url
http://www.test.com/test/?id=1
http://www.test.com/test/?id=2
http://www.test.com/test/?id=3
http://www.test.com/test/?id=4
http://www.test.com/test/?id=5
http://www.test.com/test/?id=6
http://www.test.com/test/?id=7
http://www.test.com/test/?id=8
http://www.test.com/test/?id=9
http://www.test.com/test/?id=10

siege -f test.url -c 50 -t 2m -r 20 -b

结果问题很大,从id1-10入队数基本在11~13区间,并发问题突显,增大c和r的参数问题并没有放大, 使用incr和decr来判断效果也没有明显改善
分析了redis锁的机制,使用setnx的方式
另外从业务上约定规则,比如1秒内只能执行一次,配合setnx效果明显,压测几次没有发现溢出情况

//初始化id
$id = (int) Yii::$app->request->get('id');
if($id <= 0)
{
    echo 'No id!';
    return null;
}
//记录请示时间
$request_time = microtime(true);
//设置动态时段s
$set_time = 20;
//限定执行次数
$set_num = 10;
//设置锁时为1s;
//
$k1 = 'v_'.$id;
$k2 = 'c_'.$id;
$redis = Yii::$app->redis;
//增加并发锁,设置锁的过期时间
$k3 = 'l_'.$id;
$is_lock = !$redis->setnx($k3, $request_time + 1);
//判断锁是否有效
if($is_lock && ($request_time > $redis->get($k3))) {
    $redis->del($k3);
    $is_lock = !$redis->setnx($k3, $request_time + 1);
}
if($is_lock) {
    echo 'Forbidden:is lock!';
    exit;//结束处理
}
//检查队列状态
$len = $redis->get($k2);
while ($value = $redis->rpop($k1)) {
    //超时判断
    if($request_time - $value > $set_time) {
        $len = $redis->decr($k2);
        continue;
    } else {
        $redis->rpush($k1, $value);
        break;
    }
}
//判断队列长度
//$len = $redis->llen($k1);
//$len = $redis->get($k2);
if($len < $set_num) {
    $redis->lpush($k1, $request_time);
    $redis->incr($k2);
    echo 'OK:'.$len;
} else {
    echo 'Forbidden:'.$len;
}
siege -f test.url -c 100 -t 3m -r 50 -b

Lifting the server siege...
Transactions:		       27100 hits
Availability:		      100.00 %
Elapsed time:		      179.33 secs
Data transferred:	        0.45 MB
Response time:		        0.58 secs
Transaction rate:	      151.12 trans/sec
Throughput:		        0.00 MB/sec
Concurrency:		       87.98
Successful transactions:       27100
Failed transactions:	           0
Longest transaction:	       21.77
Shortest transaction:	        0.00

发表评论

电子邮件地址不会被公开。 必填项已用*标注