基于Laravel 请求频次监控器

监控/限制请求频次

  • Support\RaitMonitor
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
<?php

namespace App\Support;

use Monolog\Logger;

/**
* 监控API调用频次
* Class RateMonitor
* @package App\Support
*/
class RateMonitor
{
const KEY_NAME = 'rate_monitor';
const KEY_ALERT_COUNT = 'rate_alert';
const IN_SECOND = '1s';
const IN_MINUTE = '1m';
const IN_MINUTE_TEN = '10m';
const IN_HOUR = '1h';
const IN_DAY = '1d';
const DING_ROBOT = 'https://oapi.dingtalk.com/robot/send?access_token=5187cd15497ab1f440cb92c49eaf29e6477ec8786251ab00133d8a89b0ee7450';

const HASH_KEYS_EXPIRE = [
self::IN_SECOND => 1,
self::IN_MINUTE => 60,
self::IN_HOUR => 3600,
self::IN_DAY => 86400,
];

/**
* @var \Redis
*/
protected $redis;
/**
* @var Logger
*/
protected $logger;

/**
* RateMonitor constructor.
* @throws \Exception
*/
public function __construct()
{
$this->logger = LogTool::getLogger('monitor', Logger::INFO, 'rate-monitor.log');
$this->redis = RedisTool::getInstance();
}

/**
* @param string $name 被调用路由
* @param string $item IP,用户ID,帐号或者null
* @param array $limit 各种阀值(秒,分,时,日),比如 [RateMonitor::IN_SECOND => 10,]
* @param callable|null $on_overflow 调用频率超限后的回调函数,默认null则直接退出。
*/
public function access(string $name, string $item, array $limit, callable $on_overflow = null)
{
$value = [];
if (empty($item)) {
$item = 'call';
}

foreach (self::HASH_KEYS_EXPIRE as $key => $expire) {
$hash_key = self::KEY_NAME . "/$name/$item/$key";
$value[$key] = $this->redis->incr($hash_key);
if (1 == $value[$key]) {
$this->redis->expire($hash_key, $expire);
}
if (array_key_exists($key, $limit) && $limit[$key] < $value[$key]) {
$this->logger->warn("$hash_key $value[$key] > $limit[$key]");
$alert_count = $this->redis->incr(self::KEY_ALERT_COUNT);
if (1 == $alert_count) {
$this->redis->expire(self::KEY_ALERT_COUNT, 600);
}
if ($alert_count % 20 == 1 && $alert_count > 1) {
DingRobot::textMessage(self::DING_ROBOT, "10分钟内已经超频调用 $alert_count 次。");
}
if ($on_overflow) {
$on_overflow($value);
} else {
$this->responseAndExit();
}
}
}
}

/**
* 响应信息并退出
*/
private function responseAndExit()
{
$msg = array(
'err' => 4000,
'msg' => '数据请求过于频繁, 请稍后再试',
'dat' => null,
);
echo json_encode($msg);
exit;
}

/**
* @return RateMonitor|null
* @throws \Exception
*/
public static function getInstance()
{
static $instance = null;
if (null == $instance) {
$instance = new static();
}
return $instance;
}
}
  • config/rate_monitor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php

use App\Support\RateMonitor;

return [
'rate_monitor' => [
'test_rate' => [
RateMonitor::IN_SECOND => 1, // 每秒种1次
RateMonitor::IN_MINUTE => 5, // 每分钟5次
RateMonitor::IN_HOUR => 10, // 每小时10次
RateMonitor::IN_DAY => 60, // 每天60次
],
]
];
  • test_rate_monitor
1
2
3
4
5
6
7
8
9
/**
* @param Request $request
* @param RateMonitor $rate_monitor
* @Post("/test_rate")
*/
public function test_rate_monitor(Request $request, RateMonitor $rate_monitor)
{
$rate_monitor->access('test_rate', $request->ip(), config('rate_monitor.rate_monitor')['test_rate']);
}

附录

Powered by Hexo and Hexo-theme-hiker

Copyright © 2017 - 2022 Keep It Simple And Stupid All Rights Reserved.

访客数 : | 访问量 :