主頁(yè) > 知識(shí)庫(kù) > Laravel中GraphQL接口請(qǐng)求頻率實(shí)戰(zhàn)記錄

Laravel中GraphQL接口請(qǐng)求頻率實(shí)戰(zhàn)記錄

熱門(mén)標(biāo)簽:gps 地圖標(biāo)注軟件 電銷(xiāo)機(jī)器人便宜的有嗎 地圖標(biāo)注視頻廣告入駐 招標(biāo)自動(dòng)語(yǔ)音外呼系統(tǒng) 中原區(qū)電話機(jī)器人價(jià)格 400電話鄭州申請(qǐng) 黔江400電話如何辦理 ai電話機(jī)器人加盟代理 OMG地圖標(biāo)注app

前言

起源:通常在產(chǎn)品的運(yùn)行過(guò)程,我們可能會(huì)做數(shù)據(jù)埋點(diǎn),以此來(lái)知道用戶觸發(fā)的行為,訪問(wèn)了多少頁(yè)面,做了哪些操作,來(lái)方便產(chǎn)品根據(jù)用戶喜好的做不同的調(diào)整和推薦,同樣在服務(wù)端開(kāi)發(fā)層面,也要做好“數(shù)據(jù)埋點(diǎn)”,去記錄接口的響應(yīng)時(shí)長(zhǎng)、接口調(diào)用頻率,參數(shù)頻率等,方便我們從后端角度去分析和優(yōu)化問(wèn)題,如果遇到異常行為或者大量攻擊來(lái)源,我們可以具體針對(duì)到某個(gè)接口去進(jìn)行優(yōu)化。

項(xiàng)目環(huán)境:

  • framework:laravel 5.8+
  • cache : redis >= 2.6.0

目前項(xiàng)目中幾乎都使用的是 graphql 接口,采用的 package 是 php lighthouse graphql,那么主要的場(chǎng)景就是去統(tǒng)計(jì)好,graphql 接口的請(qǐng)求次數(shù)即可。

實(shí)現(xiàn)GraphQL Record Middleware

首先建立一個(gè)middleware 用于稍后記錄接口的請(qǐng)求頻率,在這里可以使用artisan 腳手架快速創(chuàng)建:

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 配置中,或后添加到項(xiàng)目中 app/Http/Kernel.php 中,設(shè)置為全局中間件

'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 之后,開(kāi)始就通過(guò)在Redis 來(lái)實(shí)現(xiàn)一個(gè)接口計(jì)數(shù)器。

添加接口計(jì)數(shù)器

首先要設(shè)置我們需要記錄的時(shí)間,如5秒,60秒,半小時(shí)、一個(gè)小時(shí)、5個(gè)小時(shí)、24小時(shí)等,用一個(gè)數(shù)組來(lái)實(shí)現(xiàn),具體可以根據(jù)自我需求來(lái)調(diào)整。

const PRECISION = [5, 60, 1800, 3600, 86400];

