问题背景:线上有一个分布式计数器(统计每 5 秒内的执行数量),计数器基于 redis Incr 实现,实现方案: 执行 Incr,如果计数小于等于 10,给 key 设置 5 秒过期时间。
目前线上业务服务器大约 100 台实例,redis 大约 30 台(每台实例都可以写入,猜测是 redis cluster 集群方案,并不确定),这个函数调用 QPS 约 1000+
目前问题:redis key 偶然出现永久不过期(最近 2 个月,出现了 2 次),导致计数器不能清零
具体代码:
/**
分布式计数器,每次调用数量加一,返回计数器增加后的值
*/
func incrCount() int {
redisKey:="xxxxxx"
count,err:=redis.Incr(redisKey)
if err!=nil {
Logger.Warn("Redis Incr Error! Error Info : %+v",err) // 这里没有日志输出
return 0
}
// 设置 10 次 expire,防止失败
if count <= 10 {
res,err:= redis.Expire(redisKey,5) // 给 key 设置 5s 过期
if err!=nil || res != 1 {
Logger.Warn("Redis Set Expire Error! Error Info:%+v",err) // 这里没有日志输出
}
}
return count
}
查过日志,代码里面 2 处 Warn 日志都未打印,代码无其他报错,业务代码容器无异常重启,宿主机负载无异常,网络无异常,redis 无异常。
原因猜测:
1 、redis 单机能保证 incr 操作原子性,但是集群情况不能保证原子性
2 、集群内部互相同步的时候丢了过期时间
3 、Incr 和 Expire 分 2 次调用,Incr 成功,但是 Expire 失败