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

Redis中ZSet數(shù)據(jù)結(jié)構(gòu)與滑動窗口應(yīng)用實現(xiàn)

 更新時間:2025年07月30日 09:53:51   作者:葵續(xù)淺笑  
Redis?ZSET融合哈希表與跳躍表,支持O(1)成員查詢和O(logN)排序,適用于排行榜及滑動時間窗口限流,下面就來介紹一下Redis中ZSet數(shù)據(jù)結(jié)構(gòu)與滑動窗口應(yīng)用,感興趣的可以了解一下

Redis 的 ZSET(有序集合) 是一種結(jié)合了 哈希表跳躍表(Skip List) 的混合數(shù)據(jù)結(jié)構(gòu),既能實現(xiàn) O(1) 復(fù)雜度的成員存在性判斷,又能以 O(logN) 復(fù)雜度維護(hù)有序性。

Redis ZSET 數(shù)據(jù)存儲機(jī)制

ZSET 有兩種實現(xiàn)機(jī)制:

SkipList + HashTable

數(shù)據(jù)實際上是同時存在于兩個數(shù)據(jù)結(jié)構(gòu)中的

跳表(SkipList)

  • score 排序存儲 member
  • 支持范圍查詢(ZRANGE 等命令)
  • 維護(hù)成員的有序性

哈希表(HashTable)

  • 存儲 member -> score 的映射
  • 用于快速判斷成員是否存在(O(1) 復(fù)雜度)
  • 直接獲取成員的分?jǐn)?shù)(ZSCORE 命令)

ZipList

ZipList:對于小型有序集合(元素少且 member ?。?,Redis 會使用 ziplist 編碼來節(jié)省內(nèi)存,只有當(dāng)元素數(shù)量或大小超過閾值時才會轉(zhuǎn)換為真正的跳躍表+哈希表實現(xiàn)。

  • (元素, score) 對順序存儲

數(shù)據(jù)一致性

Redis 通過保證所有寫操作(ZADD/ZREM等)都同時更新這兩個數(shù)據(jù)結(jié)構(gòu)來維護(hù)一致性。當(dāng)添加一個新成員時:

  1. 會先將其添加到哈希表中
  2. 然后插入到跳躍表的正確位置

跳躍表(Skip List)詳解

基本概念

跳躍表是一種概率平衡的數(shù)據(jù)結(jié)構(gòu),它通過維護(hù)多級索引來提高有序鏈表的查找效率。它結(jié)合了鏈表和類似二分查找的特性。

數(shù)據(jù)結(jié)構(gòu)實現(xiàn)

Redis 中跳躍表的核心定義(簡化版):

typedef struct zskiplistNode {
    robj *member;          // 成員對象(如字符串)
    double score;          // 分?jǐn)?shù)(用于排序)
    struct zskiplistNode *backward; // 后退指針(雙向鏈表)
    struct zskiplistLevel {
        struct zskiplistNode *forward; // 前進(jìn)指針
        unsigned int span;  // 跨度(用于計算排名)
    } level[];             // 柔性數(shù)組,表示節(jié)點的層級
} zskiplistNode;
 
typedef struct zskiplist {
    struct zskiplistNode *header, *tail;
    unsigned long length;  // 節(jié)點數(shù)量
    int level;            // 當(dāng)前最大層數(shù)
} zskiplist;

關(guān)鍵特性

層級隨機(jī)生成

  • 新節(jié)點的層數(shù)由隨機(jī)算法決定(冪次定律)
  • Redis 中最大層數(shù)為 32
int zslRandomLevel(void) {
    int level = 1;
    while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))
        level += 1;
    return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}

查找操作

  • 從最高層開始查找
  • 如果當(dāng)前節(jié)點的值小于目標(biāo)值,則繼續(xù)前進(jìn)
  • 否則下降一層繼續(xù)查找
  • 時間復(fù)雜度:O(logN)

插入操作

  • 先查找插入位置
  • 隨機(jī)生成新節(jié)點的層數(shù)
  • 更新各層指針
  • 時間復(fù)雜度:O(logN)

刪除操作

  • 類似插入的逆過程
  • 時間復(fù)雜度:O(logN)

為什么選擇跳躍表?

