这篇文章主要给大家介绍了关于Laravel中GraphQL接口请求频率的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
前言
起源:通常在产品的运行过程,我们可能会做数据埋点,以此来知道用户触发的行为,访问了多少页面,做了哪些操作,来方便产品根据用户喜好的做不同的调整和推荐,同样在服务端开发层面,也要做好“数据埋点”,去记录接口的响应时长、接口调用频率,参数频率等,方便我们从后端角度去分析和优化问题,如果遇到异常行为或者大量攻击来源,我们可以具体针对到某个接口去进行优化。
项目环境:
- framework:laravel 5.8+
- cache : redis >= 2.6.0
目前项目中几乎都使用的是 graphql 接口,采用的 package 是 php lighthouse graphql,那么主要的场景就是去统计好,graphql 接口的请求次数即可。
实现GraphQL Record Middleware
首先建立一个middleware 用于稍后记录接口的请求频率,在这里可以使用artisan 脚手架快速创建:
php artisan make:middleware GraphQLRecord
<?php
namespace App\Http\Middleware;
use Closure;
class GraphQLRecord
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
return $next($request);
}
}
然后添加到 app/config/lighthouse.php middleware 配置中,或后添加到项目中 app/Http/Kernel.php 中,设置为全局中间件
'middleware' => [
\App\Http\Middleware\GraphQLRecord::class,
\Nuwave\Lighthouse\Support\Http\Middleware\AcceptJson::class,
],
获取 GraphQL Operation Name
public function handle($request, Closure $next)
{
$opName = $request->get('operationName');
return $next($request);
}
获取到 Operation Name 之后,开始就通过在Redis 来实现一个接口计数器。
添加接口计数器
首先要设置我们需要记录的时间,如5秒,60秒,半小时、一个小时、5个小时、24小时等,用一个数组来实现,具体可以根据自我需求来调整。
const PRECISION = [5, 60, 1800, 3600, 86400];
然后就开始添加对接口计数的逻辑,计数完成后,我们将其添加到zsset中,方便后续进行数据查询等操作。
/**
* 更新请求计数器
*
* @param string $opName
* @param integer $count
* @return void
*/
public function updateRequestCounter(string $opName, $count = 1)
{
$now = microtime(true);
$redis = self::getRedisConn();
if ($redis) {
$pipe = $redis->pipeline();
foreach (self::PRECISION as $prec) {
//计算时间片
$pnow = intval($now / $prec) * $prec;
//生成一个hash key标识
$hash = "request:counter:{$prec}:$opName";
//增长接口请求数
$pipe->hincrby($hash, $pnow, 1);
// 添加到集合中,方便后续数据查询
$pipe->zadd('request:counter', [$hash => 0]);
}
$pipe->execute();
}
}
/**
* 获取Redis连接
*
* @return object
*/
public static function getRedisConn()
{
$redis = Redis::connection('cache');
try {
$redis->ping();
} catch (Exception $ex) {
$redis = null;
//丢给sentry报告
app('sentry')->captureException($ex);
}
return $redis;
}
然后请求一下接口,用medis查看一下数据。
查询、分析数据
数据记录完善后,可以通过opName 及 prec两个属性来查询,如查询24小时的tag接口访问数据
/**
* 获取接口访问计数
*
* @param string $opName
* @param integer $prec
* @return array
*/
public static function getRequestCounter(string $opName, int $prec)
{
$data = [];
$redis = self::getRedisConn();
if ($redis) {
$hash = "request:counter:{$prec}:$opName";
$hashData = $redis->hgetall($hash);
foreach ($hashData as $k => $v) {
$date = date("Y/m/d", $k);
$data[] = ['timestamp' => $k, 'value' => $v, 'date' => $date];
}
}
return $data;
}
获取 tag 接口 24小时的访问统计
$data = $this->getRequestCounter('tagQuery', '86400');
清除数据
完善一系列步骤后,我们可能需要将过期和一些不必要的数据进行清理,可以通过定时任务来进行定期清理,相关实现如下:
/**
* 清理请求计数
*
* @param integer $clearDay
* @return void
*/
public function clearRequestCounter($clearDay = 7)
{
$index = 0;
$startTime = microtime(true);
$redis = self::getRedisConn();
if ($redis) {
//可以清理的情况下
while ($index < $redis->zcard('request:counter')) {
$hash = $redis->zrange('request:counter', $index, $index);
$index++;
//当前hash存在
if ($hash) {
$hash = $hash[0];
//计算删除截止时间
$cutoff = intval(microtime(true) - ($clearDay * 24 * 60 * 60));
//优先删除时间较远的数据
$samples = array_map('intval', $redis->hkeys($hash));
sort($samples);
//需要删除的数据
$removes = array_filter($samples, function ($item) use (&$cutoff) {
return $item <= $cutoff;
});
if (count($removes)) {
$redis->hdel($hash, ...$removes);
//如果整个数据都过期了的话,就清除掉统计的数据
if (count($removes) == count($samples)) {
$trans = $redis->transaction(['cas' => true]);
try {
$trans->watch($hash);
if (!$trans->hlen($hash)) {
$trans->multi();
$trans->zrem('request:counter', $hash);
$trans->execute();
$index--;
} else {
$trans->unwatch();
}
} catch (\Exception $ex) {
dump($ex);
}
}
}
}
}
dump('清理完成');
}
}
清理一个30天前的数据:
$this->clearRequestCounter(30);
整合代码
我们将所有操作接口统计的代码,单独封装到一个类中,然后对外提供静态函数调用,既实现了职责单一,又方便集成到其他不同的模块使用。
<?php
namespace App\Helpers;
use Illuminate\Support\Facades\Redis;
class RequestCounter
{
const PRECISION = [5, 60, 1800, 3600, 86400];
const REQUEST_COUNTER_CACHE_KEY = 'request:counter';
/**
* 更新请求计数器
*
* @param string $opName
* @param integer $count
* @return void
*/
public static function updateRequestCounter(string $opName, $count = 1)
{
$now = microtime(true);
$redis = self::getRedisConn();
if ($redis) {
$pipe = $redis->pipeline();
foreach (self::PRECISION as $prec) {
//计算时间片
$pnow = intval($now / $prec) * $prec;
//生成一个hash key标识
$hash = self::counterCacheKey($opName, $prec);
//增长接口请求数
$pipe->hincrby($hash, $pnow, 1);
// 添加到集合中,方便后续数据查询
$pipe->zadd(self::REQUEST_COUNTER_CACHE_KEY, [$hash => 0]);
}
$pipe->execute();
}
}
/**
* 获取Redis连接
*
* @return object
*/
public static function getRedisConn()
{
$redis = Redis::connection('cache');
try {
$redis->ping();
} catch (Exception $ex) {
$redis = null;
//丢给sentry报告
app('sentry')->captureException($ex);
}
return $redis;
}
/**
* 获取接口访问计数
*
* @param string $opName
* @param integer $prec
* @return array
*/
public static function getRequestCounter(string $opName, int $prec)
{
$data = [];
$redis = self::getRedisConn();
if ($redis) {
$hash = self::counterCacheKey($opName, $prec);
$hashData = $redis->hgetall($hash);
foreach ($hashData as $k => $v) {
$date = date("Y/m/d", $k);
$data[] = ['timestamp' => $k, 'value' => $v, 'date' => $date];
}
}
return $data;
}
/**
* 清理请求计数
*
* @param integer $clearDay
* @return void
*/
public static function clearRequestCounter($clearDay = 7)
{
$index = 0;
$startTime = microtime(true);
$redis = self::getRedisConn();
if ($redis) {
//可以清理的情况下
while ($index < $redis->zcard(self::REQUEST_COUNTER_CACHE_KEY)) {
$hash = $redis->zrange(self::REQUEST_COUNTER_CACHE_KEY, $index, $index);
$index++;
//当前hash存在
if ($hash) {
$hash = $hash[0];
//计算删除截止时间
$cutoff = intval(microtime(true) - ($clearDay * 24 * 60 * 60));
//优先删除时间较远的数据
$samples = array_map('intval', $redis->hkeys($hash));
sort($samples);
//需要删除的数据
$removes = array_filter($samples, function ($item) use (&$cutoff) {
return $item <= $cutoff;
});
if (count($removes)) {
$redis->hdel($hash, ...$removes);
//如果整个数据都过期了的话,就清除掉统计的数据
if (count($removes) == count($samples)) {
$trans = $redis->transaction(['cas' => true]);
try {
$trans->watch($hash);
if (!$trans->hlen($hash)) {
$trans->multi();
$trans->zrem(self::REQUEST_COUNTER_CACHE_KEY, $hash);
$trans->execute();
$index--;
} else {
$trans->unwatch();
}
} catch (\Exception $ex) {
dump($ex);
}
}
}
}
}
dump('清理完成');
}
}
public static function counterCacheKey($opName, $prec)
{
$key = "request:counter:{$prec}:$opName";
return $key;
}
}
在Middleware中使用.
<?php
namespace App\Http\Middleware;
use App\Helpers\RequestCounter;
use Closure;
class GraphQLRecord
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$opName = $request->get('operationName');
if (!empty($opName)) {
RequestCounter::updateRequestCounter($opName);
}
return $next($request);
}
}
结尾
上诉代码就实现了基于GraphQL的请求频率记录,但是使用不止适用于GraphQL接口,也可以基于Rest接口、模块计数等统计行为,只要有唯一的operation name即可。
到此这篇关于Laravel中GraphQL接口请求频率的文章就介绍到这了,更多相关Laravel中GraphQL接口请求频率内容请搜索编程学习网以前的文章希望大家以后多多支持编程学习网!
本文标题为:Laravel中GraphQL接口请求频率实战记录
基础教程推荐
- laravel ORM关联关系中的 with和whereHas用法 2023-03-02
- PHP命名空间简单用法示例 2022-12-01
- thinkphp3.2.3框架动态切换多数据库的方法分析 2023-03-19
- php array分组,PHP中array数组的分组排序 2022-08-01
- PHP实现Redis单据锁以及防止并发重复写入 2022-10-12
- PHP中的错误及其处理机制 2023-06-04
- 在Laravel中实现使用AJAX动态刷新部分页面 2023-03-02
- 使用PHP开发留言板功能 2023-03-13
- laravel 解决多库下的DB::transaction()事务失效问题 2023-03-08
- PHP获取MySQL执行sql语句的查询时间方法 2022-11-09