温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Laravel中GraphQL接口请求频率案例讲解

发布时间:2021-03-03 17:39:12 来源:亿速云 阅读:245 作者:TREX 栏目:开发技术

这篇文章主要介绍“Laravel中GraphQL接口请求频率案例讲解”,在日常操作中,相信很多人在Laravel中GraphQL接口请求频率案例讲解问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”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查看一下数据。

Laravel中GraphQL接口请求频率案例讲解

Laravel中GraphQL接口请求频率案例讲解

查询、分析数据

数据记录完善后,可以通过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接口请求频率案例讲解”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI