redis string 不适合单独实现点赞功能,因其无法原子性地完成“判断用户是否已点赞+更新计数”操作,易导致重复点赞和并发计数不准;应结合 set 记录用户状态,并用 lua 脚本保障原子性。

用 Redis 的 String 类型实现点赞功能,在高并发下容易出错,核心问题在于“重复点赞”和“并发计数不准”。String 本身不支持原子性增减与条件判断,直接用 INCR 或 SET 无法解决“用户是否已点过”的判断逻辑。所以单纯靠 String 并不适合做完整点赞功能——但可以配合其他结构或策略来用,关键在于设计边界和取舍。
为什么 String 不适合单独承载点赞逻辑
Redis String 是键值对,适合存单个数值(比如总点赞数),但它无法记录“谁点了”,也无法在一次操作中完成“检查 + 更新”。例如:
- 先用
GET user:like:post:123查是否点过 → 再决定是否INCR post:likes:123:中间存在竞态,两个请求可能同时读到 null,都执行 incr,导致重复计数。 - 用
SETNX记录用户状态(如user:like:post:123:uid456值为 1)→ 可防重复,但总点赞数还得额外维护,且大量用户会产生海量 key。
推荐方案:String + Set 组合(实用且易落地)
把“总点赞数”交给 String,“用户是否点过”交给 Set,利用 Redis 原子命令协同工作:
- 用
SADD like:post:123 uid456添加用户 ID 到集合:天然去重,返回 1 表示新增成功(即首次点赞),0 表示已存在。 - 只有 SADD 返回 1 时,才执行
INCR post:likes:123更新总赞数。 - 取消点赞则用
SREM like:post:123 uid456+DECR post:likes:123(注意需判断 SREM 是否真删掉了元素)。
Java 示例(Lettuce 客户端):
Java JDK 25 来自 OpenJDK 官方归档,版本为 JDK 25,本条下载地址已指向官方 Windows x64 zip 安装包直链,适合调试旧项目或兼容旧版 Java 运行环境。
public boolean likePost(Long postId, Long userId) {
String likeSetKey = "like:post:" + postId;
String likeCountKey = "post:likes:" + postId;
<pre class="brush:java;toolbar:false;">// 原子添加用户到集合,返回是否新增
Long added = syncCommands.sadd(likeSetKey, String.valueOf(userId));
if (added == 1L) {
syncCommands.incr(likeCountKey);
return true; // 点赞成功
}
return false; // 已点过}
进阶优化:用 Lua 脚本保证原子性
上面 Java 代码中 SADD 和 INCR 是两次网络调用,极端情况下仍有微小窗口(比如 SADD 成功后服务宕机,INCR 未执行)。更稳妥的做法是把整个逻辑写进 Lua 脚本,在 Redis 端原子执行:
-- like.lua
local likeSetKey = KEYS[1]
local likeCountKey = KEYS[2]
local userId = ARGV[1]
<p>local added = redis.call("SADD", likeSetKey, userId)
if added == 1 then
redis.call("INCR", likeCountKey)
return 1
else
return 0
end</p>Java 中加载并执行:
DefaultScript script = DefaultScript.script(luaLikeScript);
Long result = syncCommands.eval(script, ReturnType.INTEGER,
Arrays.asList("like:post:123", "post:likes:123"),
String.valueOf(456));
注意事项与边界处理
- 内存成本:每个帖子一个 Set,用户量大时需评估内存。可考虑定期归档冷帖的 like Set,或用 HyperLogLog 估算去重数(但不支持取消点赞)。
- 数据一致性:如果业务要求强一致,建议搭配数据库双写(Redis 更新成功后再异步落库),并增加补偿任务校验。
- 缓存穿透:对非法 postId 查询,加布隆过滤器或空值缓存,避免打穿 Redis 和 DB。
-
点赞数回源:初始点赞数可从 DB 加载到 Redis,用
SETNX防覆盖;后续只更新 Redis,定时或按需同步回 DB。
不复杂但容易忽略:String 在点赞场景里最稳的用法,就是老老实实当“计数器”,别让它背“判断逻辑”的锅。把职责拆清楚,再用原子命令或 Lua 把它们串起来,高并发下的点赞就能既快又准。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










