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

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

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

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

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

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

SkipList + HashTable

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

跳表(SkipList)

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

哈希表(HashTable)

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

ZipList

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

  • (元素, score) 對(duì)順序存儲(chǔ)

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

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

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

跳躍表(Skip List)詳解

基本概念

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

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

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

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

關(guān)鍵特性

層級(jí)隨機(jī)生成

  • 新節(jié)點(diǎn)的層數(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é)點(diǎn)的值小于目標(biāo)值,則繼續(xù)前進(jìn)
  • 否則下降一層繼續(xù)查找
  • 時(shí)間復(fù)雜度:O(logN)

插入操作

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

刪除操作

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

為什么選擇跳躍表?

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

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

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

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

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

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

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

Redis ZSET 實(shí)現(xiàn)滑動(dòng)時(shí)間窗口限流

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

關(guān)鍵步驟

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

代碼實(shí)現(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)前請(qǐ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. 記錄本次請(qǐng)求\n" +
        "redis.call('ZADD', key, now, requestId)\n" +
        "\n" +
        "-- 5. 刷新過(guò)期時(shí)間\n" +
        "redis.call('EXPIRE', key, window/1000 + 10)\n" +
        "return 1";
 
    public SlidingWindowLimiter(RedisTemplate<String, String> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
 
    /**
     * 檢查請(qǐng)求是否允許通過(guò)
     * @param key 限流鍵(如 user_123:api_pay)
     * @param windowMillis 窗口大小(毫秒)
     * @param maxRequests 窗口內(nèi)允許的最大請(qǐng)求數(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("請(qǐng)求過(guò)于頻繁");
        }
        
        // 執(zhí)行業(yè)務(wù)邏輯
        paymentService.process(request);
        return ResponseEntity.ok("支付成功");
    }
}

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

相關(guān)文章

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

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

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

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

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

    詳解Redis瘦身指南

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

    redis列表類型_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

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

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

    我們?cè)谑褂肦edis分片集群時(shí),集群最好的狀態(tài)就是每個(gè)實(shí)例可以處理相同或相近比例的請(qǐng)求,但如果不是這樣,則會(huì)出現(xiàn)某些實(shí)例壓力特別大,而某些實(shí)例特別空閑的情況發(fā)生,本文就一起來(lái)看下這種情況是如何發(fā)生的以及如何處理
    2022-12-12
  • Redis集群指定主從關(guān)系及動(dòng)態(tài)增刪節(jié)點(diǎn)方式

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

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

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

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

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

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

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

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

    Redis Lua腳本的使用教程

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

最新評(píng)論