精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

面試官:說說看你知道的常見限流算法有哪些?它們的優(yōu)缺點分別是什么?如何基于用戶身份限流?

系統(tǒng) 開發(fā)
在系統(tǒng)架構(gòu)中,限流的主要目的是保護系統(tǒng)免受過量請求的沖擊,確保系統(tǒng)資源能夠得到合理分配和有效利用,從而維持系統(tǒng)的穩(wěn)定性和可靠性。

面試官:能不能說說看在系統(tǒng)架構(gòu)中,限流的主要目的是什么,有哪些常見的限流算法

在系統(tǒng)架構(gòu)中,限流的主要目的是保護系統(tǒng)免受過量請求的沖擊,確保系統(tǒng)資源能夠得到合理分配和有效利用,從而維持系統(tǒng)的穩(wěn)定性和可靠性。具體來說,限流有助于防止以下幾種情況的發(fā)生:

  • 系統(tǒng)過載:當請求量超過系統(tǒng)的處理能力時,系統(tǒng)可能會因為資源耗盡(如CPU、內(nèi)存、數(shù)據(jù)庫連接等)而崩潰或響應(yīng)變慢。
  • 數(shù)據(jù)庫壓力:大量的并發(fā)請求可能導致數(shù)據(jù)庫負載過高,進而影響數(shù)據(jù)的一致性和完整性。
  • 服務(wù)雪崩:在微服務(wù)架構(gòu)中,一個服務(wù)的過載可能會引發(fā)連鎖反應(yīng),導致整個系統(tǒng)的崩潰。

限流常見的實現(xiàn)方式包括以下幾種:

(1) 計數(shù)器限流:

通過記錄單位時間窗口內(nèi)的請求數(shù)量,當數(shù)量達到設(shè)定的閾值時,拒絕后續(xù)請求。

優(yōu)點:實現(xiàn)簡單,易于理解。

缺點:存在臨界問題,即在時間窗口切換時的大量請求突發(fā)會影響下一個窗口對限流的判斷。

(2) 滑動窗口限流:

改進了計數(shù)器限流,通過維護一個滑動的時間窗口來記錄請求數(shù)量,從而更精細地控制請求速率。

優(yōu)點:解決了臨界問題,能夠更平滑地控制請求流量。

缺點:實現(xiàn)相對復雜,且依舊無法很好應(yīng)對突發(fā)流量給系統(tǒng)帶來的高負荷。

(3) 令牌桶限流:

以恒定速率向令牌桶中添加令牌,請求到來時從桶中取出令牌,若桶中無令牌則拒絕請求。

優(yōu)點:能夠平滑突發(fā)流量,適應(yīng)性強。

缺點:需要維護令牌桶的狀態(tài),開銷稍大。

(4) 漏桶限流:

請求以固定速率流入漏桶,桶滿則拒絕請求,桶不滿則請求繼續(xù)處理。

優(yōu)點:實現(xiàn)簡單,易于理解。

缺點:應(yīng)對突發(fā)流量時能通過恒定速度放開請求避免系統(tǒng)高負荷,但是可能造成高并發(fā)請求延遲。

(5) 基于Redis的限流:

利用Redis的原子操作特性,如INCR、EXPIRE等,實現(xiàn)限流邏輯。

面試官:能詳細說說看計數(shù)器限流的基本原理嗎?

計數(shù)器限流(也稱為固定窗口限流算法)是一種簡單且直觀的限流方法,其基本原理是在一段時間間隔內(nèi)對請求進行計數(shù),并將計數(shù)結(jié)果與設(shè)置的最大請求數(shù)進行比較,以決定是否允許后續(xù)請求通過。以下是對計數(shù)器限流基本原理的詳細解釋:

1.時間間隔與計數(shù)器

  • 時間間隔:計數(shù)器限流通常設(shè)定一個固定的時間間隔,如1分鐘、5分鐘等。在這個時間間隔內(nèi),系統(tǒng)會對接收到的請求進行計數(shù)。
  • 計數(shù)器:系統(tǒng)維護一個計數(shù)器變量,用于記錄在當前時間間隔內(nèi)接收到的請求數(shù)量。每當有請求到達時,計數(shù)器就會加1。

2.請求計數(shù)與比較

  • 請求計數(shù):在設(shè)定的時間間隔內(nèi),每當有請求到達系統(tǒng)時,計數(shù)器就會增加相應(yīng)的數(shù)值。這樣,系統(tǒng)就可以實時地跟蹤當前時間間隔內(nèi)的請求數(shù)量。
  • 比較操作:系統(tǒng)會將計數(shù)器的當前值與預設(shè)的最大請求數(shù)進行比較。如果計數(shù)器的值小于或等于最大請求數(shù),則允許請求通過;如果計數(shù)器的值大于最大請求數(shù),則拒絕后續(xù)請求,直到時間間隔結(jié)束并重置計數(shù)器。

3.時間間隔結(jié)束與計數(shù)器重置

  • 時間間隔結(jié)束:當設(shè)定的時間間隔結(jié)束時,系統(tǒng)會自動將計數(shù)器重置為0,并開始下一個時間間隔的計數(shù)。
  • 計數(shù)器重置:重置計數(shù)器是為了確保在新的時間間隔內(nèi),系統(tǒng)能夠重新接收和處理請求。這樣,系統(tǒng)就可以持續(xù)地對請求進行限流控制。

以下是一個使用Python實現(xiàn)的簡單計數(shù)器限流示例。這個示例使用了一個全局變量來模擬計數(shù)器,以及一個時間戳來跟蹤當前時間窗口的開始時間。


import time
from threading import Lock

