欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

NestJS+Redis實現(xiàn)手寫一個限流器

 更新時間:2023年11月27日 16:12:17   作者:松加德的杰洛特  
限流是大型系統(tǒng)必備的保護措施,本文將結(jié)合redis , lua 腳本 以及 Nestjs Guard 來實現(xiàn) 限流的效果,感興趣的小伙伴可以跟隨小編一起學習一下

前言

限流是大型系統(tǒng)必備的保護措施,常用的限流算法主要有固定時間窗口,滑動時間窗口,漏桶,令牌桶等。本文將會寫道的方案是使用 滑動時間窗口 算法,通過拒絕請求的方式來達到限流的目的。 本文的實現(xiàn)方式是 redis , lua 腳本 以及 Nestjs Guard 來實現(xiàn) 限流的效果。

概念淺析

這里簡單說一下 固定時間窗口 和滑動時間窗口的概念

固定時間窗口 它可以解決 每 時間單位(可以是秒或者分鐘等等),允許訪問的次數(shù)。但是無法控制頻率。舉例1分鐘允許訪問100 次,可能前10 秒訪問了90次,后面只有10次機會了。 還有一個問題就是在兩個時間單位的臨界值上可能會超出閾值,繼續(xù)用前面的例子,第59秒訪問了60次,第二個時間單位前10秒訪問了50 次,在橫跨兩個時間單位的20秒中,超出了閾值 (110>100)

滑動時間窗口 可以改善固定窗口的所帶來超出閾值的問題。它將每個單位之間分割成若干小周期,當前時間單位不再是固定的,而是根據(jù)當前請求時間往后移動,即所謂滑動窗口。每個周期分的越小,限流控制的越精細。

具體實現(xiàn)

使用的主要包的版本 nestjs 8.0.0 ioredis 5.3.2

我們主要實現(xiàn)以下幾個東西

  • 一個 guard 文件 用于實現(xiàn)限流的業(yè)務邏輯
  • 一個 decorator文件 , 裝飾器,用于設置當前接口限流的頻率,允許訪問次數(shù)等字段
  • 一個 redis 類 和一個lua 腳本

redis 相關

主要就是通過lua 腳本進行計數(shù),達到限流的目的。這里做了一個優(yōu)化,對執(zhí)行l(wèi)ua 取了hash 值,在redis 運行一次后 ,可以使用evalsha 直接運行腳本,避免二次載入腳本。

import { Injectable } from '@nestjs/common';
import * as Redis from 'ioredis';
import { ConfigService } from '@app/common';
import { createHash } from 'crypto'
import { v4 as uuidv4 } from 'uuid';

const rateLimitScript = ""http:// 后面單獨列出

@Injectable()
export class RedisService {
    private readonly redisClient: Redis.Redis;
    private luaScript: any;
    constructor(
        private readonly configService: ConfigService,
    ) {

        const self = this;
        const connConfig = this.configService.get("redisService")
        this.redisClient = new Redis.Redis(connConfig)
        this.luaScript = {
            rateLimit: {
                script: rateLimitScript,
                hash: self.hashStr(rateLimitScript)
            },
        }
        
    }

    private hashStr(value: string) {
        return createHash("sha1").update(value).digest('hex')
    }

    async rateLimit(opts: any): Promise<boolean> {

        const { key, limit, windowSize } = opts;
        const uuid = uuidv4()
        let result;
        const { script, hash } = this.luaScript.rateLimit
  
        try {

            const shaResult = await this.redisClient.evalsha(hash, 1, key, limit, windowSize, uuid)
            result = shaResult

        } catch (error) {

            const shaResult = await this.redisClient.eval(script, 1, key, limit, windowSize, uuid)
            result = shaResult

        }
        return result == 1
    }
}

接下來展示lua 腳本

--傳入四個參數(shù) 分別是key,限制次數(shù),時間范圍,唯一值
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local windowSize = tonumber(ARGV[2]) --單位毫秒
local uuid = ARGV[3] -- 唯一值是為了防止zset 重復

-- 使用redis 來獲取時間,防止多進程生成相似的邊界導致超頻。時間單位是微秒
local date = redis.call("time")
local now = tonumber(date[1]) * 1000000 + tonumber(date[2])
local startTime = now - windowSize * 1000
local endTime = now +  1000000

-- 計算過期時間 時間單位是秒
local expireSec = tonumber(math.ceil(windowSize / 1000)) + 1

-- 統(tǒng)計當前zset數(shù)組里的數(shù)據(jù),超出范圍則返回0,
-- 否則做3件事,然后返回1
-- 1、向數(shù)組里增加新值
-- 2、刪除數(shù)組中開始時間之前的數(shù)據(jù),防止數(shù)組過大
-- 3、給數(shù)組續(xù)過期時間
local count = tonumber(redis.call('zcount', key, startTime, endTime))

if count + 1 > limit then
    return 0