Redis 選擇跳躍表而非平衡樹(如紅黑樹)的主要原因:

  • 實現(xiàn)簡單:不需要復(fù)雜的旋轉(zhuǎn)操作
  • 范圍查詢高效:底層鏈表天然有序,便于范圍操作
  • 并發(fā)友好:更容易實現(xiàn)無鎖并發(fā)
  • 平均性能好:雖然最壞情況不如平衡樹,但實際表現(xiàn)優(yōu)異

ZSET中與哈希表的協(xié)作

當(dāng)執(zhí)行 ZADD 命令時:

  • 先在哈希表中查找/更新 member-score 映射
  • 然后在跳躍表中插入/更新節(jié)點
  • 保證兩個操作的原子性

這種雙數(shù)據(jù)結(jié)構(gòu)設(shè)計使得 ZSET 能夠:

  • 快速判斷成員是否存在(哈希表)
  • 高效執(zhí)行范圍查詢(跳躍表)
  • 支持豐富的有序集合操作

Redis ZSET 實現(xiàn)滑動時間窗口限流

Redis ZSET除了實現(xiàn)排行榜之類的排序功能,還能根據(jù)擁有排序的特性,簡單的實現(xiàn)滑動時間窗口限流功能。

關(guān)鍵步驟

  • 清理舊數(shù)據(jù)ZREMRANGEBYSCORE key -inf (currentTime - windowSize)
  • 統(tǒng)計當(dāng)前請求數(shù)ZCARD key
  • 檢查是否超限:比較當(dāng)前計數(shù)與閾值
  • 記錄新請求ZADD key currentTime uniqueId
  • 設(shè)置過期時間EXPIRE key windowSize + buffer

代碼實現(xiàn)(Spring Boot)

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
 
import java.util.Collections;
import java.util.UUID;
 
@Component
public class SlidingWindowLimiter {
    
    private final RedisTemplate<String, String> redisTemplate;
    
    // Lua腳本(原子操作)
    private static final String LUA_SCRIPT = 
        "local key = KEYS[1]\n" +
        "local now = tonumber(ARGV[1])\n" +
        "local window = tonumber(ARGV[2])\n" +
        "local maxRequests = tonumber(ARGV[3])\n" +
        "local requestId = ARGV[4]\n" +
        "\n" +
        "-- 1. 移除窗口外的舊數(shù)據(jù)\n" +
        "redis.call('ZREMRANGEBYSCORE', key, 0, now - window)\n" +
        "\n" +
        "-- 2. 獲取當(dāng)前請求數(shù)\n" +
        "local count = redis.call('ZCARD', key)\n" +
        "\n" +
        "-- 3. 檢查是否超限\n" +
        "if count >= maxRequests then\n" +
        "    return 0\n" +
        "end\n" +
        "\n" +
        "-- 4. 記錄本次請求\n" +
        "redis.call('ZADD', key, now, requestId)\n" +
        "\n" +
        "-- 5. 刷新過期時間\n" +
        "redis.call('EXPIRE', key, window/1000 + 10)\n" +
        "return 1";
 
    public SlidingWindowLimiter(RedisTemplate<String, String> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
 
    /**
     * 檢查請求是否允許通過
     * @param key 限流鍵(如 user_123:api_pay)
     * @param windowMillis 窗口大?。ê撩耄?
     * @param maxRequests 窗口內(nèi)允許的最大請求數(shù)
     * @return true=允許, false=限流
     */
    public boolean allowRequest(String key, long windowMillis, int maxRequests) {
        // 構(gòu)造Redis Key
        String redisKey = "rate_limit:" + key;
        
        // 準(zhǔn)備腳本參數(shù)
        long currentTime = System.currentTimeMillis();
        String requestId = UUID.randomUUID().toString();
        
        // 執(zhí)行Lua腳本
        DefaultRedisScript<Long> script = new DefaultRedisScript<>(LUA_SCRIPT, Long.class);
        Long result = redisTemplate.execute(
            script,
            Collections.singletonList(redisKey),
            currentTime, windowMillis, maxRequests, requestId
        );
        
        return result != null && result == 1;
    }
}

使用示例

@RestController
public class PaymentController {
    
    @Autowired
    private SlidingWindowLimiter limiter;
    