class CounterRateLimiter:
    def __init__(self, max_requests, window_size_in_seconds):
        """
        初始化計數(shù)器限流器
        :param max_requests: 時間窗口內(nèi)允許的最大請求數(shù)
        :param window_size_in_seconds: 時間窗口的大?。耄?        """
        self.max_requests = max_requests
        self.window_size_in_seconds = window_size_in_seconds
        self.request_count = 0
        self.window_start_time = time.time()
        self.lock = Lock()

    def allow_request(self):
        """
        檢查是否允許請求通過
        :return: 如果允許請求通過則返回True,否則返回False
        """
        with self.lock:
            current_time = time.time()
            # 檢查是否到了新的時間窗口
            if current_time - self.window_start_time >= self.window_size_in_seconds:
                # 重置計數(shù)器和時間窗口
                self.request_count = 0
                self.window_start_time = current_time

            # 檢查請求數(shù)量是否超過閾值
            if self.request_count < self.max_requests:
                self.request_count += 1
                return True
            else:
                return False

# 使用示例
if __name__ == "__main__":
    # 允許每分鐘最多10個請求
    rate_limiter = CounterRateLimiter(max_requests=10, window_size_in_seconds=60)

    # 模擬請求
    for i in range(15):
        if rate_limiter.allow_request():
            print(f"請求 {i + 1} 被允許")
        else:
            print(f"請求 {i + 1} 被拒絕")
        # 模擬請求間隔(例如,每秒一個請求)
        time.sleep(1)

計數(shù)器限流是一種常用的限流策略,具有簡單易用、直觀易懂等優(yōu)點,但也存在一些明顯的問題,具體包括:

時間臨界點突發(fā)流量問題:計數(shù)器限流存在“時間臨界點”缺陷,即在時間區(qū)間的臨界點附近,如果請求數(shù)突然增加,可能會導致短時間內(nèi)大量請求通過限流檢查,從而對系統(tǒng)造成壓力。

假設(shè)我們要求系統(tǒng)在1分鐘之內(nèi)最多通過100個請求,系統(tǒng)以1分鐘作為時間區(qū)間,因此 [0s, 59s] 是一個時間區(qū)間,[1min, 1min 59s]是一個時間區(qū)間。

假設(shè)有一個惡意用戶,他在0:59時,瞬間發(fā)送了100個請求,并且1:00又瞬間發(fā)送了100個請求。我們希望的是1分鐘內(nèi)平均最多只能接受200個請求,但其實這個用戶在 1秒里面,瞬間發(fā)送了200個請求。

要知道,1分鐘均勻請求100次和1分鐘內(nèi)的短短幾秒里突發(fā)請求100次所帶給系統(tǒng)的壓力是完全不同的。用戶有可能通過算法的這個漏洞,瞬間壓垮我們的應(yīng)用。

這個問題,本質(zhì)其實就在于這樣固定周期清空計數(shù)器的方式精度太低,不能區(qū)分 1 分鐘內(nèi)均勻請求 100 次,以及只在 1 分鐘內(nèi)很小的一個時間區(qū)間里請求 100 次,這兩種情況。

面試官:那么有沒有什么更好的算法可以解決這個問題呢?

解決這個問題本質(zhì)就是要解決時間區(qū)間的精度問題,而滑動窗口限流算法可以通過將一個大的時間區(qū)間進一步劃分為多個小的區(qū)間來解決窗口精度問題。

其基本原理可以歸納如下:

1.定義

滑動窗口限流算法通過將大的時間區(qū)間劃分為若干個固定大小的小區(qū)間(或稱為時間片段),并且以這個大的時間區(qū)間作為一個時間窗口,隨著時間的推移這個大的時間窗口可以在小的時間分片上移動,并動態(tài)地維護這個大窗口內(nèi)的請求數(shù)量和小時間片內(nèi)的請求數(shù)量,從而實現(xiàn)對流量的精確控制。

2.基本原理

(1) 時間窗口劃分:

將大的時間區(qū)間(如上面例子中的1分鐘)劃分為多個固定大小的小區(qū)間(例如10秒)。

(2) 窗口滑動:

在第一個一分鐘里,窗口并不會滑動。而之后,每隔一個時間片(也就是每隔10秒),窗口就會往前移動一個時間片。以此類推,隨著時間的推移,窗口會不斷向前滑動。

(3) 請求計數(shù):

在每個小的時間片段內(nèi),都維護一個計數(shù)器來記錄該時間片內(nèi)的請求數(shù)量。而大的時間窗口內(nèi)的請求數(shù)量等于它里面所有小時間片段的請求數(shù)量之和。

當新的請求到達時,根據(jù)其到達時間確定其所屬的小時間片段,并在該時間片的計數(shù)器上遞增(也就是圖中黃色的時間片),與此同時大窗口內(nèi)的請求數(shù)量也同時增加。

而第一個時間片中的請求數(shù)量會被大窗口內(nèi)丟棄(減法扣去),同時開始加上新的時間片內(nèi)的請求計數(shù)。

(4) 限流判斷:

每次請求到達的時候,都會檢查大窗口內(nèi)的總請求數(shù)量是否超過了預設(shè)的閾值。

如果請求數(shù)量超過了閾值,則在窗口繼續(xù)滑動到下一個時間片之前的請求都會被拒絕,等到10s過去了,窗口滑動到下一個時間片并丟棄掉窗口內(nèi)第一個時間片的請求量之后,請求才會被允許通過并繼續(xù)處理。

3.優(yōu)點

(1) 靈活性:

滑動窗口限流算法可以根據(jù)業(yè)務(wù)需求動態(tài)地調(diào)整窗口內(nèi)時間片的大小(可以調(diào)成10秒,也可以調(diào)成1秒)。

這使得算法能夠更好地適應(yīng)不同的流量場景和業(yè)務(wù)需求。

(2) 精確性:

