Laravel中GraphQL接口请求频率实战记录
前言
起源:通常在产品的运行过程,我们可能会做数据埋点,以此来知道用户触发的行为,访问了多少页面,做了哪些操作,来方便产品根据用户喜好的做不同的调整和推荐,同样在服务端开发层面,也要做好“数据埋点”,去记录接口的响应时长、接口调用频率,参数频率等,方便我们从后端角度去分析和优化问题,如果遇到异常行为或者大量攻击来源,我们可以具体针对到某个接口去进行优化。
项目环境:
- framework:laravel5.8+
- cache:redis>=2.6.0
目前项目中几乎都使用的是graphql接口,采用的package是phplighthousegraphql,那么主要的场景就是去统计好,graphql接口的请求次数即可。
实现GraphQLRecordMiddleware
首先建立一个middleware用于稍后记录接口的请求频率,在这里可以使用artisan脚手架快速创建:
phpartisanmake:middlewareGraphQLRecord
然后添加到app/config/lighthouse.phpmiddleware配置中,或后添加到项目中app/Http/Kernel.php中,设置为全局中间件
'middleware'=>[ \App\Http\Middleware\GraphQLRecord::class, \Nuwave\Lighthouse\Support\Http\Middleware\AcceptJson::class, ],获取GraphQLOperationName
publicfunctionhandle($request,Closure$next) { $opName=$request->get('operationName'); return$next($request); }获取到OperationName之后,开始就通过在Redis来实现一个接口计数器。
添加接口计数器
首先要设置我们需要记录的时间,如5秒,60秒,半小时、一个小时、5个小时、24小时等,用一个数组来实现,具体可以根据自我需求来调整。
constPRECISION=[5,60,1800,3600,86400];然后就开始添加对接口计数的逻辑,计数完成后,我们将其添加到zsset中,方便后续进行数据查询等操作。
/** *更新请求计数器 * *@paramstring$opName *@paraminteger$count *@returnvoid */ publicfunctionupdateRequestCounter(string$opName,$count=1) { $now=microtime(true); $redis=self::getRedisConn(); if($redis){ $pipe=$redis->pipeline(); foreach(self::PRECISIONas$prec){ //计算时间片 $pnow=intval($now/$prec)*$prec; //生成一个hashkey标识 $hash="request:counter:{$prec}:$opName"; //增长接口请求数 $pipe->hincrby($hash,$pnow,1); //添加到集合中,方便后续数据查询 $pipe->zadd('request:counter',[$hash=>0]); } $pipe->execute(); } } /** *获取Redis连接 * *@returnobject */ publicstaticfunctiongetRedisConn() { $redis=Redis::connection('cache'); try{ $redis->ping(); }catch(Exception$ex){ $redis=null; //丢给sentry报告 app('sentry')->captureException($ex); } return$redis; }然后请求一下接口,用medis查看一下数据。
查询、分析数据
数据记录完善后,可以通过opName及prec两个属性来查询,如查询24小时的tag接口访问数据
/** *获取接口访问计数 * *@paramstring$opName *@paraminteger$prec *@returnarray */ publicstaticfunctiongetRequestCounter(string$opName,int$prec) { $data=[]; $redis=self::getRedisConn(); if($redis){ $hash="request:counter:{$prec}:$opName"; $hashData=$redis->hgetall($hash); foreach($hashDataas$k=>$v){ $date=date("Y/m/d",$k); $data[]=['timestamp'=>$k,'value'=>$v,'date'=>$date]; } } return$data; }获取tag接口24小时的访问统计
$data=$this->getRequestCounter('tagQuery','86400');清除数据
完善一系列步骤后,我们可能需要将过期和一些不必要的数据进行清理,可以通过定时任务来进行定期清理,相关实现如下:
/** *清理请求计数 * *@paraminteger$clearDay *@returnvoid */ publicfunctionclearRequestCounter($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);整合代码
我们将所有操作接口统计的代码,单独封装到一个类中,然后对外提供静态函数调用,既实现了职责单一,又方便集成到其他不同的模块使用。
pipeline(); foreach(self::PRECISIONas$prec){ //计算时间片 $pnow=intval($now/$prec)*$prec; //生成一个hashkey标识 $hash=self::counterCacheKey($opName,$prec); //增长接口请求数 $pipe->hincrby($hash,$pnow,1); //添加到集合中,方便后续数据查询 $pipe->zadd(self::REQUEST_COUNTER_CACHE_KEY,[$hash=>0]); } $pipe->execute(); } } /** *获取Redis连接 * *@returnobject */ publicstaticfunctiongetRedisConn() { $redis=Redis::connection('cache'); try{ $redis->ping(); }catch(Exception$ex){ $redis=null; //丢给sentry报告 app('sentry')->captureException($ex); } return$redis; } /** *获取接口访问计数 * *@paramstring$opName *@paraminteger$prec *@returnarray */ publicstaticfunctiongetRequestCounter(string$opName,int$prec) { $data=[]; $redis=self::getRedisConn(); if($redis){ $hash=self::counterCacheKey($opName,$prec); $hashData=$redis->hgetall($hash); foreach($hashDataas$k=>$v){ $date=date("Y/m/d",$k); $data[]=['timestamp'=>$k,'value'=>$v,'date'=>$date]; } } return$data; } /** *清理请求计数 * *@paraminteger$clearDay *@returnvoid */ publicstaticfunctionclearRequestCounter($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('清理完成'); } } publicstaticfunctioncounterCacheKey($opName,$prec) { $key="request:counter:{$prec}:$opName"; return$key; } }在Middleware中使用.
get('operationName'); if(!empty($opName)){ RequestCounter::updateRequestCounter($opName); } return$next($request); } }结尾
上诉代码就实现了基于GraphQL的请求频率记录,但是使用不止适用于GraphQL接口,也可以基于Rest接口、模块计数等统计行为,只要有唯一的operationname即可。
到此这篇关于Laravel中GraphQL接口请求频率的文章就介绍到这了,更多相关Laravel中GraphQL接口请求频率内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。