亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

高并發(fā)下Redis精確計(jì)數(shù)與時(shí)間窗口過(guò)期的方法詳解

 更新時(shí)間:2025年03月27日 08:30:52   作者:碼農(nóng)阿豪@新空間  
在實(shí)時(shí)數(shù)據(jù)處理系統(tǒng)中,我們經(jīng)常需要統(tǒng)計(jì)某個(gè)事件在特定時(shí)間窗口內(nèi)的發(fā)生次數(shù),本文將詳細(xì)介紹如何基于 Redis 實(shí)現(xiàn)高性能、高可用的計(jì)數(shù)方案,需要的可以參考下

引言

在實(shí)時(shí)數(shù)據(jù)處理系統(tǒng)中,我們經(jīng)常需要統(tǒng)計(jì)某個(gè)事件在特定時(shí)間窗口內(nèi)的發(fā)生次數(shù),例如:

  • 統(tǒng)計(jì)用戶(hù)每小時(shí)訪(fǎng)問(wèn)次數(shù)
  • 限制設(shè)備每分鐘請(qǐng)求頻率
  • 廣告曝光按小時(shí)去重計(jì)數(shù)

這類(lèi)需求通常面臨兩個(gè)核心挑戰(zhàn):

  • 高并發(fā)計(jì)數(shù):多臺(tái)服務(wù)器同時(shí)讀寫(xiě)同一個(gè)計(jì)數(shù)器
  • 精確時(shí)間窗口:數(shù)據(jù)到點(diǎn)自動(dòng)過(guò)期,避免累積

本文將詳細(xì)介紹如何基于 Redis 實(shí)現(xiàn)高性能、高可用的計(jì)數(shù)方案,并提供完整的Java代碼實(shí)現(xiàn)。

一、Redis計(jì)數(shù)方案選型

1.1 為什么選擇Redis

方案QPS數(shù)據(jù)一致性實(shí)現(xiàn)復(fù)雜度
數(shù)據(jù)庫(kù)+事務(wù)~1K強(qiáng)一致
本地緩存~100K最終一致
Redis原子操作50K+強(qiáng)一致

Redis的單線(xiàn)程模型天然適合計(jì)數(shù)場(chǎng)景,提供INCR/INCRBY等原子命令。

1.2 Key設(shè)計(jì)原則

// 格式:業(yè)務(wù)前綴:appId:deviceId:ip:時(shí)間窗口
String key = "flow:count:app123:device456:127.0.0.1:2023080117";
  • 包含所有維度信息
  • 時(shí)間窗口按小時(shí)切分(可調(diào)整)
  • 添加業(yè)務(wù)前綴避免沖突

二、基礎(chǔ)實(shí)現(xiàn)方案

2.1 簡(jiǎn)單INCRBY實(shí)現(xiàn)

public void incrementCount(String key, int delta) {
    redisTemplate.opsForValue().increment(key, delta);
}

問(wèn)題:沒(méi)有過(guò)期時(shí)間,會(huì)導(dǎo)致數(shù)據(jù)無(wú)限堆積

2.2 增加過(guò)期時(shí)間

public void incrementWithExpire(String key, int delta, long ttlSeconds) {
    redisTemplate.opsForValue().increment(key, delta);
    redisTemplate.expire(key, ttlSeconds, TimeUnit.SECONDS);
}

新問(wèn)題:每次操作都設(shè)置TTL,造成冗余Redis調(diào)用

三、優(yōu)化方案:精準(zhǔn)TTL控制

3.1 判斷Key是否首次寫(xiě)入

我們需要確保TTL只在Key創(chuàng)建時(shí)設(shè)置一次,兩種實(shí)現(xiàn)方式:

方案A:Lua腳本(推薦)

private static final String LUA_SCRIPT =
    "local current = redis.call('INCRBY', KEYS[1], ARGV[1])\n" +
    "if current == tonumber(ARGV[1]) then\n" +
    "   redis.call('EXPIRE', KEYS[1], ARGV[2])\n" +
    "end\n" +
    "return current";

public Long incrementAtomically(String key, int delta, long ttl) {
    return redisTemplate.execute(
        new DefaultRedisScript<>(LUA_SCRIPT, Long.class),
        Collections.singletonList(key),
        String.valueOf(delta), String.valueOf(ttl)
    );
}

優(yōu)勢(shì):

  • 完全原子性執(zhí)行
  • 單次網(wǎng)絡(luò)往返
  • 精準(zhǔn)判斷首次寫(xiě)入

方案B:SETNX+INCRBY