如果將窗口內(nèi)的時間片調(diào)節(jié)的足夠小,滑動窗口限流算法就能夠更有效地應(yīng)對突發(fā)流量。

還是上面那個例子,依舊要求1分鐘內(nèi)最多處理100個請求,如果我將時間片調(diào)整為1秒,窗口就會每秒滑動一次。惡意用戶依舊是在第59秒并發(fā)100個請求,再在第60秒并發(fā)另外100個請求。

那么當窗口滑動到第60秒的時候,第60秒的100個請求都會被拒絕。

4.缺點

滑動窗口算法在將窗口劃分的很細的情況下,可能會帶來以下缺點:

(1) 算法復雜度增加:

想要進一步提高被控制流量的平滑性,就需要不斷增加窗口的精度,也就是縮小每個區(qū)間的大小。當窗口被劃分得非常細時,需要維護的計數(shù)器數(shù)量會顯著增加。每個子窗口都需要一個獨立的計數(shù)器來跟蹤請求數(shù)量,這會導致算法在計算和更新計數(shù)器時的復雜度增加。

窗口細化意味著需要處理更多的數(shù)據(jù)點和時間戳。這會增加算法的存儲需求和計算開銷。

(2) 限流效果依舊不穩(wěn)定:

滑動窗口限流算法的另一個缺點是限流仍然不夠平滑。例如,如果在某個小窗口的開始階段就達到了限流閾值,那么在這個小窗口剩余的時間內(nèi),所有新的請求都會被拒絕,這也會影響到用戶體驗。

面試官:能不能具體實現(xiàn)一下滑動窗口限流算法呢?

Redis的有序集合(zset)非常適合實現(xiàn)這種算法,因為zset能夠存儲帶時間戳的分數(shù),并根據(jù)分數(shù)自動排序。

下面是一個使用Redis的zset實現(xiàn)滑動窗口限流算法的代碼:


import redis
import time

class SlidingWindowRateLimiter:
    def __init__(self, redis_client, key_prefix, max_requests, window_size_in_seconds):
        self.redis = redis_client
        self.key_prefix = key_prefix
        self.max_requests = max_requests
        self.window_size_in_seconds = window_size_in_seconds

    def _get_key(self, user_id):
        return f"{self.key_prefix}:{user_id}"

    def _get_current_window_start_time(self):
        return int(time.time()) // self.window_size_in_seconds * self.window_size_in_seconds

    def _add_request(self, user_id):
        key = self._get_key(user_id)
        window_start_time = self._get_current_window_start_time()
        score = time.time()

        # 使用管道來確保原子性
        with self.redis.pipeline() as pipe:
            pipe.zremrangebyscore(key, 0, window_start_time - self.window_size_in_seconds)
            pipe.zadd(key, {score: score})
            pipe.zcard(key)
            result = pipe.execute()

        # 移除舊窗口的數(shù)據(jù),并添加新請求
        _, _, current_count = result

        return current_count

    def allow_request(self, user_id):
        current_count = self._add_request(user_id)
        return current_count <= self.max_requests

# 示例使用
if __name__ == "__main__":
    # 連接到Redis服務(wù)器
    redis_client = redis.StrictRedis(host='localhost', port=6379, db=0)

    # 創(chuàng)建限流器實例
    rate_limiter = SlidingWindowRateLimiter(
        redis_client,
        key_prefix="rate_limiter",
        max_requests=10,  # 每窗口最多10個請求
        window_size_in_seconds=60  # 窗口大小為60秒
    )

    # 示例用戶ID
    user_id = "user_123"

    # 模擬請求
    for i in range(15):
        if rate_limiter.allow_request(user_id):
            print(f"Request {i + 1} allowed.")
        else:
            print(f"Request {i + 1} denied.")
        time.sleep(2)  # 每2秒發(fā)送一個請求
import redis
import time


class SlidingWindowRateLimiter:
    def __init__(self, redis_client, key_prefix, max_requests, window_size_in_seconds):
        self.redis = redis_client
        self.key_prefix = key_prefix
        self.max_requests = max_requests
        self.window_size_in_seconds = window_size_in_seconds


    def _get_key(self, user_id):
        return f"{self.key_prefix}:{user_id}"


    def _get_current_window_start_time(self):
        return int(time.time()) // self.window_size_in_seconds * self.window_size_in_seconds


    def _add_request(self, user_id):
        key = self._get_key(user_id)
        window_start_time = self._get_current_window_start_time()
        score = time.time()


        # 使用管道來確保原子性
        with self.redis.pipeline() as pipe:
            pipe.zremrangebyscore(key, 0, window_start_time - self.window_size_in_seconds)
            pipe.zadd(key, {score: score})
            pipe.zcard(key)
            result = pipe.execute()


        # 移除舊窗口的數(shù)據(jù),并添加新請求
        _, _, current_count = result


        return current_count


    def allow_request(self, user_id):
        current_count = self._add_request(user_id)
        return current_count <= self.max_requests


# 示例使用
if __name__ == "__main__":
    # 連接到Redis服務(wù)器
    redis_client = redis.StrictRedis(host='localhost', port=6379, db=0)


    # 創(chuàng)建限流器實例
    rate_limiter = SlidingWindowRateLimiter(
        redis_client,
        key_prefix="rate_limiter",
        max_requests=10,  # 每窗口最多10個請求
        window_size_in_seconds=60  # 窗口大小為60秒
    )


    # 示例用戶ID
    user_id = "user_123"


    # 模擬請求
    for i in range(15):
        if rate_limiter.allow_request(user_id):
            print(f"Request {i + 1} allowed.")
        else:
            print(f"Request {i + 1} denied.")
        time.sleep(2)  # 每2秒發(fā)送一個請求

(1) 初始化:

__init__ 方法中初始化Redis客戶端、key前綴、最大請求數(shù)和窗口大小。

