Redis利用原子操作(INCR,DECR)實(shí)現(xiàn)分布式計(jì)數(shù)器
在分布式系統(tǒng)中,由于多個(gè)服務(wù)實(shí)例需要共享和修改同一個(gè)計(jì)數(shù)值,實(shí)現(xiàn)一個(gè)準(zhǔn)確、高效的分布式計(jì)數(shù)器至關(guān)重要。Redis 憑借其內(nèi)存存儲(chǔ)的高性能和原子操作命令,成為實(shí)現(xiàn)這一功能的理想選擇。
核心原理:Redis 的原子操作
Redis 的單線程命令處理模型確保了單個(gè)命令的執(zhí)行是原子性的。 這意味著當(dāng)一個(gè)命令正在執(zhí)行時(shí),不會(huì)被其他客戶端的命令打斷。對(duì)于計(jì)數(shù)器而言,INCR 和 DECR 這兩個(gè)命令是核心。
INCR key: 將存儲(chǔ)在key的數(shù)字值增一。如果key不存在,那么key的值會(huì)先被初始化為 0,然后再執(zhí)行INCR操作。DECR key: 將key中儲(chǔ)存的數(shù)字值減一。如果key不存在,其值同樣會(huì)先被初始化為 0 再執(zhí)行DECR。
這兩個(gè)操作的原子性是實(shí)現(xiàn)分布式計(jì)數(shù)器的基石,它保證了即使在大量并發(fā)請(qǐng)求下,計(jì)數(shù)結(jié)果也是準(zhǔn)確的,避免了“讀取-修改-寫(xiě)入”模式中可能出現(xiàn)的競(jìng)態(tài)條件。
基本實(shí)現(xiàn)方法
實(shí)現(xiàn)一個(gè)基本的分布式計(jì)數(shù)器非常簡(jiǎn)單,只需要為你的計(jì)數(shù)器定義一個(gè)唯一的鍵(key),然后調(diào)用相應(yīng)的原子命令即可。
使用場(chǎng)景示例:
- 文章閱讀量統(tǒng)計(jì): 每當(dāng)有用戶閱讀一篇文章,就對(duì)該文章的計(jì)數(shù)器執(zhí)行
INCR。 - 在線用戶數(shù): 用戶登錄時(shí)執(zhí)行
INCR,登出時(shí)執(zhí)行DECR。 - 庫(kù)存管理: 用戶下單時(shí)執(zhí)行
DECR,取消訂單或補(bǔ)貨時(shí)執(zhí)行INCR。
Python 代碼示例 (使用redis-py)
import redis
# 連接到 Redis
r = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)
def get_post_views(post_id: int) -> int:
"""獲取文章的閱讀量"""
key = f"post:{post_id}:views"
view_count = r.get(key)
return int(view_count) if view_count else 0
def increment_post_views(post_id: int) -> int:
"""增加文章的閱讀量"""
key = f"post:{post_id}:views"
# INCR 是原子操作,返回增加后的值
return r.incr(key)
# --- 使用示例 ---
post_id = 123
print(f"文章 {post_id} 的初始閱讀量: {get_post_views(post_id)}")
# 模擬10次并發(fā)的閱讀請(qǐng)求
for _ in range(10):
new_views = increment_post_views(post_id)
print(f"閱讀量已增加至: {new_views}")
print(f"文章 {post_id} 的最終閱讀量: {get_post_views(post_id)}")
Java 代碼示例 (使用Jedis)
import redis.clients.jedis.Jedis;
public class DistributedCounter {
private final Jedis jedis;
public DistributedCounter(String host, int port) {
this.jedis = new Jedis(host, port);
}
public long increment(String key) {
// incr 是原子操作
return jedis.incr(key);
}
public long decrement(String key) {
// decr 是原子操作
return jedis.decr(key);
}
public long getCount(String key) {
String value = jedis.get(key);
return value != null ? Long.parseLong(value) : 0;
}
public static void main(String[] args) {
DistributedCounter counter = new DistributedCounter("localhost", 6379);
String counterKey = "online_users";
System.out.println("初始在線人數(shù): " + counter.getCount(counterKey));
// 模擬用戶登錄
long user1_login = counter.increment(counterKey);
System.out.println("用戶1登錄,當(dāng)前在線人數(shù): " + user1_login);
long user2_login = counter.increment(counterKey);
System.out.println("用戶2登錄,當(dāng)前在線人數(shù): " + user2_login);
// 模擬用戶登出
long user1_logout = counter.decrement(counterKey);
System.out.println("用戶1登出,當(dāng)前在線人數(shù): " + user1_logout);
System.out.println("最終在線人數(shù): " + counter.getCount(counterKey));
}
}
處理需要重置的計(jì)數(shù)器(例如每日計(jì)數(shù))
在某些場(chǎng)景下,計(jì)數(shù)器需要定期重置,例如統(tǒng)計(jì)每日活躍用戶或每日API調(diào)用次數(shù)。一種常見(jiàn)的錯(cuò)誤做法是先 INCR,再用 EXPIRE 設(shè)置過(guò)期時(shí)間。這種方式存在競(jìng)態(tài)條件:如果在 INCR 執(zhí)行后、EXPIRE 執(zhí)行前,服務(wù)發(fā)生故障,這個(gè)鍵就會(huì)永久存在,導(dǎo)致計(jì)數(shù)器無(wú)法自動(dòng)重置。
正確的做法是使用 Lua 腳本將 INCR 和 EXPIRE 捆綁成一個(gè)原子操作。
Lua 腳本示例
-- increment_with_ttl.lua
local key = KEYS[1]
local ttl = ARGV[1]
local count = redis.call("INCR", key)
-- 如果是第一次增加(即增加后的值為1),則設(shè)置過(guò)期時(shí)間
if count == 1 then
redis.call("EXPIRE", key, ttl)
end
return count
在應(yīng)用程序中,通過(guò) EVAL 命令執(zhí)行此腳本,可以確保增加計(jì)數(shù)和設(shè)置過(guò)期時(shí)間這兩步操作的原子性。
復(fù)雜操作與事務(wù)
如果需要根據(jù)計(jì)數(shù)值執(zhí)行更復(fù)雜的操作(例如,檢查庫(kù)存是否足夠再減庫(kù)存),簡(jiǎn)單的 DECR 可能不夠用。雖然可以使用 WATCH, MULTI, EXEC 事務(wù)來(lái)解決,但這會(huì)增加代碼的復(fù)雜性。 在這種情況下,使用 Lua 腳本通常是更簡(jiǎn)單、更高效的選擇,因?yàn)樗鼘⒄麄€(gè)邏輯封裝在服務(wù)器端作為一個(gè)原子單元執(zhí)行。
總結(jié)
利用 Redis 的 INCR 和 DECR 原子操作是實(shí)現(xiàn)分布式計(jì)數(shù)器的標(biāo)準(zhǔn)且高效的方法。其核心優(yōu)勢(shì)在于 Redis 保證了單個(gè)命令的原子性,從而避免了分布式環(huán)境下的競(jìng)態(tài)條件。對(duì)于需要自動(dòng)重置的計(jì)數(shù)器,強(qiáng)烈建議使用 Lua 腳本來(lái)確保操作的原子性,防止數(shù)據(jù)不一致。
到此這篇關(guān)于Redis利用原子操作(INCR,DECR)實(shí)現(xiàn)分布式計(jì)數(shù)器的文章就介紹到這了,更多相關(guān)Redis分布式計(jì)數(shù)器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳談redis跟數(shù)據(jù)庫(kù)的數(shù)據(jù)同步問(wèn)題
文章討論了在Redis和數(shù)據(jù)庫(kù)數(shù)據(jù)一致性問(wèn)題上的解決方案,主要比較了先更新Redis緩存再更新數(shù)據(jù)庫(kù)和先更新數(shù)據(jù)庫(kù)再更新Redis緩存兩種方案,文章指出,刪除Redis緩存后再更新數(shù)據(jù)庫(kù)的方案更優(yōu),因?yàn)樗梢员苊鈹?shù)據(jù)不一致的問(wèn)題,但可能產(chǎn)生高并發(fā)問(wèn)題2025-01-01
Redis實(shí)現(xiàn)鎖續(xù)期的項(xiàng)目實(shí)踐
本文介紹了使用Redis實(shí)現(xiàn)分布式鎖的續(xù)期,包括使用Lua腳本、Redlock算法和Redisson客戶端等方法,具有一定的參考價(jià)值,感興趣的可以了解一下2024-12-12
大白話講解調(diào)用Redis的increment失敗原因及推薦使用詳解
本文主要介紹了調(diào)用Redis的increment失敗原因及推薦使用,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11
Spring?Boot?3.0x的Redis?分布式鎖的概念和原理
Redis?分布式鎖是一種基于?Redis?的分布式鎖解決方案,它的原理是利用?Redis?的原子性操作實(shí)現(xiàn)鎖的獲取和釋放,從而保證共享資源的獨(dú)占性,這篇文章主要介紹了適合?Spring?Boot?3.0x的Redis?分布式鎖,需要的朋友可以參考下2024-08-08