然后就開(kāi)始添加對(duì)接口計(jì)數(shù)的邏輯,計(jì)數(shù)完成后,我們將其添加到zsset中,方便后續(xù)進(jìn)行數(shù)據(jù)查詢等操作。

  /**
   * 更新請(qǐng)求計(jì)數(shù)器
   *
   * @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) {
        //計(jì)算時(shí)間片
        $pnow = intval($now / $prec) * $prec;
        //生成一個(gè)hash key標(biāo)識(shí)
        $hash = "request:counter:{$prec}:$opName";
        //增長(zhǎng)接口請(qǐng)求數(shù)
        $pipe->hincrby($hash, $pnow, 1);
        // 添加到集合中,方便后續(xù)數(shù)據(jù)查詢
        $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報(bào)告
      app('sentry')->captureException($ex);
    }

    return $redis;
  }

然后請(qǐng)求一下接口,用medis查看一下數(shù)據(jù)。

查詢、分析數(shù)據(jù)

數(shù)據(jù)記錄完善后,可以通過(guò)opName 及 prec兩個(gè)屬性來(lái)查詢,如查詢24小時(shí)的tag接口訪問(wèn)數(shù)據(jù)

  /**
   * 獲取接口訪問(wèn)計(jì)數(shù)
   *
   * @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小時(shí)的訪問(wèn)統(tǒng)計(jì)

$data = $this->getRequestCounter('tagQuery', '86400');

清除數(shù)據(jù)

完善一系列步驟后,我們可能需要將過(guò)期和一些不必要的數(shù)據(jù)進(jìn)行清理,可以通過(guò)定時(shí)任務(wù)來(lái)進(jìn)行定期清理,相關(guān)實(shí)現(xiàn)如下:

/**
   * 清理請(qǐng)求計(jì)數(shù)
   *
   * @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++;

        //當(dāng)前hash存在
        if ($hash) {
          $hash = $hash[0];
          //計(jì)算刪除截止時(shí)間
          $cutoff = intval(microtime(true) - ($clearDay * 24 * 60 * 60));

          //優(yōu)先刪除時(shí)間較遠(yuǎn)的數(shù)據(jù)
          $samples = array_map('intval', $redis->hkeys($hash));
          sort($samples);

          //需要?jiǎng)h除的數(shù)據(jù)
          $removes = array_filter($samples, function ($item) use ($cutoff) {
            return $item = $cutoff;
          });
          if (count($removes)) {
            $redis->hdel($hash, ...$removes);
            //如果整個(gè)數(shù)據(jù)都過(guò)期了的話,就清除掉統(tǒng)計(jì)的數(shù)據(jù)
            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('清理完成');
    }

  }

清理一個(gè)30天前的數(shù)據(jù):

$this->clearRequestCounter(30);

整合代碼

我們將所有操作接口統(tǒng)計(jì)的代碼,單獨(dú)封裝到一個(gè)類(lèi)中,然后對(duì)外提供靜態(tài)函數(shù)調(diào)用,既實(shí)現(xiàn)了職責(zé)單一,又方便集成到其他不同的模塊使用。

?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';

  /**
   * 更新請(qǐng)求計(jì)數(shù)器
   *
   * @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) {
        //計(jì)算時(shí)間片
        $pnow = intval($now / $prec) * $prec;
        //生成一個(gè)hash key標(biāo)識(shí)
        $hash = self::counterCacheKey($opName, $prec);
        //增長(zhǎng)接口請(qǐng)求數(shù)
        $pipe->hincrby($hash, $pnow, 1);
        // 添加到集合中,方便后續(xù)數(shù)據(jù)查詢
        $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報(bào)告
      app('sentry')->captureException($ex);
    }

    return $redis;
  }

  /**
   * 獲取接口訪問(wèn)計(jì)數(shù)
   *
   * @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;
  }

  /**
   * 清理請(qǐng)求計(jì)數(shù)
   *
   * @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++;

        //當(dāng)前hash存在
        if ($hash) {
          $hash = $hash[0];
          //計(jì)算刪除截止時(shí)間
          $cutoff = intval(microtime(true) - ($clearDay * 24 * 60 * 60));

          //優(yōu)先刪除時(shí)間較遠(yuǎn)的數(shù)據(jù)
          $samples = array_map('intval', $redis->hkeys($hash));
          sort($samples);

          //需要?jiǎng)h除的數(shù)據(jù)
          $removes = array_filter($samples, function ($item) use ($cutoff) {
            return $item = $cutoff;
          });
          if (count($removes)) {
            $redis->hdel($hash, ...$removes);
            //如果整個(gè)數(shù)據(jù)都過(guò)期了的話,就清除掉統(tǒng)計(jì)的數(shù)據(jù)
            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);
  }
}

結(jié)尾

上訴代碼就實(shí)現(xiàn)了基于GraphQL的請(qǐng)求頻率記錄,但是使用不止適用于GraphQL接口,也可以基于Rest接口、模塊計(jì)數(shù)等統(tǒng)計(jì)行為,只要有唯一的operation name即可。

到此這篇關(guān)于Laravel中GraphQL接口請(qǐng)求頻率的文章就介紹到這了,更多相關(guān)Laravel中GraphQL接口請(qǐng)求頻率內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

標(biāo)簽:濟(jì)源 阿里 日照 孝感 池州 北京 那曲 哈密

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《Laravel中GraphQL接口請(qǐng)求頻率實(shí)戰(zhàn)記錄》,本文關(guān)鍵詞  Laravel,中,GraphQL,接口,請(qǐng)求,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問(wèn)題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無(wú)關(guān)。
  • 相關(guān)文章
  • 下面列出與本文章《Laravel中GraphQL接口請(qǐng)求頻率實(shí)戰(zhàn)記錄》相關(guān)的同類(lèi)信息!
  • 本頁(yè)收集關(guān)于Laravel中GraphQL接口請(qǐng)求頻率實(shí)戰(zhàn)記錄的相關(guān)信息資訊供網(wǎng)民參考!
  • 推薦文章