斷尾求生 —— 簡單限流

限流算法在分佈式領域是一個常常被提起的話題,當系統的處理能力有限時,如何阻止計劃外的請求繼續對系統施壓,這是一個須要重視的問題。算法

除了控制流量,限流還有一個應用目的是用於控制用戶行爲,避免垃圾請求。好比在UGC 社區,用戶的發帖、回覆、點贊等行爲都要嚴格受控,通常要嚴格限定某行爲在規定時間內容許的次數,超過了次數那就是非法行爲。對非法行爲,業務必須規定適當的懲處策略。緩存

如何使用 Redis 來實現簡單限流策略?

首先咱們來看一個常見 的簡單的限流策略。系統要限定用戶的某個行爲在指定的時間裏只能容許發生 N 次,如何使用 Redis 的數據結構來實現這個限流的功能?數據結構

這個限流需求中存在一個滑動時間窗口,想一想 zset 數據結構的 score 值,是否是能夠經過 score 來圈出這個時間窗口來。並且咱們只須要保留這個時間窗口,窗口以外的數據均可以砍掉。那這個 zset 的 value 填什麼比較合適呢?它只須要保證惟一性便可,用 uuid 會比較浪費空間,那就改用毫秒時間戳吧。分佈式

Redis簡單限流策略

如圖所示,用一個 zset 結構記錄用戶的行爲歷史,每個行爲都會做爲 zset 中的一個key 保存下來。同一個用戶同一種行爲用一個 zset 記錄。性能

爲節省內存,咱們只須要保留時間窗口內的行爲記錄,同時若是用戶是冷用戶,滑動時間窗口內的行爲是空記錄,那麼這個 zset 就能夠從內存中移除,再也不佔用空間。ui

經過統計滑動窗口內的行爲數量與閾值 max_count 進行比較就能夠得出當前的行爲是否容許。用代碼表示以下:code

/**
 * @Auther: majx2
 * @Date: 2019-3-22 14:21
 * @Description:
 */
public class SimpleRateLimiter {

    private Jedis jedis = RedisDS.create().getJedis();

    public boolean isActionAllowed(String userId, String actionKey, int period, int maxCount) {
        String key = String.format("hist:%s:%s", userId, actionKey);
        long nowTs = System.currentTimeMillis();
        Pipeline pipe = jedis.pipelined();
        pipe.multi();
        pipe.zadd(key, nowTs, "" + nowTs);
        // 移除有序集中,指定score區間內的全部成員
        pipe.zremrangeByScore(key, 0, nowTs - period * 1000);
        Response<Long> count = pipe.zcard(key);
        pipe.expire(key, period + 1);
        pipe.exec();
        pipe.close();
        return count.get() <= maxCount;
    }
    public static void main(String[] args) throws InterruptedException {
        final SimpleRateLimiter limiter = new SimpleRateLimiter();
        for(int i=0;i<20;i++) {
            System.out.println(limiter.isActionAllowed("majx2", "reply", 60, 5));
        }
    }
}

總體思路就是:每個行爲到來時,都維護一次時間窗口。將時間窗口外的記錄所有清理掉,只保留窗口內的記錄。zset 集合中只有 score 值很是重要,value 值沒有特別的意義,只須要保證它是惟一的就能夠了。orm

由於這幾個連續的 Redis 操做都是針對同一個 key 的,使用 pipeline 能夠顯著提高Redis 存取效率。但這種方案也有缺點,由於它要記錄時間窗口內全部的行爲記錄,若是這個量很大,好比限定 60s 內操做不得超過 100w 次這樣的參數,它是不適合作這樣的限流的,由於會消耗大量的存儲空間。中間件

下一節咱們將引入高級限流算法——漏斗限流。ip

本文基於《Redis深度歷險:核心原理和應用實踐》一文的JAVA實踐。更多文章請參考:高性能緩存中間件Redis應用實戰(JAVA)

相關文章
相關標籤/搜索