当前位置: 首页 > news >正文

如何利用 Redis 的原子操作(INCR, DECR)实现分布式计数器?

在分布式系统中,由于多个服务实例需要共享和修改同一个计数值,实现一个准确、高效的分布式计数器至关重要。Redis 凭借其内存存储的高性能和原子操作命令,成为实现这一功能的理想选择。

核心原理:Redis 的原子操作

Redis 的单线程命令处理模型确保了单个命令的执行是原子性的。 这意味着当一个命令正在执行时,不会被其他客户端的命令打断。对于计数器而言,INCRDECR 这两个命令是核心。

  • INCR key: 将存储在 key 的数字值增一。如果 key 不存在,那么 key 的值会先被初始化为 0,然后再执行 INCR 操作。
  • DECR key: 将 key 中储存的数字值减一。如果 key 不存在,其值同样会先被初始化为 0 再执行 DECR

这两个操作的原子性是实现分布式计数器的基石,它保证了即使在大量并发请求下,计数结果也是准确的,避免了“读取-修改-写入”模式中可能出现的竞态条件。

基本实现方法

实现一个基本的分布式计数器非常简单,只需要为你的计数器定义一个唯一的键(key),然后调用相应的原子命令即可。

使用场景示例:

  • 文章阅读量统计: 每当有用户阅读一篇文章,就对该文章的计数器执行 INCR
  • 在线用户数: 用户登录时执行 INCR,登出时执行 DECR
  • 库存管理: 用户下单时执行 DECR,取消订单或补货时执行 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 0def 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次并发的阅读请求
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("初始在线人数: " + counter.getCount(counterKey));// 模拟用户登录long user1_login = counter.increment(counterKey);System.out.println("用户1登录,当前在线人数: " + user1_login);long user2_login = counter.increment(counterKey);System.out.println("用户2登录,当前在线人数: " + user2_login);// 模拟用户登出long user1_logout = counter.decrement(counterKey);System.out.println("用户1登出,当前在线人数: " + user1_logout);System.out.println("最终在线人数: " + counter.getCount(counterKey));}
}

处理需要重置的计数器(例如每日计数)

在某些场景下,计数器需要定期重置,例如统计每日活跃用户或每日API调用次数。一种常见的错误做法是先 INCR,再用 EXPIRE 设置过期时间。这种方式存在竞态条件:如果在 INCR 执行后、EXPIRE 执行前,服务发生故障,这个键就会永久存在,导致计数器无法自动重置。

正确的做法是使用 Lua 脚本将 INCREXPIRE 捆绑成一个原子操作。

Lua 脚本示例
-- increment_with_ttl.lua
local key = KEYS[1]
local ttl = ARGV[1]local count = redis.call("INCR", key)-- 如果是第一次增加(即增加后的值为1),则设置过期时间
if count == 1 thenredis.call("EXPIRE", key, ttl)
endreturn count

在应用程序中,通过 EVAL 命令执行此脚本,可以确保增加计数和设置过期时间这两步操作的原子性。

复杂操作与事务

如果需要根据计数值执行更复杂的操作(例如,检查库存是否足够再减库存),简单的 DECR 可能不够用。虽然可以使用 WATCH, MULTI, EXEC 事务来解决,但这会增加代码的复杂性。 在这种情况下,使用 Lua 脚本通常是更简单、更高效的选择,因为它将整个逻辑封装在服务器端作为一个原子单元执行。

总结

利用 Redis 的 INCRDECR 原子操作是实现分布式计数器的标准且高效的方法。其核心优势在于 Redis 保证了单个命令的原子性,从而避免了分布式环境下的竞态条件。对于需要自动重置的计数器,强烈建议使用 Lua 脚本来确保操作的原子性,防止数据不一致。

http://www.lryc.cn/news/604506.html

相关文章:

  • 微算法科技MLGO突破性的监督量子分类器:纠缠辅助训练算法为量子机器学习开辟新天地
  • 代码随想录算法训练营第五十五天|图论part5
  • Python设计模式详解:策略模式(Strategy Pattern)实战指南
  • OpenBayes 教程上新丨仅激活 3B 参数可媲美 GPT-4o,Qwen3 深夜更新,一手实测来了!
  • 代码随想录day50图论1
  • Apache Ignite 与 Spring Boot 集成
  • 学习游戏制作记录(冻结敌人时间与黑洞技能)7.30
  • nginx安装配置Lua模块的支持
  • 我的世界模组开发教程——资源(1)
  • 【AI】入门级提示词模板:适用于ChatGPT、文心一言等主流模型
  • 【ee类保研面试】数学类---线性代数
  • 从0开始学习R语言--Day62--RE插补
  • JVM对象创建与内存分配机制深度剖析
  • 基于Catboost的铁路交通数据分析及列车延误预测系统的设计与实现【全国城市可选、欠采样技术】
  • 【JVM篇11】:分代回收与GC回收范围的分类详解
  • 数据分析师进阶——95页零售相关数据分析【附全文阅读】
  • JVM 性能调优实战:让系统性能 “飞” 起来的核心策略
  • 观远 ChatBI 完成 DeepSeek-R1 大模型适配:开启智能数据分析跃升新篇
  • 【Spring】一文了解SpringMVC的核心功能及工作流程,以及核心组件及注解
  • Linux 日志管理与时钟同步详解
  • GIS工程师面试题
  • GitHub 热门项目 PandaWiki:零门槛搭建智能漏洞库,支持 10 + 大模型接入
  • UG NX二次开发(Python)-根据封闭曲线创建拉伸特征
  • Class27GoogLeNet
  • 实用性方案:高效处理图片拼接的正确打开方式
  • sed编程入门
  • [Agent开发平台] Coze Loop开源 | 前端 | typescript架构API速查
  • Python Pandas.get_dummies函数解析与实战教程
  • 【iOS】weak修饰符
  • 磁盘io查看命令iostat与网络连接查看命令netstat