else
    redis.call('zadd', key, now, uuid)
    redis.call('zremrangebyscore', key, 0, startTime - 100000)
    redis.call('expire', key, expireSec)
    return 1
end

裝飾器相關

這個很簡單就是,設置一下redis 鍵值的前綴,允許訪問的次數(shù)和 單位之間的長度。在這里設置了之后可以在 guard 里通過反射拿到這些值

import { SetMetadata } from '@nestjs/common';

export interface rateLimitOptions {
    keyPrefix: string,
    limit: number,
    windowSize: number
}

export const RateLimit = (options: rateLimitOptions): MethodDecorator => SetMetadata('rateLimit', options)

guard 相關

guard 就是把之前的部分整合了一下,如果當前接口沒有設置限流參數(shù)則啟用默認參數(shù),keyprefix 取當前接口的路徑。

import { Injectable, ExecutionContext, CanActivate } from "@nestjs/common";
import { Reflector } from '@nestjs/core';
import { RedisService, rateLimitOptions } from "@app/common";
import { BusinessException } from "@app/common";

@Injectable()
export class RateLimitGuard implements CanActivate {
    constructor(
        private reflector: Reflector,
        private redisService: RedisService
    ) { }

    private getIpFromRequest(request: { ip: string }): string {
        return request.ip?.match(/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/)?.[0]
    }

    async canActivate(context: ExecutionContext) {

         // 通過反射拿到前面設置的值
        const rateLimitConfig = this.reflector.get<rateLimitOptions>("rateLimit", context.getHandler());
           if (!rateLimitConfig) {
			 // 當前接口如果沒設置參數(shù)則定義默認參數(shù)
            const cMethod = this.reflector.get("method", context.getHandler());// 是GET,POST 等http method
            const cPath = this.reflector.get("path", context.getHandler());// 接口的具體路徑
            rateLimitConfig = {
                keyPrefix: cMethod + ":" + cPath,
                limit: 1,
                windowSize: 5000
            }
        }

        const { keyPrefix, limit, windowSize } = rateLimitConfig
        const request = context.switchToHttp().getRequest();
        const ip = this.getIpFromRequest(request)
        const key = keyPrefix + ":" + ip

        const isPass = await this.redisService.rateLimit({
            key,
            limit,
            windowSize
        })
        if (!isPass) {
            // 返回自定義的錯誤
            throw new BusinessException("RATE_LIMIT_EXCEEDED_LIMIT")
        }
        return true
    }
}

使用方法

引入guand 和RateLimit 裝飾器,可以給特定路由增加限流保護

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) { }
  
  @Public()
  @RateLimit({ keyPrefix: "login", limit: 3, windowSize: 1000 })
  @UseGuards(RateLimitGuard)
  @Post('register')
  register(@Body() createUserDto: CreateUserDto) {
    return this.userService.register(createUserDto);
   }
}

或者基于模塊的也可以,這樣路由里的就可以省略了,如果某些接口沒設置RateLimit 參數(shù),guard 內(nèi)部就會使用默認統(tǒng)一參數(shù)。

@Module({
  providers: [
    {
      provide: APP_GUARD,
      useClass: RateLimitGuard
    }
  ],
})

使用ab 測試一下結(jié)果,為了便于測試設置為每5秒可以請求3次。用 ab進行兩次測試,結(jié)果如下

2023-11-25 17:50:39 - error - HttpExceptionFilter - d1385d48-183c-4fbf-b751-4d0b6786f5ba : {"validatorCode":10005,"validatorMessage":"用戶已存在"} - {}
2023-11-25 17:50:39 - error - HttpExceptionFilter - 65f0e427-92e4-4854-a6cc-116c70daac61 : {"validatorCode":10005,"validatorMessage":"用戶已存在"} - {}
2023-11-25 17:50:39 - error - HttpExceptionFilter - b24b8f7e-f961-45d1-a909-36219fc5d112 : {"validatorCode":10005,"validatorMessage":"用戶已存在"} - {}
2023-11-25 17:50:39 - error - HttpExceptionFilter - 9c65d452-8eeb-4c40-a76e-b5bf01524ebb : {"validatorCode":30000,"validatorMessage":"請求頻率過快"} - {}
2023-11-25 17:50:39 - error - HttpExceptionFilter - 1065a900-bb55-4514-9b55-08cc57509e37 : {"validatorCode":30000,"validatorMessage":"請求頻率過快"} - {}
2023-11-25 17:50:45 - error - HttpExceptionFilter - a8176f1c-2788-4e2a-8267-4e2ffadb6238 : {"validatorCode":10005,"validatorMessage":"用戶已存在"} - {}
2023-11-25 17:50:45 - error - HttpExceptionFilter - db911d44-ee8b-4da2-a87b-fa0bcb433c45 : {"validatorCode":10005,"validatorMessage":"用戶已存在"} - {}
2023-11-25 17:50:45 - error - HttpExceptionFilter - 3c59e335-441b-4907-80e4-0d807e5bfb01 : {"validatorCode":10005,"validatorMessage":"用戶已存在"} - {}
2023-11-25 17:50:45 - error - HttpExceptionFilter - 6f48108b-bc8e-4fb6-8231-fa5a9cd22b5f : {"validatorCode":30000,"validatorMessage":"請求頻率過快"} - {}
2023-11-25 17:50:45 - error - HttpExceptionFilter - 7c77c1df-bfe6-4f72-b75a-0d9a1211fe64 : {"validatorCode":30000,"validatorMessage":"請求頻率過快"} - {}