(2) 獲取Redis鍵:

_get_key 方法根據(jù)用戶ID生成Redis鍵。

(3) 獲取當前窗口開始時間:

_get_current_window_start_time 方法計算當前時間所屬窗口的開始時間(向下取整到窗口大小的倍數(shù))。

(4) 添加請求:

  • _add_request 方法使用Redis管道(pipeline)來確保原子性操作。
  • 先移除舊窗口的數(shù)據(jù)(分數(shù)小于當前窗口開始時間減去窗口大小的數(shù)據(jù))。
  • 添加新請求的時間戳到zset中。
  • 獲取當前窗口內(nèi)的請求數(shù)量,判斷請求是否允許。

在不使用中間件的情況下我們可以使用純內(nèi)存數(shù)據(jù)結(jié)構(gòu)(隊列)結(jié)合時間戳來實現(xiàn)滑動窗口限流算法。這種方法適用于單機環(huán)境,或者當你不希望/不能使用外部存儲(如Redis)時。

以下是一個使用Python的deque實現(xiàn)滑動窗口限流的示例代碼:


from collections import deque
import time

class SlidingWindowRateLimiter:
    def __init__(self, max_requests, window_size_in_seconds):
        self.max_requests = max_requests
        self.window_size_in_seconds = window_size_in_seconds
        self.window = deque()  # 存儲 (timestamp, request_id) 元組

    def _is_within_window(self, timestamp):
        window_start = time.time() - self.window_size_in_seconds
        return timestamp >= window_start

    def allow_request(self):
        current_timestamp = time.time()

        # 移除窗口外的請求
        while self.window and not self._is_within_window(self.window[0][0]):
            self.window.popleft()

        # 添加當前請求到窗口
        self.window.append((current_timestamp, id(self)))  # 使用id(self)作為示例請求ID,實際中應(yīng)替換為唯一請求標識

        # 檢查窗口內(nèi)的請求數(shù)量
        if len(self.window) > self.max_requests:
            return False

        return True

# 示例使用
if __name__ == "__main__":
    # 創(chuàng)建限流器實例
    rate_limiter = SlidingWindowRateLimiter(max_requests=5, window_size_in_seconds=10)

    # 模擬請求
    for i in range(10):
        if rate_limiter.allow_request():
            print(f"Request {i + 1} allowed.")
        else:
            print(f"Request {i + 1} denied.")
        time.sleep(2)  # 每2秒發(fā)送一個請求

(1) 初始化:

__init__ 方法中初始化最大請求數(shù)、窗口大小和用于存儲請求記錄的deque。

(2) 檢查時間戳是否在窗口內(nèi):

_is_within_window 方法用于檢查給定的時間戳是否在當前窗口內(nèi)。

(3) 允許請求:

  • allow_request 方法首先獲取當前時間戳,然后移除窗口外(即過期)的請求記錄。
  • 接著,將當前請求(帶有時間戳和請求ID)添加到窗口中。
  • 最后,檢查窗口內(nèi)的請求數(shù)量是否超過最大請求數(shù),并返回相應(yīng)的結(jié)果。

面試官:就像你剛剛說的,滑動窗口限流在面對突發(fā)流量的情況下依舊不夠平滑,還有沒有什么限流算法能夠讓系統(tǒng)在面對突發(fā)流量的時候更均勻的接收請求呢?

可以使用漏桶算法,當請求速率大于桶的流出速率時,漏桶只會按照桶的流出速率均勻的放出請求給系統(tǒng)處理,而超出速率的請求會被儲蓄在桶中等待處理。

漏桶算法的原理可以簡單描述為:數(shù)據(jù)(或請求)流入漏桶,桶內(nèi)的數(shù)據(jù)以固定的速率流出,控制了系統(tǒng)能夠處理請求的速度。如果桶滿了(即桶的容量到達上限),后續(xù)的流量(或請求)會被丟棄,防止系統(tǒng)超載。

具體來說,漏桶算法維持一個具有固定容量的“桶”,該桶接受以任意速率流入的請求,并以固定的速率處理(即“漏出”)這些請求。當桶滿時,任何新進入的請求都將被丟棄。這樣,無論請求到達的速率如何變化,系統(tǒng)處理請求的速率都保持恒定,從而實現(xiàn)了流量整形和限流控制。

漏桶算法的重要參數(shù)主要包括以下2個:

  • 桶的容量:代表了系統(tǒng)能夠處理的請求的最大數(shù)量,也代表了漏桶(包緩存)可以容納的數(shù)據(jù)包的最大字節(jié)數(shù)。這個尺寸受限于有效的系統(tǒng)內(nèi)存。
  • 漏出速率:這個速率代表了系統(tǒng)處理數(shù)據(jù)的速率。它決定了數(shù)據(jù)從漏桶中注入網(wǎng)絡(luò)的速率,從而平滑了突發(fā)流量。

在實際應(yīng)用中,漏桶算法的工作流程大致如下:

  • 初始化:設(shè)置桶的容量和漏出速率。
  • 接收請求:每當有數(shù)據(jù)(或請求)到達時,先檢查桶的狀態(tài)。
  • 桶未滿:如果桶未滿,允許數(shù)據(jù)(或請求)進入桶中,然后等待漏出。
  • 桶已滿:如果桶已滿,新到達的數(shù)據(jù)(或請求)會被丟棄或排隊等待。
  • 數(shù)據(jù)(或請求)處理:數(shù)據(jù)(或請求)在桶中等待直到之前的請求被處理完,然后以漏出速率再被處理。

優(yōu)缺點

優(yōu)點:

  • 穩(wěn)定性好:漏桶算法能夠以固定的速率去控制流量,使得系統(tǒng)處理請求的速率保持恒定,從而有效防止了流量激增導致的服務(wù)不穩(wěn)定。
  • 保護后端服務(wù):通過限制請求的處理速率,漏桶算法能夠保護后端服務(wù)免受大流量沖擊,避免服務(wù)崩潰。

