# 1.计数器的限流器
<?php
class AtomicLimit
{
public function get_client_ip($type = 0) {
$type = $type ? 1 : 0;
static $ip = NULL;
if ($ip !== NULL) return $ip[$type];
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$pos = array_search('unknown',$arr);
if(false !== $pos) unset($arr[$pos]);
$ip = trim($arr[0]);
}elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
}elseif (isset($_SERVER['REMOTE_ADDR'])) {
$ip = $_SERVER['REMOTE_ADDR'];
}
// IP地址合法验证
$long = ip2long($ip);
$ip = $long ? array($ip, $long) : array('0.0.0.0', 0);
return $ip[$type];
}
public function atomic()
{
//接口时间限流,这种方式可以防止钻时间漏洞无限的访问接口 比如在59秒的时候访问,就钻了空子
$redis = new Redis();
$redis->connect('121.42.136.46', 16379);
$redis->auth("L520g521");
$ip = $this->get_client_ip(true);
$len = $redis->lLen($ip);
if($len === 0)
{
$redis->lPush($ip,time());
echo "访问1次<br>";
$redis->expire($ip,10);
}else{
//判断有没有超过1分钟
$max_time = $redis->lRange($ip,0,0);
//判断最后一次访问的时间比对是否超过了1分钟
if((time()- $max_time[0]) < 60){
if($len> 10){
echo '访问超过了限制';
}else{
$redis->lPush($ip,time());
echo "访问{$len}次<br>";
}
}
}
}
}
(new AtomicLimit())->atomic();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# 2.令牌桶算法
<?php
namespace Api\Lib;
/**
* 限流控制
*/
class RateLimit
{
private $minNum = 60; //单个用户每分访问数
private $dayNum = 10000; //单个用户每天总的访问量
public function minLimit($uid)
{
$minNumKey = $uid . '_minNum';
$dayNumKey = $uid . '_dayNum';
$resMin = $this->getRedis($minNumKey, $this->minNum, 60);
$resDay = $this->getRedis($minNumKey, $this->minNum, 86400);
if (!$resMin['status'] || !$resDay['status']) {
exit($resMin['msg'] . $resDay['msg']);
}
}
public function getRedis($key, $initNum, $expire)
{
$nowtime = time();
$result = ['status' => true, 'msg' => ''];
$redisObj = $this->di->get('redis');
$redis->watch($key);
$limitVal = $redis->get($key);
if ($limitVal) {
$limitVal = json_decode($limitVal, true);
$newNum = min($initNum, ($limitVal['num'] - 1) + (($initNum / $expire) * ($nowtime - $limitVal['time'])));
if ($newNum > 0) {
$redisVal = json_encode(['num' => $newNum, 'time' => time()]);
} else {
return ['status' => false, 'msg' => '当前时刻令牌消耗完!'];
}
} else {
$redisVal = json_encode(['num' => $initNum, 'time' => time()]);
}
$redis->multi();
$redis->set($key, $redisVal);
$rob_result = $redis->exec();
if (!$rob_result) {
$result = ['status' => false, 'msg' => '访问频次过多!'];
}
return $result;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# 3.滑动窗口限流
/**
* @param string $key key
* @param num $limitNum 限制数
* @param int $ttl 过期时间
* @return mixed|null
*/
public static function apiRedisLimit($key, $limitNum, $ttl = 5)
{
return RedisPool::invoke(function (\EasySwoole\Redis\Redis $redis) use ($key, $limitNum, $ttl) {
$score = time();
$nonce = uniqid() . (10000 * microtime(true));
$redis->multi();
$redis->zRemRangeByScore($key, 0, $score - $ttl);
$redis->zAdd($key, $score, $nonce);
$redis->expire($key, $ttl);
$redis->zCount($key, 0, $score);
$rt = $redis->exec();
if (empty($rt[3])) {
return true;
}
if ($rt[3] > $limitNum) {
return false;
}
return true;
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 4.lua限流
--[[
KEYS:
- redisKey
ARGV:
- count 申请的数量
- rateCount 限流数量
- rateTime 限流时间
- time 当前时间
--]]
local function addToQueue(x,time)
local count=0
for i=1,x,1 do
redis.call('lpush',KEYS[1],time)
count=count+1
end
return count
end
local result=0
local timeBase = redis.call('lindex',KEYS[1], tonumber(ARGV[2])-tonumber(ARGV[1]))
if (timeBase == false) or (tonumber(ARGV[4]) - tonumber(timeBase)>tonumber(ARGV[3])) then
result=result+addToQueue(tonumber(ARGV[1]),tonumber(ARGV[4]))
end
if (timeBase~=false) then
redis.call('ltrim',KEYS[1],0,tonumber(ARGV[2]))
end
return result
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* redis+lua限流
*
* @param $key
* @param $limitNum
* @param int $ttl
* @return mixed|null
*/
public static function apiRateLimitByLua($key, $limitNum, $ttl = 5)
{
return RedisPool::invoke(function (\EasySwoole\Redis\Redis $redis) use ($key, $limitNum, $ttl) {
$lua = <<<SCRIPT
redis.call('zAdd',KEYS[1],tonumber(ARGV[2]),ARGV[3])
redis.call('zRemRangeByScore',KEYS[1],0,tonumber(ARGV[2])-tonumber(ARGV[1]))
redis.call('expire',KEYS[1],tonumber(ARGV[1]))
local num = redis.call('zCount',KEYS[1],0,tonumber(ARGV[2]))
if num > tonumber(ARGV[4]) then
return 1
else
return 0
end
SCRIPT;
$score = time();
$nonce = uniqid() . (10000 * microtime(true));
$rt = $redis->eval($lua, 1, $key, $ttl, $score, $nonce,$limitNum);
if ($rt === 1) {
return false;
}
return true;
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
关于限流器的话,这是通过应用层去实现的,一般的情况都是通过网关层面去做的,做统一的网关入口,网关去做限流,日志,权限等。如:nginx
,apisix
,阿里网关
等。
← 分布式锁 redis-shake工具 →