    @PostMapping("/pay")
    public ResponseEntity<?> createPayment(@RequestBody PaymentRequest request) {
        // 構(gòu)造限流鍵: 用戶ID + 接口名
        String key = "user_" + request.getUserId() + ":payment";
        
        // 檢查限流: 每用戶每分鐘最多10次支付
        if (!limiter.allowRequest(key, 60000, 10)) {
            return ResponseEntity.status(429).body("請求過于頻繁");
        }
        
        // 執(zhí)行業(yè)務(wù)邏輯
        paymentService.process(request);
        return ResponseEntity.ok("支付成功");
    }
}

到此這篇關(guān)于Redis中ZSet數(shù)據(jù)結(jié)構(gòu)與滑動窗口應(yīng)用的文章就介紹到這了,更多相關(guān)Redis ZSet滑動窗口內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 淺談Redis分布式鎖的正確實現(xiàn)方式

    淺談Redis分布式鎖的正確實現(xiàn)方式

    這篇文章主要介紹了淺談Redis分布式鎖的正確實現(xiàn)方式,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-05-05
  • redis cluster支持pipeline的實現(xiàn)思路

    redis cluster支持pipeline的實現(xiàn)思路

    本文給大家介紹redis cluster支持pipeline的實現(xiàn)思路,在 cluster 上執(zhí)行 pipeline 可能會由于 redis 節(jié)點擴(kuò)縮容 中途 redirection 切換連接導(dǎo)致結(jié)果丟失,具體細(xì)節(jié)問題請參考下本文
    2021-06-06
  • 詳解Redis瘦身指南

    詳解Redis瘦身指南

    Redis應(yīng)該是開發(fā)者最常用的緩存服務(wù)器了,它豐富的數(shù)據(jù)結(jié)構(gòu),快速高效的內(nèi)存操作能幫助開發(fā)者迅速完成復(fù)雜功能的設(shè)計,作為一個內(nèi)存型數(shù)據(jù)庫,Redis經(jīng)常會遇到內(nèi)存問題,今天我們來談一下Redis常見的內(nèi)存滿的問題,介紹一下給 Redis “瘦身”的通用方式。
    2021-05-05
  • redis列表類型_動力節(jié)點Java學(xué)院整理

    redis列表類型_動力節(jié)點Java學(xué)院整理

    這篇文章主要為大家詳細(xì)介紹了redis列表類型的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-08-08
  • redis數(shù)據(jù)傾斜處理方法

    redis數(shù)據(jù)傾斜處理方法

    我們在使用Redis分片集群時,集群最好的狀態(tài)就是每個實例可以處理相同或相近比例的請求,但如果不是這樣,則會出現(xiàn)某些實例壓力特別大,而某些實例特別空閑的情況發(fā)生,本文就一起來看下這種情況是如何發(fā)生的以及如何處理
    2022-12-12
  • Redis集群指定主從關(guān)系及動態(tài)增刪節(jié)點方式

    Redis集群指定主從關(guān)系及動態(tài)增刪節(jié)點方式

    這篇文章主要介紹了Redis集群指定主從關(guān)系及動態(tài)增刪節(jié)點方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-01-01
  • redis和redisson實現(xiàn)分布式鎖的操作方法

    redis和redisson實現(xiàn)分布式鎖的操作方法

    使用 Redis 實現(xiàn)分布式鎖,最直接的想法是利用 setnx 和 expire 命令實現(xiàn)加鎖,這篇文章主要介紹了redis和redisson實現(xiàn)分布式鎖的操作方法,需要的朋友可以參考下
    2024-03-03
  • redis 實現(xiàn)登陸次數(shù)限制的思路詳解

    redis 實現(xiàn)登陸次數(shù)限制的思路詳解

    這篇文章主要介紹了redis 實現(xiàn)登陸次數(shù)限制的思路詳解,本文通過實例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-08-08
  • 如何監(jiān)聽Redis中Key值的變化(SpringBoot整合)

    如何監(jiān)聽Redis中Key值的變化(SpringBoot整合)

    測試過程中我們有一部分常量值放入redis,共大部分應(yīng)用調(diào)用,但在測試過程中經(jīng)常有人會清空redis,回歸測試,下面這篇文章主要給大家介紹了關(guān)于如何監(jiān)聽Redis中Key值變化的相關(guān)資料,需要的朋友可以參考下
    2024-03-03
  • Redis Lua腳本的使用教程

    Redis Lua腳本的使用教程

    在Redis的學(xué)習(xí)中,Lua腳本是一項強(qiáng)大的高級特性,它允許用戶在Redis中執(zhí)行復(fù)雜的操作,本文就來介紹一下Redis Lua,腳本的使用教程,感興趣的可以了解一下
    2024-03-03

最新評論