缺點:

  • 請求延遲處理:當系統(tǒng)在短時間內(nèi)有突發(fā)的大流量時,由于漏桶的流出速率是固定的,因此無法及時處理這些突發(fā)流量,突發(fā)流量會被阻塞一段時間才得到處理,還可能導致部分請求被丟棄。

下面是一個使用Python實現(xiàn)的簡單漏桶算法示例實現(xiàn)。


import time
import threading
from collections import deque

class LeakyBucket:
    def __init__(self, capacity, leak_rate):
        self.capacity = capacity  # 桶的容量
        self.leak_rate = leak_rate  # 漏出速率(每秒漏出的請求數(shù))
        self.bucket = deque()  # 使用雙端隊列來模擬桶,存儲請求的時間戳
        self.lock = threading.Lock()  # 線程鎖,用于線程安全

    def _leak(self):
        """內(nèi)部方法,用于模擬漏出請求"""
        current_time = time.time()
        while self.bucket and (current_time - self.bucket[0]) >= 1.0 / self.leak_rate:
            self.bucket.popleft()  # 移除最舊的請求

    def allow_request(self):
        """判斷是否可以允許一個請求進入桶中"""
        with self.lock:
            self._leak()  # 先嘗試漏出一些請求
            current_time = time.time()
            if len(self.bucket) < self.capacity:
                self.bucket.append(current_time)  # 允許請求進入桶中
                return True
            else:
                # 桶已滿,拒絕請求
                return False

# 示例使用
if __name__ == "__main__":
    bucket = LeakyBucket(capacity=10, leak_rate=5)  # 創(chuàng)建一個漏桶,容量為10,漏出速率為5個請求/秒

    # 模擬請求到達
    for i in range(20):
        if bucket.allow_request():
            print(f"Request {i} allowed at {time.time():.2f}")
        else:
            print(f"Request {i} rejected at {time.time():.2f}")
        # 模擬請求到達的間隔
        time.sleep(0.2)
import time
import threading
from collections import deque

class LeakyBucket:
    def __init__(self, capacity, leak_rate):
        self.capacity = capacity  # 桶的容量
        self.leak_rate = leak_rate  # 漏出速率(每秒漏出的請求數(shù))
        self.bucket = deque()  # 使用雙端隊列來模擬桶,存儲請求的時間戳
        self.lock = threading.Lock()  # 線程鎖,用于線程安全

    def _leak(self):
        """內(nèi)部方法,用于模擬漏出請求"""
        current_time = time.time()
        while self.bucket and (current_time - self.bucket[0]) >= 1.0 / self.leak_rate:
            self.bucket.popleft()  # 移除最舊的請求

    def allow_request(self):
        """判斷是否可以允許一個請求進入桶中"""
        with self.lock:
            self._leak()  # 先嘗試漏出一些請求
            current_time = time.time()
            if len(self.bucket) < self.capacity:
                self.bucket.append(current_time)  # 允許請求進入桶中
                return True
            else:
                # 桶已滿,拒絕請求
                return False

# 示例使用
if __name__ == "__main__":
    bucket = LeakyBucket(capacity=10, leak_rate=5)  # 創(chuàng)建一個漏桶,容量為10,漏出速率為5個請求/秒

    # 模擬請求到達
    for i in range(20):
        if bucket.allow_request():
            print(f"Request {i} allowed at {time.time():.2f}")
        else:
            print(f"Request {i} rejected at {time.time():.2f}")
        # 模擬請求到達的間隔
        time.sleep(0.2)

在這個示例中,LeakyBucket 類實現(xiàn)了漏桶算法。

  • __init__ 方法初始化了桶的容量、漏出速率以及用于存儲請求時間戳的雙端隊列。
  • _leak 方法是一個內(nèi)部方法,用于模擬漏出請求的過程,它會檢查桶中最舊的請求是否已經(jīng)超過了漏出速率所允許的時間,并移除這些請求。
  • allow_request 方法用于判斷是否可以允許一個新的請求進入桶中,它首先嘗試漏出一些請求,然后檢查桶是否已滿,如果未滿則允許請求進入并返回 True,否則返回 False 表示請求被拒絕。

面試官:能否說說看與漏桶算法相似的令牌桶算法以及它們的差異呢?

令牌桶算法的基本算法是這樣的:

  • 如果我們需要在一秒內(nèi)限制訪問次數(shù)為 N 次,那么就每隔 1/N 的時間,往桶內(nèi)放入一個令牌;
  • 在處理請求之前先要從桶中獲得一個令牌,如果桶中已經(jīng)沒有了令牌,那么就需要等待新的令牌或者直接拒絕服務(wù);
  • 桶中的令牌總數(shù)也要有一個限制,如果超過了限制就不能向桶中再增加新的令牌了。這樣可以限制或增加令牌的總數(shù),一定程度上可以避免瞬時流量高峰的問題。

令牌桶算法與漏桶算法的差異點是,漏桶算法的目的是“精確平滑控制速率”,它會完全避免系統(tǒng)受到突發(fā)流量的沖擊,讓系統(tǒng)一直以均勻的速度處理請求。

而令牌桶的桶本身具備一定的容量,可以允許一次把桶里的令牌全都取出,因此,令牌桶算法在限制請求的平均速率的同時,還允許一定程度的突發(fā)流量。

正常來說,系統(tǒng)并非是受不了一點沖擊的溫室花朵,也還是具有一定的處理突發(fā)流量的能力的,令牌桶算法也符合現(xiàn)實業(yè)務(wù)中90%的時間流量處于均值或者低谷,而偶爾存在并發(fā)高峰的場景。

