账号层级限流-抢白名单功能

Laravel框架
324
0
0
2022-04-10
标签   Laravel缓存

功能说明

应用场景

数据库高负荷时,在员工账号层级进行限流,目的是让数据库尽早缓冲过来

特色功能
  1. 支持临时 追加/减少 白名单名额
  2. 支持重置白名单
  3. 直接配置化,不需要操作数据库

源代码

// config/white_list.php
<?php

// 抢白名单配置,白名单内的员工允许访问 xxx 系统,其他员工则跳转到登陆界面
// 变更配置后,记得执行 php artisan optimize
return [
    // 是否启用 
    'is_enable'  =>  env('WHITE_LIST_IS_ENABLE', 0),

    // 白名单名额,支持临时追加白名单名额,但不支持减少操作 
    'number'  =>  env('WHITE_LIST_NUMBER', 100),

    // 缓存 key 前缀,变更后会重置白名单 
    'cache_key_prefix'  =>  env('WHITE_LIST_CACHE_KEY_PREFIX', 'first'),

    // 缓存 key 
    'cache_key_list_init'  =>  '_xxx_white_list_init',
    'cache_key_list'  =>  '_xxx_white_list',
    'cache_key_hash'  =>  '_xxx_white_hash' 
    'cache_ttl'  =>  43200,

    // 员工没在白名单内的提示文案,并跳转到登陆界面 
    'error_msg'  =>  'xxx 系统暂不可用,请稍晚一些再登陆使用,谢谢配合!',
];

// Services/RedisHelper.php
<?php

namespace  App\Services;

use Illuminate\Support\Facades\Redis;

class  RedisHelper
{
    /**
    * redis排他锁:拒绝并发相同的请求
    * @param  string $key
    * @param $value
    * @param  int $ttl
    * @return  bool
    */ 
    public static function setNxWithTTL(string $key, $value, int $ttl =  300):  bool
    {
        if ((Redis::connection('default'))->set($key, $value, 'ex', $ttl, 'nx')) {
            return true;
        }
        return false;
    }
}
/**
* 核心代码~判断该员工是否在白名单内:只允许部分员工可以正常使用 xxx 系统,其他人则跳转到登陆界面
* @param  int $staffId
* @return  bool 返回 true 表示该员工在白名单内,允许正常使用 xxx 系统
*/
public function isInWhiteList(int $staffId): bool
{
    // 是否启用白名单功能 
    if (!config('white_list.is_enable')) {
        return true;
    }

    // 白名单名额 
    $whiteListNumber = (int)config('white_list.number');
    if ($whiteListNumber <=  0) {
        return false;
    }

    $redis =  Redis::connection('default');
    $cacheKeyPrefix =  config('white_list.cache_key_prefix');
    $cacheKeyListInit = $cacheKeyPrefix .  config('white_list.cache_key_list_init');
    $cacheKeyList = $cacheKeyPrefix .  config('white_list.cache_key_list');
    $cacheKeyHash = $cacheKeyPrefix .  config('white_list.cache_key_hash');
    $cacheTTL =  config('white_list.cache_ttl');

    // 判断该员工是否在白名单中 
    if ($redis->exists($cacheKeyHash) && $redis->hexists($cacheKeyHash, $staffId)) {
        return true;
    }

    // 初始化令牌队列 
    if (!$redis->exists($cacheKeyListInit)) {
        if (!RedisHelper::setNxWithTTL("{$cacheKeyListInit}_nx", 1, config('cache.one_hour'))) {
            return true;
        }

        $redis->rpush($cacheKeyListInit, array_fill(0, $whiteListNumber, 1));
        $redis->expire($cacheKeyListInit, $cacheTTL);
    }

    // 支持临时追加白名单名额 
    $length = $redis->llen($cacheKeyListInit);
    if ($length < $whiteListNumber) {
        if (!RedisHelper::setNxWithTTL("{$cacheKeyListInit}_nx_{$whiteListNumber}", 1, config('cache.one_hour'))) {
            return false;
        }

        $redis->rpush($cacheKeyListInit, array_fill($length -  1, $whiteListNumber - $length, 1));
        $redis->expire($cacheKeyListInit, $cacheTTL);
    }

    // 避免并发情况下,同一员工占用多个白名单名额 
    if (!$redis->hsetnx($cacheKeyHash, $staffId, 1)) {
        return true;
    }

    $index = $redis->rpush($cacheKeyList, [$staffId]);
    $ret = $redis->lindex($cacheKeyListInit, $index -  1);

    // 手慢了,白名单名额已被抢完 
    if (empty($ret)) {
        $redis->rpop($cacheKeyList);
        $redis->hdel($cacheKeyHash, [$staffId]);
        return false;
    }

    $redis->expire($cacheKeyList, $cacheTTL);
    $redis->expire($cacheKeyHash, $cacheTTL);

    return true;
}

使用案例

  1. 启用白名单功能,在 .env 文件配置以下参数:
 WHITE_LIST_IS_ENABLE=1 
 WHITE_LIST_NUMBER=10
  1. 追加白名单名额,在 .env 文件调整以下参数:
 WHITE_LIST_NUMBER=20
  1. 减少白名单名额,在 .env 文件调整以下参数:
 WHITE_LIST_NUMBER=10 
 WHITE_LIST_CACHE_KEY_PREFIX="second"
  1. 觉得该换另一批人使用 xxx 系统了,则重置白名单,在 .env 文件调整以下参数:
 WHITE_LIST_CACHE_KEY_PREFIX="third"
  1. 关闭白名单功能,在 .env 文件调整以下参数:
 WHITE_LIST_IS_ENABLE=0