在内容平台(如微博、抖音、B站)中,点赞是一个极其核心的交互行为。本篇文章将分析如何设计一个点赞模块。我们将对比不同方案的优缺点,并最终选用 Redis + Lua 脚本来实现。
点赞模块常见技术选型对比
我们先来对比常见的几种点赞实现方案:
Redis + Lua 脚本架构设计方案
我们最终选择 Redis + Lua 脚本,理由如下:
Redis 是内存数据库,极致的读写性能(10w+ QPS);
使用
Set
存储用户点赞关系,天然支持去重、取消点赞;使用
ZSet
存储用户点赞过的内容,支持时间排序;Lua 脚本确保多步操作的原子性,防止并发异常。
数据结构设计
Lua 脚本实现点赞逻辑(以点赞为例)
点赞操作应包括:
判断该用户是否已点赞
已经是Set数据结构了,是不是没必要?
虽然 Redis 的重复写入不会导致错误,但:
SADD
重复元素虽然无效,但 Redis 仍然要做哈希查找 + hash 写尝试在高并发下,这个会带来无意义的系统负担
加个判断可以减少 Redis 的指令开销(尤其是涉及多个 Key 的 Lua 脚本)
如果未点赞:
添加到文章的用户点赞集合;
添加到用户的点赞文章集合;
-- like.lua
-- KEYS[1] = like:user:<postId>
-- KEYS[2] = like:posts:<userId>
-- ARGV[1] = userId
-- ARGV[2] = postId
-- ARGV[3] = 当前时间戳
if redis.call("SISMEMBER", KEYS[1], ARGV[1]) == 0 then
redis.call("SADD", KEYS[1], ARGV[1])
redis.call("ZADD", KEYS[2], ARGV[3], ARGV[2])
return 1
else
return 0
end
Spring Boot 调用 Lua 脚本代码(以点赞为例)
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public boolean likePost(Long userId, Long postId) {
String luaScript = """
if redis.call("SISMEMBER", KEYS[1], ARGV[1]) == 0 then
redis.call("SADD", KEYS[1], ARGV[1])
redis.call("ZADD", KEYS[2], ARGV[3], ARGV[2])
return 1
else
return 0
end
""";
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
script.setScriptText(luaScript);
script.setResultType(Long.class);
List<String> keys = List.of(
"like:user:" + postId,
"like:posts:" + userId
);
List<String> args = List.of(
String.valueOf(userId),
String.valueOf(postId),
String.valueOf(System.currentTimeMillis())
);
Long result = redisTemplate.execute(script, keys, args.toArray());
return Long.valueOf(1).equals(result);
}
其他功能(附代码)
点击这里下载源码示例:Like-Module-Redis-Lua
总结
Redis 是实现点赞模块的首选方案,结合 Lua 脚本:
✅ 保证操作原子性
✅ 性能极高,支持百万并发点赞
✅ 支持数据结构丰富(去重、排序)
✅ 易于扩展“我的喜欢”“点赞排行榜”等功能