【JAVA架构师成长之路】【Redis】第18集:Redis实现分布式高并发加减计数器

30分钟自学教程:Redis实现分布式高并发加减计数器

目标

  1. 掌握Redis原子操作实现分布式计数器的原理。
  2. 学会使用Lua脚本和Redisson处理高并发加减。
  3. 能够设计应对网络抖动、节点故障的容错方案。

教程内容

0~2分钟:分布式计数器的核心需求
  • 场景:秒杀库存扣减、实时投票计数、API调用限流等。
  • 挑战
    • 原子性:多节点并发操作需保证结果正确。
    • 高性能:支持万级QPS。
    • 容错:网络异常时数据不丢失。

2~5分钟:基础实现------INCR/DECR命令(Java示例)
java 复制代码
// 初始化计数器  
redisTemplate.opsForValue().set("product:stock:1001", "1000");  

// 原子增加  
public Long incr(String key, long delta) {  
    return redisTemplate.opsForValue().increment(key, delta);  
}  

// 原子减少  
public Long decr(String key, long delta) {  
    return redisTemplate.opsForValue().decrement(key, delta);  
}  

// 扣减库存示例  
public boolean deductStock(String productId, int quantity) {  
    Long stock = decr("product:stock:" + productId, quantity);  
    return stock != null && stock >= 0;  
}  

问题 :直接使用DECR可能扣减到负数(需结合Lua脚本解决)。


5~12分钟:解决方案1------Lua脚本保证原子性
  • 原理:通过Lua脚本在Redis服务端执行多个命令,确保原子性。
java 复制代码
// Lua脚本:仅当库存充足时扣减  
String script =  
    "local current = tonumber(redis.call('GET', KEYS[1]))\n" +  
    "if current >= tonumber(ARGV[1]) then\n" +  
    "   return redis.call('DECRBY', KEYS[1], ARGV[1])\n" +  
    "else\n" +  
    "   return -1\n" +  
    "end";  

// 执行脚本  
public Long safeDeduct(String key, int quantity) {  
    DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();  
    redisScript.setScriptText(script);  
    redisScript.setResultType(Long.class);  
    return redisTemplate.execute(redisScript, Collections.singletonList(key), String.valueOf(quantity));  
}  

验证 :调用safeDeduct("product:stock:1001", 2),库存不足时返回-1


12~20分钟:解决方案2------Redisson分布式锁
  • 适用场景:复杂计数逻辑(如先查询再计算)。
java 复制代码
// Redisson分布式锁实现  
public boolean deductWithLock(String productId, int quantity) {  
    String key = "product:stock:" + productId;  
    RLock lock = redissonClient.getLock(key + ":lock");  
    try {  
        if (lock.tryLock(1, 10, TimeUnit.SECONDS)) {  
            Integer stock = Integer.parseInt(redisTemplate.opsForValue().get(key));  
            if (stock >= quantity) {  
                redisTemplate.opsForValue().set(key, String.valueOf(stock - quantity));  
                return true;  
            }  
            return false;  
        }  
    } finally {  
        lock.unlock();  
    }  
    return false;  
}  

注意:锁粒度应精确到资源级别(如按商品ID加锁)。


20~25分钟:解决方案3------集群模式与分片计数
  • 分片设计:将计数器按哈希分片,分散压力。
java 复制代码
// 分片计数(伪代码)  
public void incrSharded(String key, int shards, long delta) {  
    int shardId = Math.abs(key.hashCode()) % shards;  
    String shardKey = key + ":shard_" + shardId;  
    redisTemplate.opsForValue().increment(shardKey, delta);  
}  

// 总分计算(遍历所有分片求和)  
public long getTotal(String key, int shards) {  
    long total = 0;  
    for (int i = 0; i < shards; i++) {  
        String shardKey = key + ":shard_" + i;  
        String value = redisTemplate.opsForValue().get(shardKey);  
        total += Long.parseLong(value == null ? "0" : value);  
    }  
    return total;  
}  

适用场景:超高并发(如百万QPS)的全局计数器。


25~28分钟:应急处理方案
  1. 重试机制:网络抖动时自动重试。
java 复制代码
public boolean safeDeductWithRetry(String key, int quantity, int maxRetries) {  
    int retries = 0;  
    while (retries < maxRetries) {  
        try {  
            Long result = safeDeduct(key, quantity);  
            return result != null && result >= 0;  
        } catch (RedisConnectionFailureException e) {  
            retries++;  
            Thread.sleep(100);  
        }  
    }  
    return false;  
}  
  1. 降级策略:Redis不可用时降级到本地计数器。
java 复制代码
// 本地Guava缓存降级  
private Cache<String, Long> localCache = CacheBuilder.newBuilder().build();  

public long getStockWithFallback(String key) {  
    try {  
        return Long.parseLong(redisTemplate.opsForValue().get(key));  
    } catch (Exception e) {  
        return localCache.get(key, () -> 0L);  
    }  
}  
  1. 监控告警:通过Prometheus监控计数器异常。
yaml 复制代码
# Prometheus配置示例  
- job_name: 'redis_counters'  
  static_configs:  
    - targets: ['redis_host:9121']  # Redis Exporter端口  

28~30分钟:总结与优化方向
  • 核心原则:原子操作优先、分片减压、降级容灾。
  • 高级优化
    • 使用Redis Streams实现异步计数。
    • 结合数据库持久化最终结果。
    • 使用Pipelining减少网络往返。

练习与拓展

练习

  1. 实现一个分片计数器,支持动态增加分片数量。
  2. 使用Redisson锁实现"先查询库存再扣减"的原子操作。

推荐拓展

  1. 研究Redis的HyperLogLog实现基数统计。
  2. 学习Redis事务的WATCH/MULTI/EXEC机制。
  3. 探索分布式协调框架(如ZooKeeper)的计数器实现。
相关推荐
DBWYX32 分钟前
redis
java·redis·mybatis
〆、风神44 分钟前
从零搭建高可用分布式限流组件:设计模式与Redis令牌桶实践
redis·分布式·设计模式
未来会更好yes9 小时前
Centos 7.6安装redis-6.2.6
linux·redis·centos
熏鱼的小迷弟Liu11 小时前
【Redis】Redis Zset实现原理:跳表+哈希表的精妙设计
数据库·redis·散列表
观无14 小时前
Redis远程链接应用案例
数据库·redis·缓存·c#
好想有猫猫16 小时前
【Redis】服务端高并发分布式结构演进之路
数据库·c++·redis·分布式·缓存
孔令飞17 小时前
使用 Go 与 Redis Streams 构建可靠的事件驱动系统
redis·ai·云原生·golang·kubernetes
yuanlaile17 小时前
Go全栈_Golang、Gin实战、Gorm实战、Go_Socket、Redis、Elasticsearch、微服务、K8s、RabbitMQ全家桶
linux·redis·golang·k8s·rabbitmq·gin
高效匠人18 小时前
FastAPI + Redis Pub/Sub + WebSocket 组合解决方案的详细介绍
redis·websocket·fastapi
lovebugs18 小时前
Redis的高性能奥秘:深入解析IO多路复用与单线程事件驱动模型
redis·后端·面试