public void incrementWithNX(String key, int delta, long ttl) {
    redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
        StringRedisConnection conn = (StringRedisConnection) connection;
        conn.setNX(key, "0"); // 嘗試初始化
        conn.incrBy(key, delta);
        if (conn.setNX(key + ":lock", "1")) { // 簡(jiǎn)易鎖判斷首次
            conn.expire(key, ttl);
            conn.expire(key + ":lock", 10);
        }
        return null;
    });
}

適用場(chǎng)景:Redis版本<2.6(不支持Lua)

四、完整生產(chǎn)級(jí)實(shí)現(xiàn)

4.1 時(shí)間窗口計(jì)算

public long calculateTtlToNextHour() {
    LocalDateTime now = LocalDateTime.now();
    LocalDateTime nextHour = now.plusHours(1).truncatedTo(ChronoUnit.HOURS);
    return ChronoUnit.SECONDS.between(now, nextHour);
}

4.2 Kafka消費(fèi)者集成

@Component
@RequiredArgsConstructor
public class FlowCounter {
    private final RedisTemplate<String, String> redisTemplate;
    private static final String KEY_PREFIX = "flow:count:";

    @KafkaListener(topics = "${kafka.topic}")
    public void handleMessages(List<Message> messages) {
        Map<String, Integer> countMap = messages.stream()
            .collect(Collectors.toMap(
                this::buildKey,
                msg -> 1,
                Integer::sum
            ));
        
        countMap.forEach((k, v) -> 
            incrementAtomically(k, v, calculateTtlToNextHour())
        );
    }

???????    private String buildKey(Message msg) {
        return String.format("%s%s:%s:%s:%s", 
            KEY_PREFIX,
            msg.getAppId(),
            msg.getDeviceId(),
            msg.getIp(),
            LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHH"))
        );
    }
}

4.3 查詢(xún)接口

public long getCurrentCount(String appId, String deviceId, String ip) {
    String key = buildKey(appId, deviceId, ip);
    String val = redisTemplate.opsForValue().get(key);
    return val != null ? Long.parseLong(val) : 0L;
}

五、性能優(yōu)化技巧

5.1 Pipeline批量處理

redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
    StringRedisConnection conn = (StringRedisConnection) connection;
    countMap.forEach((k, v) -> {
        conn.incrBy(k, v);
        // 可結(jié)合Lua腳本進(jìn)一步優(yōu)化
    });
    return null;
});

5.2 本地預(yù)聚合

// 在內(nèi)存中先合并相同Key的計(jì)數(shù)
Map<String, Integer> localCount = messages.stream()
    .collect(Collectors.toMap(
        this::buildKey,
        m -> 1,
        Integer::sum
    ));

5.3 集群部署注意事項(xiàng)

使用{}強(qiáng)制哈希標(biāo)簽,保證相同Key路由到同一節(jié)點(diǎn)

"{flow}:count:app123:..."

考慮分片策略避免熱點(diǎn)

六、異常處理與監(jiān)控

6.1 Redis重試機(jī)制

@Retryable(maxAttempts = 3, backoff = @Backoff(delay = 100))
public void safeIncrement(String key, int delta) {
    // 業(yè)務(wù)邏輯
}

6.2 監(jiān)控指標(biāo)

# TYPE redis_operations_total counter
redis_operations_total{operation="incr"} 12345
redis_operations_total{operation="expire"} 678

6.3 數(shù)據(jù)補(bǔ)償

@Scheduled(fixedRate = 3600000)
public void checkDataConsistency() {
    // 對(duì)比DB與Redis計(jì)數(shù)差異
}

七、方案對(duì)比總結(jié)

方案優(yōu)點(diǎn)缺點(diǎn)適用場(chǎng)景
Lua腳本原子性強(qiáng),性能最佳需要Redis 2.6+新項(xiàng)目首選
SETNX+INCR兼容舊版有競(jìng)態(tài)風(fēng)險(xiǎn)遺留系統(tǒng)
純INCR+TTL實(shí)現(xiàn)簡(jiǎn)單TTL冗余不推薦生產(chǎn)

結(jié)語(yǔ)

通過(guò)本文的方案,我們實(shí)現(xiàn)了:

  • 單機(jī)50K+ QPS的計(jì)數(shù)能力
  • 精確到小時(shí)的時(shí)間窗口控制
  • 分布式環(huán)境下的強(qiáng)一致性

最佳實(shí)踐建議:

  • 生產(chǎn)環(huán)境優(yōu)先選擇Lua腳本方案
  • 對(duì)于超高并發(fā)場(chǎng)景(如雙11),可增加本地緩存層
  • 定期檢查Redis內(nèi)存使用情況

