redis数量累加api,能够保证请求时间超时和保证并发安全,比如因网络抖动导致的重试,进而出现累加的幂等性问题
Redis 高并发安全累加方案:解决超时重试与幂等性问题
针对网络抖动导致的重试和并发安全需求,我将设计一个完整的幂等累加方案,确保在超时、重试和高并发场景下的数据一致性。
整体解决方案设计
核心思路
-
幂等控制:使用唯一请求ID防止重试导致重复累加
-
原子操作:Lua脚本保证操作的原子性
-
超时管理:客户端+服务端双重超时控制
-
并发安全:Redis单线程特性保证操作顺序性
完整实现方案
1. 幂等累加Lua脚本
-- KEYS[1]: 计数器key
-- KEYS[2]: 请求ID存储key (格式: idempotent:<request_id>)
-- ARGV[1]: 请求ID
-- ARGV[2]: 增量值
-- ARGV[3]: 请求记录TTL (秒)
-- ARGV[4]: 计数器TTL (秒)
-- 检查请求是否已处理
local handled = redis.call('GET', KEYS[2])
if handled then
return {tonumber(handled), 1} -- 返回结果和状态码1(已处理)
end
-- 处理计数器逻辑
local current
if redis.call('EXISTS', KEYS[1]) == 1 then
current = redis.call('INCRBY', KEYS[1], ARGV[2])
else
current = tonumber(ARGV[2])
redis.call('SET', KEYS[1], current, 'EX', tonumber(ARGV[4]))
end
-- 记录请求结果
redis.call('SET', KEYS[2], current, 'EX', tonumber(ARGV[3]))
return {current, 0} -- 返回结果和状态码0(新处理)
2. Java客户端实现(带重试策略)
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.exceptions.JedisConnectionException;
import java.util.*;
public class IdempotentRedisIncrementer {
private final JedisPool jedisPool;
private final int maxRetries;
private final long retryDelayMs;
private final int requestIdTtl;
private final int counterTtl;
public IdempotentRedisIncrementer(JedisPool jedisPool, int maxRetries,
long retryDelayMs, int requestIdTtl,
int counterTtl) {
this.jedisPool = jedisPool;
this.maxRetries = maxRetries;
this.retryDelayMs = retryDelayMs;
this.requestIdTtl = requestIdTtl;
this.counterTtl = counterTtl;
}
public IncrementResult safeIncrement(String counterKey, long delta) {
String requestId = UUID.randomUUID().toString();
String requestKey = "idempotent:" + requestId;
int attempt = 0;
Jedis jedis = null;
while (attempt <= maxRetries) {
attempt++;
try {
jedis = jedisPool.getResource();
// Lua脚本参数
List<String> keys = Arrays.asList(counterKey, requestKey);
List<String> args = Arrays.asList(
requestId,
String.valueOf(delta),
String.valueOf(requestIdTtl),
String.valueOf(counterTtl)
);
// 加载Lua脚本(生产环境应预加载)
String script = loadLuaScript();
// 执行脚本
List<Long> result = (List<Long>) jedis.eval(
script,
keys,
args
);
long newValue = result.get(0);
int status = result.get(1).intValue();
return new IncrementResult(newValue, status, requestId);
} catch (JedisConnectionException e