下面是一個令牌桶算法的簡單Python實現(xiàn)。

import time
import threading

class TokenBucket:
    def __init__(self, rate, capacity):
        """
        初始化令牌桶
        :param rate: 令牌生成速率(每秒生成的令牌數(shù))
        :param capacity: 桶的容量(最大令牌數(shù))
        """
        self.rate = rate
        self.capacity = capacity
        self.tokens = capacity  # 初始令牌數(shù)
        self.lock = threading.Lock()
        self.last_refill_timestamp = time.time()  # 上次添加令牌的時間戳

    def _add_tokens(self):
        """
        根據(jù)時間差計算并添加令牌
        """
        now = time.time()
        elapsed = now - self.last_refill_timestamp
        added_tokens = elapsed * self.rate
        self.last_refill_timestamp = now

        # 添加令牌,但不能超過桶的容量
        self.tokens = min(self.capacity, self.tokens + added_tokens)

    def consume(self, num_tokens):
        """
        嘗試消耗指定數(shù)量的令牌
        :param num_tokens: 需要消耗的令牌數(shù)
        :return: 如果成功消耗則返回 True,否則返回 False
        """
        with self.lock:
            self._add_tokens()
            if self.tokens >= num_tokens:
                self.tokens -= num_tokens
                return True
            else:
                return False

# 示例使用
if __name__ == "__main__":
    # 令牌生成速率 5 個/秒,桶容量 10 個
    bucket = TokenBucket(rate=5, capacity=10)

    for i in range(15):
        if bucket.consume(1):
            print(f"Successfully consumed 1 token. Current tokens: {bucket.tokens}")
        else:
            print("Failed to consume token. Not enough tokens in the bucket.")
        time.sleep(0.5)  # 每 0.5 秒嘗試消耗一次

此外還需要注意的是使用令牌桶算法就需要存儲令牌的數(shù)量以及申請獲取令牌,如果是單機上實現(xiàn)限流的話,可以在進程中使用一個變量來存儲;但是如果在分布式環(huán)境下,不同的機器之間無法共享進程中的變量,我們就一般會使用 Redis 來存儲這個令牌的數(shù)量。

這樣的話,每次請求的時候都需要請求一次 Redis 來獲取一個令牌,會增加幾毫秒的延遲,性能上會有一些損耗。因此,一個折中的思路是:我們可以在每次取令牌的時候,不再只獲取一個令牌,而是獲取一批令牌,這樣可以盡量減少請求 Redis 的次數(shù)。

面試官:如何實現(xiàn)不僅對總流量限流,也對單個用戶的在指定長度的時間段限流?

舉個例子,如果我需要對總流量限流為每分鐘1萬個請求,對單個用戶限流為每分鐘60個請求。這就意味著對于用戶A來說,如果A請求時已經(jīng)觸發(fā)總流量1萬個請求的限流,那么A無法訪問;或者如果A請求時已經(jīng)在1分鐘內(nèi)請求超過了60次,那么A也還是無法訪問。

此時我們可以設(shè)置了兩個令牌桶:一個是全局的令牌桶,一個是以 userid 為 key,按照用戶來劃分的令牌桶。

這里用兩個令牌桶做了一個組合,就可以即做到對總流量限流,也對用戶身份限流:

在全局令牌桶還有令牌的情況下,判斷全局的令牌桶,如果全局令牌桶沒有令牌了,那么觸發(fā)限流拒絕訪問。如果全局令牌桶還有令牌,就再去檢查用戶令牌桶;

在用戶令牌桶有令牌的情況下,用戶A的請求可以通過。用戶令牌桶沒有令牌的情況下,用戶A的請求會被拒絕掉。

這樣一來,就可以保證其他用戶的請求不會受到影響。

責任編輯:趙寧寧 來源: 程序員阿沛
相關(guān)推薦

2024-04-19 00:00:00

計數(shù)器算法限流算法

2023-02-20 08:08:48

限流算法計數(shù)器算法令牌桶算法

2024-02-26 14:07:18

2024-06-05 10:07:00

限流微服務(wù)算法

2024-11-19 08:00:00

2021-09-30 07:57:13

排序算法面試

2023-11-28 09:19:12

2021-05-27 05:37:10

HTTP請求頭瀏覽器

2023-11-20 10:09:59

2024-12-25 15:44:15

2024-02-04 10:08:34

2022-06-10 13:56:42

Java

2021-07-02 07:06:20

React組件方式

2022-12-05 08:12:31

net/http庫http連接池

2022-06-15 15:14:17

Java公平鎖非公平鎖

2025-07-28 00:00:01

2021-09-26 10:57:16

集合操作場景

2025-05-14 00:00:00

MySQL雙主架構(gòu)循環(huán)復制

2021-08-11 08:53:23

Git命令面試

2021-10-29 09:40:21

設(shè)計模式軟件
點贊
收藏

51CTO技術(shù)棧公眾號

