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

 更新时间:2025年07月31日 09:05:35   作者:冰糖心书房  
在分布式系统中,由于多个服务实例需要共享和修改同一个计数值,实现一个准确、高效的分布式计数器至关重要,下面我们就来看看具体实现方法吧

在分布式系统中,由于多个服务实例需要共享和修改同一个计数值,实现一个准确、高效的分布式计数器至关重要。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 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次并发的阅读请求
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 then
    redis.call("EXPIRE", key, ttl)
end

return count

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

复杂操作与事务

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

总结

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

到此这篇关于Redis利用原子操作(INCR,DECR)实现分布式计数器的文章就介绍到这了,更多相关Redis分布式计数器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详谈redis跟数据库的数据同步问题

    详谈redis跟数据库的数据同步问题

    文章讨论了在Redis和数据库数据一致性问题上的解决方案,主要比较了先更新Redis缓存再更新数据库和先更新数据库再更新Redis缓存两种方案,文章指出,删除Redis缓存后再更新数据库的方案更优,因为它可以避免数据不一致的问题,但可能产生高并发问题
    2025-01-01
  • Redis 命令的详解及简单实例

    Redis 命令的详解及简单实例

    这篇文章主要介绍了Redis 命令的详解及简单实例的相关资料,这里提供基础语法及使用实例,需要的朋友可以参考下
    2017-08-08
  • Redis集群部署Windows版本的过程详解

    Redis集群部署Windows版本的过程详解

    本文介绍了如何在Windows系统上部署Redis集群,包括从GitHub下载Windows版本的Redis、配置文件的创建、启动脚本的编写以及集群的启动和配置过程,感兴趣的朋友一起看看吧
    2025-03-03
  • Redis实现锁续期的项目实践

    Redis实现锁续期的项目实践

    本文介绍了使用Redis实现分布式锁的续期,包括使用Lua脚本、Redlock算法和Redisson客户端等方法,具有一定的参考价值,感兴趣的可以了解一下
    2024-12-12
  • redis事务常用操作详解

    redis事务常用操作详解

    在本篇文章里小编给大家分享了关于redis事务常用操作的相关知识点内容,有兴趣的朋友们可以跟着学习参考下。
    2019-07-07
  • 大白话讲解调用Redis的increment失败原因及推荐使用详解

    大白话讲解调用Redis的increment失败原因及推荐使用详解

    本文主要介绍了调用Redis的increment失败原因及推荐使用,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-11-11
  • Spring Boot 3.0x的Redis 分布式锁的概念和原理

    Spring Boot 3.0x的Redis 分布式锁的概念和原理

    Redis 分布式锁是一种基于 Redis 的分布式锁解决方案,它的原理是利用 Redis 的原子性操作实现锁的获取和释放,从而保证共享资源的独占性,这篇文章主要介绍了适合 Spring Boot 3.0x的Redis 分布式锁,需要的朋友可以参考下
    2024-08-08
  • 浅谈Redis中LFU算法源码解析

    浅谈Redis中LFU算法源码解析

    Redis的LFU淘汰算法主要用于 maxmemory-policy 设置为allkeys-lfu或volatile-lfu时,以最少使用频率的键进行淘汰,本文主要介绍了浅谈Redis中LFU算法源码解析,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧
    2025-04-04
  • 深入理解redis分布式锁和消息队列

    深入理解redis分布式锁和消息队列

    本篇文章主要介绍了深入理解redis分布式锁和消息队列,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-07-07
  • redis缓存延时双删的原因分析

    redis缓存延时双删的原因分析

    延时双删就是在增删改某实体类的时候,要对该实体类的缓存进行清空,清空的位置在数据库操作方法的前后,这篇文章主要介绍了redis缓存为什么要延时双删,需要的朋友可以参考下
    2022-08-08

最新评论