3

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

 2 years ago
source link: https://zsmhub.github.io/post/%E5%AE%9E%E6%88%98%E6%A1%88%E4%BE%8B/%E9%99%90%E6%B5%81-%E6%8A%A2%E7%99%BD%E5%90%8D%E5%8D%95%E5%8A%9F%E8%83%BD/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

2021-04-21 约 881 字 预计阅读 2 分钟

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

  1. 支持临时 追加/减少 白名单名额
  2. 支持重置白名单
  3. 直接配置化,不需要操作数据库
  1. 启用白名单功能,在 .env 文件配置以下参数:

    WHITE_LIST_IS_ENABLE=1
    WHITE_LIST_NUMBER=10
    
  2. 追加白名单名额,在 .env 文件调整以下参数:

    WHITE_LIST_NUMBER=20
    
  3. 减少白名单名额,在 .env 文件调整以下参数:

    WHITE_LIST_NUMBER=10
    WHITE_LIST_CACHE_KEY_PREFIX="second"
    
  4. 觉得该换另一批人使用 xxx 系统了,则重置白名单,在 .env 文件调整以下参数:

    WHITE_LIST_CACHE_KEY_PREFIX="third"
    
  5. 关闭白名单功能,在 .env 文件调整以下参数:

    WHITE_LIST_IS_ENABLE=0
    
// 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;
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK