资讯动态

高并发点赞系统实战:Spring Boot+Redis+MySQL架构设计与实现

发布时间:2026/8/25 2:02:23 来源:尧图企业网站定制
1. 背景与核心概念在社交媒体和内容平台的开发与运营中“点赞”功能是用户互动最基础、最核心的指标之一。它不仅是用户表达对内容认可的直接方式更是平台算法进行内容分发、热度计算和用户画像构建的关键数据源。无论是微博、TwitterX、CSDN博客还是抖音点赞行为的背后都涉及一套复杂的技术实现和产品逻辑。本文将从开发者的视角深入剖析一个典型的“点赞”功能模块。我们将超越简单的“点击按钮数字1”的表面逻辑探讨其背后的技术架构、数据一致性挑战、高并发处理以及在实际工程中可能遇到的各类“坑点”。通过一个完整的实战案例我们将构建一个支持高并发的点赞系统涵盖数据库设计、缓存策略、接口防刷、数据异步同步等核心环节。本文适合的读者后端开发工程师希望深入理解互动功能的技术细节与架构设计。全栈开发者需要独立实现社区或内容平台的点赞模块。对系统设计感兴趣的开发者想了解如何设计一个应对高流量、保证数据一致性的服务。技术爱好者好奇一个简单功能背后复杂的技术支撑。学完后你将掌握点赞业务的核心数据模型与关系设计。利用 Redis 缓存应对高并发点赞/取消点赞的经典方案。保证缓存与数据库最终一致性的异步任务设计。接口幂等性与防刷策略的实现。一套可运行、可扩展的点赞系统完整代码。2. 环境准备与版本说明我们将使用 Spring Boot 作为基础框架配合 Redis 和 MySQL 来实现点赞系统。这是一个在 Java 后端领域非常经典和通用的技术栈。核心环境与版本JDK: 17 或 8LTS 版本均可本文示例基于 JDK 17Spring Boot: 3.1.x 或 2.7.x本文示例基于 3.1.5核心逻辑兼容主流版本构建工具: Maven 3.6数据库: MySQL 8.0 或 5.7缓存: Redis 6.xIDE: IntelliJ IDEA 或 Eclipse (STS)关键依赖 (Mavenpom.xml):以下依赖是项目基石请确保正确引入。dependencies !-- Spring Boot Web -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- Spring Data JPA (简化数据库操作) -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-jpa/artifactId /dependency !-- MySQL 驱动 -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency !-- Spring Data Redis -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency !-- 连接池 -- dependency groupIdorg.apache.commons/groupId artifactIdcommons-pool2/artifactId /dependency !-- Lombok (简化代码非必需) -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency !-- 测试 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency /dependencies项目结构预览一个清晰的项目结构有助于管理和理解代码。like-system-demo ├── src/main/java/com/example/likesystem │ ├── controller // 控制器层处理HTTP请求 │ │ └── LikeController.java │ ├── service // 业务逻辑层 │ │ ├── LikeService.java │ │ └── impl │ │ └── LikeServiceImpl.java │ ├── repository // 数据访问层 (JPA) │ │ └── UserLikeRepository.java │ ├── entity // 实体类 │ │ └── UserLike.java │ ├── dto // 数据传输对象 │ │ └── LikeRequestDTO.java │ ├── config // 配置类 │ │ └── RedisConfig.java │ ├── task // 定时任务 │ │ └── LikeSyncTask.java │ └── LikeSystemApplication.java // 启动类 ├── src/main/resources │ ├── application.yml // 主配置文件 │ └── ... └── pom.xml版本兼容性说明Spring Boot 3.x 与 2.x 在部分配置和注解上略有差异如javax包迁移到jakarta。本文代码以 Spring Boot 3.x 为基础编写但核心业务逻辑是通用的。如果你使用的是 Spring Boot 2.x只需注意import语句中javax.persistence.*的差异并将配置文件中的jdbc:mysql://驱动类保持为com.mysql.cj.jdbc.Driver即可。3. 核心原理与架构拆解在动手编码前我们必须理解点赞系统面临的核心挑战和通用解决方案。直接对数据库进行“读-改-写”操作在并发场景下是灾难性的会导致性能瓶颈、数据不一致甚至超卖等问题。3.1 读写分离与缓存优先核心思路是写操作走缓存读操作优先读缓存数据异步持久化到数据库。写操作点赞/取消用户点击按钮后请求首先到达后端。后端并不直接操作MySQL的likes_count字段而是将这次点赞行为作为一个事件写入Redis的高性能内存存储中。这个过程非常快通常能在毫秒级完成完美支撑高并发。读操作获取点赞数/状态当需要显示文章的点赞数或判断当前用户是否点赞时优先从Redis中查询。如果缓存中没有如刚启动则从MySQL中查询并回写到缓存。数据同步通过一个独立的定时任务定期将Redis中积累的点赞数据例如记录用户对内容的点赞关系同步到MySQL中并更新MySQL中的计数。这保证了数据的最终一致性。3.2 数据结构设计在Redis中我们主要使用两种数据结构Hash (哈希表) - 存储用户点赞关系Key:like:article:{articleId}Field:userId(点赞用户的ID)Value:1(代表已点赞) 或0(代表未点赞/已取消实践中更常用直接删除 field)作用快速判断用户userId是否对文章articleId点过赞。查询复杂度 O(1)。String (字符串) / Hash - 存储点赞总数方案A (String): Key:count:article:{articleId}, Value:12345。操作简单但每次更新需要GETSET非原子性。方案B (Hash): Key:count:article, Field:{articleId}, Value:12345。可以将所有文章计数放在一个Hash中方便批量操作。方案C (直接使用关系Hash计数): 通过对like:article:{articleId}这个 Hash 执行HLEN命令可以直接得到点赞用户数无需单独维护计数。这是最常用且一致性最好的方案我们将在实战中采用。在MySQL中我们至少需要两张表内容表 (e.g.,article)存储文章/帖子基本信息其中包含一个like_count字段用于存储从 Redis 同步过来的、或作为缓存的点赞总数。用户点赞关系表 (e.g.,user_like)记录用户和内容的点赞关系包含复合唯一索引(user_id, target_id, target_type)防止重复点赞。这是我们的“事实表”。3.3 最终一致性 vs 强一致性我们的架构选择了最终一致性。这意味着在用户点赞后Redis中的计数立即更新但MySQL中的like_count可能稍后例如几秒后才更新。在这段极短的时间窗口内从MySQL直接查到的点赞数可能是旧的。对于点赞这种业务这是完全可以接受的。用户更关心自己的操作是否成功即时反馈而总点赞数稍有延迟并不影响体验。强一致性会带来巨大的性能开销和复杂度对于高并发互动场景是得不偿失的。4. 完整实战案例现在我们开始从零搭建这个点赞系统。4.1 数据库与表结构设计首先在MySQL中创建数据库和表。-- 创建数据库 CREATE DATABASE IF NOT EXISTS like_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE like_system; -- 内容表这里以文章为例 CREATE TABLE article ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键ID, title varchar(255) NOT NULL COMMENT 文章标题, content text COMMENT 文章内容, like_count int(11) NOT NULL DEFAULT 0 COMMENT 点赞总数从Redis同步, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT文章表; -- 用户点赞关系表 CREATE TABLE user_like ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键ID, user_id bigint(20) NOT NULL COMMENT 用户ID, target_id bigint(20) NOT NULL COMMENT 点赞目标ID如文章ID, target_type tinyint(4) NOT NULL COMMENT 点赞目标类型1-文章2-评论..., status tinyint(1) NOT NULL DEFAULT 1 COMMENT 状态1-点赞0-取消, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_user_target (user_id,target_id,target_type) COMMENT 防止重复点赞 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户点赞表;关键点user_like表的唯一索引uk_user_target是保证数据库层数据唯一性的最后防线。4.2 项目配置与实体类1. 配置文件 (application.yml):server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/like_system?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: yourpassword # 请替换为你的数据库密码 jpa: hibernate: ddl-auto: update # 首次启动可设为update生产环境建议使用none通过SQL脚本管理 show-sql: true # 开发时显示SQL生产关闭 properties: hibernate: dialect: org.hibernate.dialect.MySQL8Dialect format_sql: true data: redis: host: localhost port: 6379 password: # 如果Redis有密码则填写 database: 0 lettuce: pool: max-active: 8 max-wait: -1ms max-idle: 8 min-idle: 0 # 自定义配置 app: like: target-type: article: 1 redis: key-prefix: like:article: # Redis Key前缀2. 实体类 (entity/UserLike.java):package com.example.likesystem.entity; import jakarta.persistence.*; import lombok.Data; import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; import java.time.LocalDateTime; Entity Table(name user_like, uniqueConstraints { UniqueConstraint(columnNames {user_id, target_id, target_type}, name uk_user_target) }) Data public class UserLike { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; Column(name user_id, nullable false) private Long userId; Column(name target_id, nullable false) private Long targetId; // 例如文章ID Column(name target_type, nullable false) private Integer targetType; // 1-文章2-评论等 Column(nullable false) private Integer status 1; // 1-点赞0-取消 CreationTimestamp Column(name create_time, updatable false) private LocalDateTime createTime; UpdateTimestamp Column(name update_time) private LocalDateTime updateTime; }3. 数据传输对象 (dto/LikeRequestDTO.java):package com.example.likesystem.dto; import lombok.Data; import javax.validation.constraints.NotNull; Data public class LikeRequestDTO { NotNull(message 用户ID不能为空) private Long userId; NotNull(message 目标ID不能为空) private Long targetId; NotNull(message 操作类型不能为空) private Integer action; // 1-点赞2-取消点赞 }4.3 核心业务逻辑实现1. 服务层接口 (service/LikeService.java):package com.example.likesystem.service; import com.example.likesystem.dto.LikeRequestDTO; public interface LikeService { /** * 点赞或取消点赞 * param request 点赞请求 * return 操作后的点赞状态 (true-已点赞, false-未点赞) */ Boolean like(LikeRequestDTO request); /** * 获取点赞状态 * param userId 用户ID * param targetId 目标ID * return true-已点赞false-未点赞 */ Boolean getLikeStatus(Long userId, Long targetId); /** * 获取点赞总数 * param targetId 目标ID * return 点赞数量 */ Long getLikeCount(Long targetId); }2. 服务层实现 (service/impl/LikeServiceImpl.java):这是整个系统的核心请仔细阅读注释。package com.example.likesystem.service.impl; import com.example.likesystem.dto.LikeRequestDTO; import com.example.likesystem.entity.UserLike; import com.example.likesystem.repository.UserLikeRepository; import com.example.likesystem.service.LikeService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.concurrent.TimeUnit; Service Slf4j RequiredArgsConstructor public class LikeServiceImpl implements LikeService { private final RedisTemplateString, Object redisTemplate; private final UserLikeRepository userLikeRepository; // Redis Key 模板 private static final String LIKE_KEY_PREFIX like:article:; // 假设目标类型为文章 private static final Integer TARGET_TYPE_ARTICLE 1; Override public Boolean like(LikeRequestDTO request) { Long userId request.getUserId(); Long targetId request.getTargetId(); Integer action request.getAction(); // 1. 构造Redis Key String redisKey LIKE_KEY_PREFIX targetId; String userField userId.toString(); // 2. 执行Redis原子操作 Boolean result; if (action 1) { // 点赞 // HSETNX: 如果field不存在则设置保证同一用户只能点一次赞在缓存层面 result redisTemplate.opsForHash().putIfAbsent(redisKey, userField, 1); // 如果result为true表示是第一次点赞缓存中如果为false表示已点过赞。 } else if (action 2) { // 取消点赞 // HDEL: 删除field表示取消点赞 result redisTemplate.opsForHash().delete(redisKey, userField) 0; // 如果result为true表示成功删除了一个点赞记录。 } else { throw new IllegalArgumentException(非法的操作类型: action); } // 3. 设置Key的过期时间防止冷数据长期占用内存例如7天 redisTemplate.expire(redisKey, 7, TimeUnit.DAYS); log.info(用户 {} 对文章 {} 执行操作 {}Redis操作结果: {}, userId, targetId, action, result); // 注意这里返回的result含义根据action不同而不同更佳实践是返回操作后的状态。 // 我们重新查询一次状态返回给前端。 return getLikeStatus(userId, targetId); } Override public Boolean getLikeStatus(Long userId, Long targetId) { String redisKey LIKE_KEY_PREFIX targetId; // HEXISTS: 检查Hash中是否存在某个field return redisTemplate.opsForHash().hasKey(redisKey, userId.toString()); } Override public Long getLikeCount(Long targetId) { String redisKey LIKE_KEY_PREFIX targetId; // HLEN: 获取Hash中field的数量即点赞用户数 Long count redisTemplate.opsForHash().size(redisKey); return count ! null ? count : 0L; } /** * 将Redis中的点赞数据同步到数据库通常由定时任务调用 * param targetId 目标ID */ Transactional public void syncLikeToDatabase(Long targetId) { String redisKey LIKE_KEY_PREFIX targetId; // 1. 从Redis获取该文章的所有点赞用户ID MapObject, Object entries redisTemplate.opsForHash().entries(redisKey); if (entries.isEmpty()) { log.info(文章 {} 在Redis中无点赞数据跳过同步。, targetId); return; } // 2. 遍历更新或插入到user_like表 for (Map.EntryObject, Object entry : entries.entrySet()) { Long userId Long.valueOf(entry.getKey().toString()); // 使用 save 方法依靠唯一索引 uk_user_target 来避免重复 UserLike userLike new UserLike(); userLike.setUserId(userId); userLike.setTargetId(targetId); userLike.setTargetType(TARGET_TYPE_ARTICLE); userLike.setStatus(1); // Redis中存在即为点赞状态 try { userLikeRepository.save(userLike); } catch (Exception e) { // 唯一索引冲突是预期内的忽略即可 log.debug(用户 {} 对文章 {} 的点赞关系已存在跳过插入。, userId, targetId); } } // 3. 更新article表的like_count (这里需要注入ArticleRepository简化起见直接写SQL或调用Service) // 假设我们有一个articleService.updateLikeCount方法 Long currentCount getLikeCount(targetId); // 从Redis取最新计数 // articleService.updateLikeCount(targetId, currentCount); log.info(已同步文章 {} 的点赞数据到数据库当前计数: {}, targetId, currentCount); // 4. (可选) 同步成功后可以删除Redis key或者保留作为缓存。 // redisTemplate.delete(redisKey); } }3. 数据访问层 (repository/UserLikeRepository.java):package com.example.likesystem.repository; import com.example.likesystem.entity.UserLike; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import java.util.Optional; Repository public interface UserLikeRepository extends JpaRepositoryUserLike, Long { // 根据用户、目标、类型查找记录 OptionalUserLike findByUserIdAndTargetIdAndTargetType(Long userId, Long targetId, Integer targetType); }4.4 控制器层与接口定义控制器 (controller/LikeController.java):package com.example.likesystem.controller; import com.example.likesystem.dto.LikeRequestDTO; import com.example.likesystem.service.LikeService; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; RestController RequestMapping(/api/like) RequiredArgsConstructor Validated public class LikeController { private final LikeService likeService; PostMapping(/action) public ApiResponseBoolean like(Valid RequestBody LikeRequestDTO request) { Boolean status likeService.like(request); return ApiResponse.success(status); } GetMapping(/status) public ApiResponseBoolean getStatus(RequestParam Long userId, RequestParam Long targetId) { Boolean status likeService.getLikeStatus(userId, targetId); return ApiResponse.success(status); } GetMapping(/count) public ApiResponseLong getCount(RequestParam Long targetId) { Long count likeService.getLikeCount(targetId); return ApiResponse.success(count); } // 统一的API响应封装类 Data public static class ApiResponseT { private Integer code; private String message; private T data; public static T ApiResponseT success(T data) { ApiResponseT response new ApiResponse(); response.setCode(200); response.setMessage(success); response.setData(data); return response; } // 可以补充 error 方法 } }4.5 定时同步任务为了保证数据最终一致性我们需要一个定时任务将Redis中的点赞数据同步到MySQL。同步任务 (task/LikeSyncTask.java):package com.example.likesystem.task; import com.example.likesystem.service.LikeService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ScanOptions; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.util.HashSet; import java.util.Set; Component Slf4j RequiredArgsConstructor public class LikeSyncTask { private final RedisTemplateString, Object redisTemplate; private final LikeService likeService; private static final String LIKE_KEY_PATTERN like:article:*; /** * 每30秒执行一次同步生产环境可根据压力调整如每分钟一次 */ Scheduled(fixedDelay 30000) // 单位毫秒 public void syncLikeData() { log.info(开始执行点赞数据同步任务...); SetString keys scanKeys(LIKE_KEY_PATTERN); if (keys.isEmpty()) { log.info(未找到需要同步的点赞Key。); return; } for (String key : keys) { try { // 从Key中提取targetId例如 like:article:1001 - 1001 String[] parts key.split(:); if (parts.length 3) { continue; } Long targetId Long.parseLong(parts[2]); // 调用服务层的同步方法 likeService.syncLikeToDatabase(targetId); } catch (Exception e) { log.error(同步Key: {} 的数据时发生异常, key, e); // 记录错误继续同步下一个避免一个失败导致全部失败 } } log.info(点赞数据同步任务执行完毕。); } /** * 使用SCAN命令安全地遍历Redis Key避免KEYS命令阻塞。 */ private SetString scanKeys(String pattern) { SetString keys new HashSet(); Cursorbyte[] cursor redisTemplate.getConnectionFactory() .getConnection() .scan(ScanOptions.scanOptions().match(pattern).count(100).build()); while (cursor.hasNext()) { keys.add(new String(cursor.next())); } // 注意这里需要手动关闭cursor或者使用try-with-resources (Spring Data Redis 高阶用法) try { if (cursor ! null) { cursor.close(); } } catch (Exception e) { log.warn(关闭Redis SCAN cursor时出错, e); } return keys; } }重要别忘了在启动类上添加EnableScheduling注解来启用定时任务。4.6 运行与验证启动服务确保MySQL和Redis服务已启动。运行LikeSystemApplication的main方法。测试点赞使用Postman或curl测试接口。点赞请求:curl -X POST -H Content-Type: application/json \ -d {userId: 1001, targetId: 2001, action: 1} \ http://localhost:8080/api/like/action预期返回{code:200,message:success,data:true}查询点赞状态:curl http://localhost:8080/api/like/status?userId1001targetId2001预期返回{code:200,message:success,data:true}查询点赞数:curl http://localhost:8080/api/like/count?targetId2001预期返回{code:200,message:success,data:1}取消点赞:curl -X POST -H Content-Type: application/json \ -d {userId: 1001, targetId: 2001, action: 2} \ http://localhost:8080/api/like/action预期返回{code:200,message:success,data:false}观察数据立即查看RedisHGETALL like:article:2001会看到用户1001的点赞记录被添加或删除。等待定时任务执行默认30秒后查看MySQL的user_like表会看到对应的记录被插入article表的like_count也会被更新如果实现了该逻辑。5. 常见问题与排查思路在实际开发和线上运维中你会遇到各种问题。下面是一个排查清单。问题现象可能原因排查步骤与解决方案点赞/取消无效果接口返回错误1. 参数校验失败。2. Redis连接失败。3. NPE空指针。1. 检查请求体JSON格式和字段是否为空。2. 查看应用日志确认Redis配置正确且服务可达。3. 检查LikeServiceImpl中redisTemplate等依赖是否成功注入。Redis中有点赞记录但数据库没同步1. 定时任务未执行。2. 同步方法异常。3. 唯一索引冲突被捕获忽略。1. 确认启动类有EnableScheduling。2. 查看任务日志LikeSyncTask是否有报错。3. 检查syncLikeToDatabase方法中的异常处理逻辑确认user_like表唯一索引正常。高并发下数据库出现重复点赞记录1. 缓存穿透多个请求同时发现缓存无数据都去查库并写缓存。2. 同步任务并发执行。1.缓存层面使用HSETNX保证原子性。2.数据库层面依赖唯一索引uk_user_target作为最后保障。3.同步任务对同一个targetId的同步加分布式锁如基于Redis的锁。点赞数偶尔不准确1. 缓存与数据库短暂不一致最终一致性窗口期。2. Redis内存不足Key被淘汰。3. 同步任务失败未重试。1.业务容忍确认业务是否能接受短暂不一致。2.缓存保障设置合理的Key过期时间并确保内存充足。3.增加重试与监控同步任务失败后进入重试队列并报警。HLEN获取的计数与实际情况不符1. 用户取消点赞后Redis中对应field被删除计数正确。2. 如果采用status0标记取消则HLEN会包含已取消的用户导致计数偏大。方案确认我们采用的是“取消即删除field”的方案所以HLEN是准确的。如果你采用标记方案则需要额外维护一个只包含有效点赞用户的集合如另一个Set。SCAN命令在同步任务中报错或效率低1. Redis连接未正确关闭。2. 数据量巨大时单次SCAN耗时。1. 确保使用try-with-resources或正确关闭Cursor。2. 调整ScanOptions.count参数分批扫描。对于海量数据应考虑按业务分片而不是全量扫描。6. 最佳实践与工程建议将系统运行起来只是第一步要使其健壮、可维护、高性能还需要遵循以下工程实践。6.1 缓存策略优化Key设计使用清晰的命名空间如like:article:{id}、like:comment:{id}。避免Key冲突。过期时间一定要设置过期时间对于互动数据可以根据业务活跃度设置如7天、30天。防止冷数据永久占用内存。内存优化对于超热点的内容如爆款文章其点赞Hash可能非常大。考虑使用Redis Cluster分片或对单个大Hash进行拆分例如按用户ID范围拆分到多个Key。6.2 数据库与一致性批量操作在syncLikeToDatabase方法中可以考虑使用 JPA 的saveAll进行批量插入提升性能。补偿机制定时同步不是100%可靠。可以额外增加一个补偿任务定期对比Redis的HLEN和MySQL中article.like_count的差异并进行修复。分库分表当user_like表数据量达到千万甚至亿级时需要考虑按user_id或target_id进行分库分表。6.3 接口设计与安全幂等性点赞/取消接口要实现幂等。用户连续点击两次“点赞”结果应该与点一次相同。我们的HSETNX和HDEL是原子操作天然支持幂等。防刷必须限制单个用户对同一内容的频繁操作。可以在Redis中为每个用户-内容对设置一个操作冷却期的Key如operate:lock:userId:targetId设置1秒过期在冷却期内拒绝请求。认证与授权本文示例为了简化直接传递userId。在生产环境中userId应从会话如JWT Token中获取防止参数篡改。限流对/api/like/action接口实施限流如使用Sentinel或Resilience4j防止恶意刷接口。6.4 可观测性与监控埋点日志在LikeServiceImpl.like()方法中记录关键日志用户、目标、操作、结果便于后续审计和问题排查。监控指标Redis 内存使用率、连接数、命令耗时。点赞接口的 QPS、平均响应时间、错误率。定时同步任务的成功率、耗时、同步延迟。报警当同步任务连续失败、Redis内存使用率超过阈值、接口错误率飙升时应及时触发报警。6.5 扩展性思考支持多类型当前代码固定了target_type为文章。可以通过枚举、配置化等方式轻松扩展支持评论、视频、动态等多种类型。消息队列解耦在超高并发场景下可以将点赞动作作为消息发送到Kafka或RocketMQ由消费者异步处理Redis和数据库的写入进一步提高接口响应速度和解耦。计数服务抽象可以将点赞、收藏、转发等互动计数抽象成统一的“计数服务”提供通用的incr、decr、get接口提升代码复用性。从一次简单的“点赞”点击到背后这套涵盖缓存、数据库、异步同步、安全防护的完整技术体系正是现代Web系统复杂性的一个缩影。理解并实践这套流程不仅能帮你构建出稳健的互动功能更能深化你对高并发、数据一致性、系统架构设计的认知。建议你在理解本文代码的基础上尝试实现文中提到的“防刷”、“限流”、“消息队列解耦”等进阶功能这将是一次非常好的练手机会。如果在实践中遇到问题欢迎在评论区交流探讨。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价