天堂精品中文字幕在线| 欧美电影在线观看完整版| 亚洲精品成人悠悠色影视| 不卡一卡2卡3卡4卡精品在| 日韩免费视频网站| 黄色不卡一区| 精品国产免费视频| 亚洲成色www.777999| 在线观看午夜av| 久久网站最新地址| www.久久久| 午夜一级黄色片| xxx性欧美| 国产日本欧洲亚洲| 国产精品一区二| 亚洲在线视频播放| 亚洲一区网站| 亚洲国产精品久久久久秋霞不卡 | 九色视频在线观看免费播放| 精品在线免费观看| 国产精品www网站| 国产无套内射又大又猛又粗又爽| www.久久99| 日韩欧美一区二区三区| 男人天堂新网址| 视频一区二区三区不卡| 久久精品在线观看| 国产欧美一区二区视频| 99久久国产免费| 九九在线精品视频| 国产成人91久久精品| 日韩精品国产一区二区| 欧美日韩国产高清| 成人97在线观看视频| www.涩涩爱| 国产毛片一区二区三区 | 91精品国产综合久久香蕉最新版 | wwwww黄色| 亚洲最大在线| 日韩精品在线私人| 国产视频久久久久久| 盗摄系列偷拍视频精品tp| 欧美一区二区三区免费大片| 26uuu成人| 成年人视频免费在线观看| 人人超碰91尤物精品国产| 欧美综合第一页| 国产成人精品网| 国产欧美日韩一级| 97色在线视频观看| 日本中文在线播放| 国产精品久久国产愉拍| 69av在线视频| a v视频在线观看| 国产精品一二| 日韩av手机在线观看| 波多野结衣 久久| 日韩中文字幕91| 国产精品普通话| 一级特黄aaa大片| 韩国女主播成人在线观看| 亚洲www在线观看| 亚洲av无码国产精品永久一区| 国产成人免费视频一区| 欧美一级淫片丝袜脚交| 欧美在线观看不卡| 久久午夜精品一区二区| 国产精品福利观看| 一级黄色短视频| 国产高清不卡一区| 激情久久av| 国产98在线| 国产精品成人午夜| 国产真实老熟女无套内射| 中文在线а√在线8| 欧美午夜宅男影院| 一级日本黄色片| 婷婷午夜社区一区| 欧美三级在线看| 97人人模人人爽人人澡| 国产伦乱精品| 国产一区二区三区高清在线观看| 波多野结衣久久久久| 欧美日本一区二区高清播放视频| 97超级碰碰碰久久久| 日韩一级片中文字幕| 精品一区二区免费在线观看| 国产v亚洲v天堂无码| 可以在线观看的av网站| 亚洲天堂免费看| 久久久999视频| 日日夜夜亚洲精品| 免费观看在线午夜影视| 蘑菇福利视频一区播放| 国产区精品视频| 天堂av资源在线| 国产精品亲子乱子伦xxxx裸| 日本a级片在线观看| 日韩精品福利| 国产精品免费网站在线观看| 免费在线看黄色片| 国产在线观看91| 精品久久久久久久久久国产| 久久婷婷国产91天堂综合精品| 欧美久久亚洲| 在线观看视频亚洲| www.日本精品| 国产高清久久久| 亚洲乱码一区二区三区| 国产白浆在线免费观看| 樱花影视一区二区| 成人一级片网站| 日韩黄色av| 一本色道久久综合狠狠躁篇的优点 | 亚洲一区 在线播放| 桃花岛tv亚洲品质| 精品国产一区a| 人人干在线观看| 日韩精品久久理论片| 国产嫩草一区二区三区在线观看| av国产在线观看| 欧美午夜精品久久久久久浪潮| 粗大的内捧猛烈进出视频| 成人黄色免费网站| 日韩精品免费在线播放| 久久久一二三区| 精品在线播放免费| 亚洲精品在线视频观看| 老司机2019福利精品视频导航| 亚洲成人精品视频在线观看| 91人妻一区二区三区蜜臀| 蜜桃av一区二区| 日本在线高清视频一区| 在线免费三级电影网站| 亚洲第一福利视频| 日韩av电影网| www.爱久久.com| 欧美日韩最好看的视频| av在线中出| 亚洲第一色中文字幕| 免看一级a毛片一片成人不卡| 黄页网站大全一区二区| 中文字幕av日韩精品| 国产成人精品一区二区三区在线 | 欧美色手机在线观看| av黄色免费网站| 天堂成人国产精品一区| 日本成人三级电影网站| 影视一区二区三区| 色青青草原桃花久久综合| a片在线免费观看| 国产精品色婷婷久久58| 国产色视频在线播放| 97精品一区二区| 91亚洲精品一区| 欧美熟妇另类久久久久久不卡| 偷拍自拍在线| 亚洲免费色视频| 欧美午夜性视频| 在线天堂资源| 国产婷婷97碰碰久久人人蜜臀| 国产精品第9页| www国产亚洲精品久久麻豆| 92看片淫黄大片一级| 欧美综合另类| 亚洲aⅴ日韩av电影在线观看 | 亚洲激情在线观看视频| 欧美激情电影| 99视频免费观看蜜桃视频| 成年人黄色大片在线| 亚洲欧美日韩精品| 91精品中文字幕| 亚洲主播在线播放| 中文字幕5566| 老司机精品视频在线| 亚洲精品少妇一区二区| 国内精品国产成人国产三级粉色| 5566成人精品视频免费| 中文日本在线观看| 亚洲成人精品视频在线观看| 国产成人无码专区| 亚洲精品乱码久久久久久| 噜噜噜在线视频| 久久爱另类一区二区小说| 妞干网在线播放| 最新国产一区| 亚洲一区亚洲二区| 国产不卡网站| 欧美日本中文字幕| 国产精品一级伦理| 精品福利视频一区二区三区| 五月婷婷中文字幕| 亚洲人xxxx| 久久久久亚洲av成人无码电影| 狠狠色丁香久久婷婷综合丁香| 亚洲 欧美 综合 另类 中字| 国产99久久精品一区二区300| 成人xxxx视频| 成人影院免费观看| 日韩视频一区二区三区在线播放| 四虎成人在线观看| 一区二区三区欧美日| 日本黄色小视频在线观看| 国产xxx精品视频大全| 中文字幕天天干| 亚洲日本久久| 欧美另类videos| 精品精品久久| 国产精品99导航| xxxx视频在线| 欧美xxxx做受欧美| 草碰在线视频| 日韩精品在线影院| 好男人www在线视频| 亚洲最大的成人av| 免费成人深夜蜜桃视频| 久久亚洲精品国产精品紫薇| 少妇极品熟妇人妻无码| 久草中文综合在线| 一区二区三区 欧美| 亚洲综合社区| 亚洲 欧美 日韩 国产综合 在线 | 久久久av毛片精品| 欧美一级片黄色| 国产福利一区在线| 做a视频在线观看| 秋霞电影一区二区| 激情网站五月天| 免费视频一区二区三区在线观看| 国产日韩亚洲欧美在线| 午夜精品国产| 强开小嫩苞一区二区三区网站| 色综合咪咪久久网| 亚洲国产一区二区精品视频| 国产一区二区区别| 欧美成人综合一区| 综合亚洲色图| 欧美精品一区二区三区久久| 无码日韩精品一区二区免费| 国产欧美日韩伦理| 日韩av黄色在线| 国产综合av一区二区三区| 98视频精品全部国产| 成人免费视频视频在| 一区二区免费| 国产精品一区二区三区不卡| 成人爽a毛片免费啪啪红桃视频| 超碰97人人人人人蜜桃| heyzo欧美激情| 精品国产乱码久久久久久郑州公司 | 亚洲欧美视频一区二区三区| 波多野结衣家庭教师视频| 亚洲在线电影| 免费看黄色一级大片| 美女任你摸久久| 男人午夜视频在线观看| 国产九色精品成人porny | 91影院在线播放| 这里是久久伊人| 亚洲精品成人电影| 日韩av在线天堂网| 国产伦精品一区二区三区视频孕妇| 国产cdts系列另类在线观看| 欧美大片在线免费观看| caoporn-草棚在线视频最| 91大神在线播放精品| 日韩一区二区三区在线免费观看 | 国产99久久久久久免费看| 在线视频欧美精品| 91女人18毛片水多国产| 日韩欧美一二区| 五月激情丁香婷婷| 欧美亚洲动漫精品| 97精品人妻一区二区三区| 日韩一区和二区| 五月婷婷六月色| 国产一区二区美女视频| 国产一二区在线观看| 久久久免费观看视频| h视频在线播放| 久久精品最新地址| av手机在线观看| 国产精品久久一区主播| 成人h动漫免费观看网站| 欧美亚洲国产免费| 欧美在线网址| 熟妇人妻va精品中文字幕| 国产伦精品一区二区三区免费迷 | www.av黄色| 亚洲欧洲午夜一线一品| 在线不卡日本v二区707| 国产精品69精品一区二区三区| 精品国产欧美| 青青草久久网络| 欧美日韩精品| 蜜臀av免费观看| 99久久婷婷国产| 国产高潮国产高潮久久久91| 色播五月激情综合网| 蜜臀av免费在线观看| 久久精品国产69国产精品亚洲| 春色校园综合激情亚洲| 成人av在线网址| 精品久久中文| 黄色大片在线免费看| 国产综合久久久久久鬼色| 亚洲a v网站| 精品免费在线视频| 国产黄a三级三级看三级| 亚洲久久成人| 国产a级片免费观看| 粉嫩欧美一区二区三区高清影视| 性の欲びの女javhd| 欧美日韩精品在线播放| 性做久久久久久久久久| 日韩视频―中文字幕| 中日韩脚交footjobhd| 国产伦精品一区二区三区| 久久久久久久久久久久久久久久久久 | 国产激情在线观看| 国产欧美va欧美va香蕉在| 国产99久久精品一区二区300| r级无码视频在线观看| 国产精品99久久久久久似苏梦涵| 天天干天天操天天拍| 在线一区二区三区四区| 欧美挠脚心网站| 欧美在线视频免费观看| 奇米777国产一区国产二区| 国产freexxxx性播放麻豆| 国产激情一区二区三区桃花岛亚洲| avhd101老司机| 欧美在线一二三四区| 激情小说 在线视频| 国产91精品青草社区| 欧美1区2区3区4区| 国产综合av在线| 久久福利影视| 国产麻豆天美果冻无码视频 | 久久99成人| 超碰免费在线公开| 精品午夜久久福利影院 | 外国成人激情视频| 成人午夜免费在线视频| 国产在线精品一区二区不卡了| 三级影片在线观看| 欧美精品在线观看播放| 免费黄色在线网站| 91色精品视频在线| 欧美一区成人| 最新版天堂资源在线| 亚洲成av人片一区二区三区| 99久久自偷自偷国产精品不卡| 韩国精品主播一区二区在线观看 | 国产精品久久久久久av| 日韩最新在线| 波多野结衣50连登视频| 99久久久无码国产精品| 国产成人精品网| 中文字幕在线看视频国产欧美在线看完整 | 精品成av人一区二区三区| 五月婷婷狠狠操| 国产精品二三区| www.黄色av| 26uuu另类亚洲欧美日本老年| 伊人成综合网yiren22| 日韩大片一区二区| 亚洲色图视频网站| 天堂av在线免费| 国产精品久久久久av| 亚洲精品91| 精品国产一区在线| 欧美视频三区在线播放| gogo在线观看| 久久精品人人做人人爽电影| 日本最新不卡在线| 卡通动漫亚洲综合| 亚洲精品国产福利| 高清在线一区| 国内精品视频一区二区三区| 国产婷婷色一区二区三区| 久久久久成人网站| 日韩av在线播放资源| 国产极品一区| 日b视频免费观看| 国产午夜亚洲精品不卡 | 亚洲欧美制服综合另类| 欧美一级做一级爱a做片性| 青春草国产视频| 国产精品视频在线看| 天堂网在线观看视频| 国产精品视频自在线| 亚洲每日在线| 激情高潮到大叫狂喷水| 亚洲国产精品一区二区久| 久久av影院| 国产午夜福利视频在线观看| 亚洲精品成人精品456| 国产三级电影在线| 国产日韩久久|