中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Laravel中GraphQL接口請求頻率案例講解

發布時間:2021-03-03 17:39:12 來源:億速云 閱讀:203 作者: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

昭平县| 文安县| 华宁县| 阿克苏市| 呼伦贝尔市| 岐山县| 盱眙县| 保德县| 旬邑县| 襄垣县| 黔西| 沂源县| 南涧| 泽州县| 嘉祥县| 永川市| 威信县| 延安市| 康马县| 两当县| 墨玉县| 堆龙德庆县| 鱼台县| 温泉县| 镇雄县| 婺源县| 武宁县| 凌海市| 洛扎县| 思南县| 阿拉善左旗| 嘉鱼县| 鄂尔多斯市| 兴仁县| 蛟河市| 淅川县| 疏勒县| 永和县| 新安县| 曲靖市| 新泰市|