以上就是高并發(fā)下Redis精確計(jì)數(shù)與時(shí)間窗口過(guò)期的方法詳解的詳細(xì)內(nèi)容,更多關(guān)于Redis高并發(fā)精確計(jì)數(shù)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • redis使用zset實(shí)現(xiàn)延時(shí)隊(duì)列的示例代碼

    redis使用zset實(shí)現(xiàn)延時(shí)隊(duì)列的示例代碼

    本文主要介紹了redis使用zset實(shí)現(xiàn)延時(shí)隊(duì)列的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-06-06
  • 淺談Redis中的緩存更新策略

    淺談Redis中的緩存更新策略

    這篇文章主要介紹了淺談Redis中的緩存更新策略,CacheAsidePatter是我們比較常用的緩存更新策略,其由緩存調(diào)用者在更新數(shù)據(jù)庫(kù)時(shí),在業(yè)務(wù)邏輯中設(shè)置緩存更新,需要的朋友可以參考下
    2023-08-08
  • Redis的持久化方案詳解

    Redis的持久化方案詳解

    在本篇文章里小編給大家整理的是關(guān)于Redis的持久化方案詳解,有興趣的朋友們可以參考下。
    2020-03-03
  • Redis分布式鎖之紅鎖的實(shí)現(xiàn)

    Redis分布式鎖之紅鎖的實(shí)現(xiàn)

    在Redis中,紅鎖是一種分布式鎖的實(shí)現(xiàn)機(jī)制,旨在解決多個(gè)客戶(hù)端在分布式環(huán)境中對(duì)共享資源進(jìn)行并發(fā)訪(fǎng)問(wèn)的問(wèn)題,本文主要介紹了Redis分布式鎖之紅鎖的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-12-12
  • redis實(shí)現(xiàn)存儲(chǔ)帖子的點(diǎn)贊狀態(tài)和數(shù)量的示例代碼

    redis實(shí)現(xiàn)存儲(chǔ)帖子的點(diǎn)贊狀態(tài)和數(shù)量的示例代碼

    使用Redis來(lái)實(shí)現(xiàn)點(diǎn)贊功能是一種高效的選擇,因?yàn)镽edis是一個(gè)內(nèi)存數(shù)據(jù)庫(kù),適用于處理高并發(fā)的數(shù)據(jù)操作,這篇文章主要介紹了redis實(shí)現(xiàn)存儲(chǔ)帖子的點(diǎn)贊狀態(tài)和數(shù)量的示例代碼,需要的朋友可以參考下
    2023-09-09
  • Redis分布式限流的幾種實(shí)現(xiàn)

    Redis分布式限流的幾種實(shí)現(xiàn)

    分布式限流是指通過(guò)將限流策略嵌入到分布式系統(tǒng)中,以控制流量或保護(hù)服務(wù),本文就來(lái)介紹一下Redis分布式限流的幾種實(shí)現(xiàn),感興趣的可以了解一下
    2023-12-12
  • redis?lua腳本解決高并發(fā)下秒殺場(chǎng)景

    redis?lua腳本解決高并發(fā)下秒殺場(chǎng)景

    這篇文章主要為大家介紹了redis?lua腳本解決高并發(fā)下秒殺場(chǎng)景,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-10-10
  • redis?zset實(shí)現(xiàn)滑動(dòng)窗口限流的代碼

    redis?zset實(shí)現(xiàn)滑動(dòng)窗口限流的代碼

    這篇文章主要介紹了redis?zset實(shí)現(xiàn)滑動(dòng)窗口限流,滑動(dòng)窗口算法思想就是記錄一個(gè)滑動(dòng)的時(shí)間窗口內(nèi)的操作次數(shù),操作次數(shù)超過(guò)閾值則進(jìn)行限流,本文通過(guò)實(shí)例代碼給大家詳細(xì)介紹,需要的朋友參考下吧
    2022-03-03
  • gem install redis報(bào)錯(cuò)的解決方案

    gem install redis報(bào)錯(cuò)的解決方案

    今天小編就為大家分享一篇關(guān)于gem install redis報(bào)錯(cuò)的解決方案,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2019-01-01
  • Redis高級(jí)玩法之利用SortedSet實(shí)現(xiàn)多維度排序的方法

    Redis高級(jí)玩法之利用SortedSet實(shí)現(xiàn)多維度排序的方法

    Redis的SortedSet是可以根據(jù)score進(jìn)行排序的,以手機(jī)應(yīng)用商店的熱門(mén)榜單排序?yàn)槔?,根?jù)下載量倒序排列。接下來(lái)通過(guò)本文給大家分享Redis高級(jí)玩法之利用SortedSet實(shí)現(xiàn)多維度排序的方法,一起看看吧
    2019-07-07

最新評(píng)論