不能直接用INCR统计连续签到天数,因其无法判断昨日是否签到及断签归零;须用Lua脚本原子执行:检查今日是否已签、查询昨日签到状态、条件更新连续天数和日期字段。

为什么不能直接用 INCR 统计连续签到天数
连续签到不是简单累加,它依赖「上一天是否签到」这个状态。如果只用 INCR,断签后无法自动归零,也无法回溯判断昨天有没有签——Redis 原生命令没有时间维度的条件跳转能力。
必须借助 Lua 脚本在服务端原子执行:读取昨日签到状态 → 判断是否连续 → 更新今日状态和连续计数。否则客户端读-改-写(read-modify-write)会因并发导致计数错乱。
常见错误现象:(error) ERR Error running script (call to f_...): @user_script:7: user_script:7: attempt to compare number with boolean,通常是没处理 GET 返回 nil 的情况。
EVAL 脚本里怎么安全获取「昨天是否签到」
用 KEYS[1] 存用户签到集合(如 sign:uid:123),ARGV[1] 传今天日期(如 20240520)。昨天日期靠 Lua 字符串操作生成,不依赖服务器时间,避免时区/漂移问题。
关键点:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 用
redis.call("SISMEMBER", KEYS[1], yesterday)查昨天,返回1或0,不是布尔值 - 不能用
redis.call("GET", ...)查日期键——签到记录应存为 Set,支持去重、交集等后续扩展 - 昨天日期必须补零:若
ARGV[1]是"20240520",需截取前6位 +tonumber(末两位)-1,再处理跨月(如 20240501 → 20240430),但简单场景可先假设不跨月,用string.sub(ARGV[1],1,6) .. string.format("%02d", tonumber(string.sub(ARGV[1],7,8))-1)
连续天数字段该存在哪?用 Hash 还是 String?
存在同一个 key 的 Hash 里最合理,比如 HSET sign:uid:123 today 20240520 streak 5。理由:
- String 类型每次要
GET+SET全量更新,无法只改streak字段 - Hash 支持
HGET/HSET单字段操作,且HGETALL可一次拉出所有签到元数据 - 避免多 key 维护(如
sign:uid:123:streak和sign:uid:123:today),减少 key 数量和过期管理成本
注意:HINCRBY 不能用于条件递增,所以仍需 Lua 里做 if-else 判断后再 redis.call("HSET", KEYS[1], "streak", new_streak)。
脚本里如何防止同一天重复签到影响连续性
核心是先查今天是否已签到,再决定是否更新连续计数。不能只靠 Set 的 SADD 返回值(1=新增,0=已存在),因为连续天数逻辑必须和「是否新增」强绑定。
实操步骤:
- 用
redis.call("SISMEMBER", KEYS[1], ARGV[1])检查今天是否已存在 - 若已存在,直接
return {0, current_streak}(0 表示未新增,current_streak 从HGET读) - 若不存在,才执行:添加今日日期到 Set + 判断昨日是否签到 + 计算新 streak +
HSET更新 - 整个过程用
local res = redis.call(...)捕获每步结果,避免nil传给算术运算
容易被忽略的是:跨年、跨月的日期计算在 Lua 里没有内置日历库,生产环境建议把日期计算逻辑下沉到业务层,Lua 脚本只做原子状态更新。

