達到要求,收工。

以上就是NestJS+Redis實現(xiàn)手寫一個限流器的詳細內(nèi)容,更多關于NestJS Redis限流器的資料請關注腳本之家其它相關文章!

相關文章

  • 關于Redis的主從復制及哨兵問題

    關于Redis的主從復制及哨兵問題

    redis中以master為主機,slave為從機,一個master可以對應多個slave,而一個slave只能對應一個master,這篇文章主要介紹了Redis的主從復制及哨兵,需要的朋友可以參考下
    2022-06-06
  • 在Ubuntu?14.04系統(tǒng)上備份和恢復Redis數(shù)據(jù)詳細步驟

    在Ubuntu?14.04系統(tǒng)上備份和恢復Redis數(shù)據(jù)詳細步驟

    這篇文章主要給大家介紹了關于在Ubuntu?14.04系統(tǒng)上備份和恢復Redis數(shù)據(jù)的詳細步驟,文中通過代碼介紹的非常詳細,對大家學習或者使用Redis具有一定的參考借鑒價值,需要的朋友可以參考下
    2024-04-04
  • Redis連接池監(jiān)控(連接池是否已滿)與優(yōu)化方法

    Redis連接池監(jiān)控(連接池是否已滿)與優(yōu)化方法

    本文詳細講解了如何在Linux系統(tǒng)中監(jiān)控Redis連接池的使用情況,以及如何通過連接池參數(shù)配置、系統(tǒng)資源使用情況、Redis命令監(jiān)控、外部監(jiān)控工具等多種方法進行檢測和優(yōu)化,以確保系統(tǒng)在高并發(fā)場景下的性能和穩(wěn)定性,討論了連接池的概念、工作原理、參數(shù)配置,以及優(yōu)化策略等內(nèi)容
    2024-09-09
  • Redis中l(wèi)ua腳本實現(xiàn)及其應用場景

    Redis中l(wèi)ua腳本實現(xiàn)及其應用場景

    本文主要介紹了Redis中l(wèi)ua腳本實現(xiàn)及其應用場景,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-04-04
  • 解決redis服務啟動失敗的問題

    解決redis服務啟動失敗的問題

    今天小編就為大家分享一篇解決redis服務啟動失敗的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-05-05
  • redis使用不當導致應用卡死bug的過程解析

    redis使用不當導致應用卡死bug的過程解析

    本文主要記一次找因redis使用不當導致應用卡死bug的過程,文中通過示例代碼介紹的非常詳細,需要的朋友們下面隨著小編來一起學習學習吧
    2021-07-07
  • RedisTemplate 實現(xiàn)基于Value 操作的簡易鎖機制(示例代碼)

    RedisTemplate 實現(xiàn)基于Value 操作的簡易鎖機制(示例代碼)

    本文將介紹如何使用 RedisTemplate 的 opsForValue().setIfAbsent() 方法來實現(xiàn)一種簡單的鎖機制,并提供一個示例代碼,展示如何在 Java 應用中利用這一機制來保護共享資源的訪問,感興趣的朋友跟隨小編一起看看吧
    2024-05-05
  • 使用寶塔在服務器上配置Redis的詳細圖文教程

    使用寶塔在服務器上配置Redis的詳細圖文教程

    這篇文章主要給大家介紹了關于使用寶塔在服務器上配置Redis的相關資料,包括下載和安裝Redis,開放端口,修改配置文件以允許遠程訪問和設置密碼,該過程對于理解Redis在項目部署中的配置提供了實用指導,需要的朋友可以參考下
    2024-11-11
  • Redis三種集群模式詳解

    Redis三種集群模式詳解

    redis有三種集群模式,其中主從是最常見的模式,今天通過本文給大家分享Redis三種集群模式介紹,感興趣的朋友一起看看吧
    2021-10-10
  • 詳解Redis中的BigKey如何發(fā)現(xiàn)和處理

    詳解Redis中的BigKey如何發(fā)現(xiàn)和處理

    這篇文章主要為大家詳細介紹了Redis中的BigKey如何發(fā)現(xiàn)和處理,文中給大家詳細講解了BigKey危害和如何解決這些問題,文章通過代碼示例和圖文介紹的非常詳細,需要的朋友可以參考下
    2023-10-10

最新評論