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

