在内容平台(如微博、抖音、B站)中,点赞是一个极其核心的交互行为。本篇文章将分析如何设计一个点赞模块。我们将对比不同方案的优缺点,并最终选用 Redis + Lua 脚本来实现。

点赞模块常见技术选型对比

我们先来对比常见的几种点赞实现方案:

技术方案

优点

缺点

适用场景

关系型数据库

实现简单;支持持久化;查询方便

写入慢;不适合高并发;重复点赞判断麻烦

小规模系统,低并发

Elasticsearch

支持倒排索引查询;全文搜索

写入延迟高;点赞逻辑复杂;不适合计数

搜索、推荐系统

消息队列(Kafka)

支持异步解耦,便于日志分析

只做中转,不具备查询和状态存储能力

搜索、推荐系统

Redis(Set/ZSet)

高性能、高并发、结构丰富,支持去重和排序

数据易丢失(需落盘);内存成本高

高并发、实时交互系统(推荐)

Redis + Lua 脚本

保证操作原子性;支持复杂逻辑

开发成本略高;需要了解 Lua 脚本

点赞、抢购、计数场景(推荐)

Redis + Lua 脚本架构设计方案

我们最终选择 Redis + Lua 脚本,理由如下:

  • Redis 是内存数据库,极致的读写性能(10w+ QPS);

  • 使用 Set 存储用户点赞关系,天然支持去重、取消点赞

  • 使用 ZSet 存储用户点赞过的内容,支持时间排序

  • Lua 脚本确保多步操作的原子性,防止并发异常。

数据结构设计

功能

Redis Key 设计

数据结构

描述

记录某篇文章点赞用户

like:user:<postId>

Set

存储 userId,防止重复点赞

记录某用户点赞文章

like:posts:<userId>

ZSet

成员为 postId,score 为点赞时间戳

Lua 脚本实现点赞逻辑(以点赞为例)

点赞操作应包括:

  1. 判断该用户是否已点赞

    1. 已经是Set数据结构了,是不是没必要?

    2. 虽然 Redis 的重复写入不会导致错误,但:

      • SADD 重复元素虽然无效,但 Redis 仍然要做哈希查找 + hash 写尝试

      • 高并发下,这个会带来无意义的系统负担

      • 加个判断可以减少 Redis 的指令开销(尤其是涉及多个 Key 的 Lua 脚本)

  2. 如果未点赞:

    1. 添加到文章的用户点赞集合;

    2. 添加到用户的点赞文章集合;

-- 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);
    }

其他功能(附代码)

功能

操作建议

取消点赞

使用 Lua 脚本 + SREM + ZREM

查询点赞数

直接使用 SCARD

查询是否点赞

使用 SISMEMBER

查询我点赞过的文章

使用 ZREVRANGE like:posts:<userId>

点击这里下载源码示例:Like-Module-Redis-Lua

总结

Redis 是实现点赞模块的首选方案,结合 Lua 脚本:

  • ✅ 保证操作原子性

  • ✅ 性能极高,支持百万并发点赞

  • ✅ 支持数据结构丰富(去重、排序)

  • ✅ 易于扩展“我的喜欢”“点赞排行榜”等功能

我是三叶的狗,QQ2279538834