ThumbUP 整体架构设计¶
💡 一句话概述
基于 Spring Boot 3 + Java 21 构建的中台化高并发点赞系统,采用两套可切换的点赞方案(Redis 异步批量落库 vs Caffeine + Redis 二级缓存),通过 HeavyKeeper 算法实现热点 Key 自动探测,支撑亿级数据规模与万级 QPS 流量。
🔑 核心概念¶
- 中台化设计 — 点赞服务作为独立基础服务,支撑多业务场景的点赞交互与数据查询
- 双方案架构 — Redis 异步方案(高并发写入)与二级缓存方案(读多写少)可按需切换
- 最终一致性 — Lua 脚本原子操作 + 时间片分桶暂存 + 定时批量落库 + 补偿任务兜底
- 热点自动探测 — HeavyKeeper 算法实时检测 Top-K 热 Key,仅热 Key 进入本地缓存
📝 分层架构¶
整体分层¶
项目采用经典四层架构 + Manager 中间层的模式:
┌─────────────────────────────────────────────────────────────┐
│ Controller 层(接口层) │
│ HealthController │ UserController │ BlogController │ Thumb │
├─────────────────────────────────────────────────────────────┤
│ Service 层(业务层) │
│ UserService │ BlogService │ ThumbService (接口) │
│ │ ├── ThumbServiceImpl (二级缓存方案) │
│ │ └── ThumbServiceRedisImpl (Redis方案) │
├─────────────────────────────────────────────────────────────┤
│ Manager 层(管理层) │
│ CacheManager(二级缓存协调器) │
│ ├── Caffeine 本地缓存 │
│ ├── HeavyKeeper 热点探测 │
│ └── RedisTemplate │
├─────────────────────────────────────────────────────────────┤
│ Mapper 层(数据访问层) │
│ UserMapper │ BlogMapper │ ThumbMapper (MyBatis-Plus) │
├─────────────────────────────────────────────────────────────┤
│ 数据层 │
│ MySQL(持久化) │ Redis(缓存/临时计数/Session) │
└─────────────────────────────────────────────────────────────┘
包结构¶
cn.hezhaohui.thumb
├── controller/ # REST API 接口(4 个)
├── service/ # 业务接口(3 个)
│ └── impl/ # 业务实现(4 个,含两套点赞实现)
├── mapper/ # MyBatis Mapper 接口(3 个)
├── model/
│ ├── entity/ # 实体类(User, Blog, Thumb)
│ ├── dto/ # 请求 DTO(DoThumbRequest)
│ ├── vo/ # 视图对象(BlogVO)
│ └── enums/ # 枚举(LuaStatusEnum, ThumbTypeEnum)
├── config/ # 配置类(CorsConfig, RedisConfig)
├── exception/ # 异常处理(GlobalExceptionHandler, ErrorCode 等)
├── common/ # 通用类(BaseResponse, ResultUtils 等)
├── constant/ # 常量(UserConstant, ThumbConstant, RedisLuaScriptConstant)
├── manager/cache/ # 缓存管理(CacheManager, HeavyKeeper, TopK, Item)
├── job/ # 定时任务(SyncThumb2DBJob, SyncThumb2DBCompensatoryJob)
└── util/ # 工具类(RedisKeyUtil)
📊 数据库设计¶
user 表¶
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGINT (PK, AUTO) | 用户 ID |
| username | VARCHAR(128) | 用户名 |
设计极简,无密码字段,通过 userId 直接模拟登录。
blog 表¶
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGINT (PK, AUTO) | 博客 ID |
| userId | BIGINT | 作者 ID,有索引 idx_userId |
| title | VARCHAR(512) | 标题 |
| coverImg | VARCHAR(1024) | 封面图 |
| content | TEXT | 内容 |
| thumbCount | INT (DEFAULT 0) | 点赞计数器(冗余字段) |
| createTime | DATETIME | 创建时间 |
| updateTime | DATETIME | 更新时间(自动更新) |
thumbCount 冗余了点赞总数,避免每次查询都 COUNT thumb 表。
thumb 表(点赞记录表)¶
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGINT (PK, AUTO) | 点赞记录 ID |
| userId | BIGINT | 点赞用户 ID |
| blogId | BIGINT | 被点赞博客 ID |
| createTime | DATETIME | 点赞时间 |
唯一索引:UNIQUE INDEX idx_userId_blogId ON thumb(userId, blogId) — 保证同一用户对同一博客只能点赞一次。
ER 关系¶
erDiagram
USER ||--o{ BLOG : "发布"
USER ||--o{ THUMB : "点赞"
BLOG ||--o{ THUMB : "被点赞"
USER {
bigint id PK
varchar username
}
BLOG {
bigint id PK
bigint userId FK
varchar title
text content
int thumbCount
}
THUMB {
bigint id PK
bigint userId FK
bigint blogId FK
datetime createTime
}
🛣️ REST API 设计¶
API 前缀:/api(application.yml 配置 servlet.path: /api)
| 方法 | 路径 | 参数 | 说明 |
|---|---|---|---|
| GET | /api/health |
- | 健康检查 |
| GET | /api/user/login |
userId (query) | 模拟登录,将 User 存入 Session |
| GET | /api/user/get/login |
- | 获取当前登录用户 |
| GET | /api/blog/get |
blogId (query) | 获取单篇博客详情(含点赞状态) |
| GET | /api/blog/list |
- | 获取全部博客列表(含点赞状态) |
| POST | /api/thumb/do |
DoThumbRequest (body: blogId) | 点赞 |
| POST | /api/thumb/undo |
DoThumbRequest (body: blogId) | 取消点赞 |
统一响应格式¶
所有 API 返回 BaseResponse<T>:
错误码体系¶
| 错误码 | 说明 |
|---|---|
| 0 | 成功 |
| 40000 | 参数错误 |
| 40100 | 未登录 |
| 40101 | 无权限 |
| 40400 | 数据不存在 |
| 40300 | 禁止访问 |
| 50000 | 系统内部异常 |
| 50001 | 操作失败 |
⚡ 两套点赞方案对比¶
项目包含两套点赞服务实现,通过 Spring Bean 名称区分注入:
方案一:ThumbServiceRedisImpl(Redis 异步方案)¶
Bean 名称:thumbServiceRedis
核心思路:写操作完全不碰数据库,点赞/取消赞全部在 Redis 中完成,通过定时任务异步同步到 MySQL。
sequenceDiagram
participant Client
participant Controller
participant Service
participant Redis
participant Scheduler
participant MySQL
Client->>Controller: POST /api/thumb/do
Controller->>Service: doThumb()
Service->>Redis: Lua 脚本原子操作
Note over Redis: HEXISTS 防重 + HSET 临时计数 + HSET 状态标记
Redis-->>Service: 返回结果
Service-->>Controller: 返回成功
Controller-->>Client: 响应
Note over Scheduler: 每 10 秒触发
Scheduler->>Redis: HGETALL 临时数据
Scheduler->>MySQL: 批量插入/删除/更新
Scheduler->>Redis: DELETE 已同步 Key
方案二:ThumbServiceImpl(二级缓存方案)¶
Bean 名称:thumbService(当前默认使用)
核心思路:实时写库保证一致性,Caffeine + Redis 二级缓存加速读取,HeavyKeeper 算法自动探测热 Key。
sequenceDiagram
participant Client
participant Controller
participant Service
participant Cache as CacheManager
participant Caffeine
participant Redis
participant MySQL
Client->>Controller: POST /api/thumb/do
Controller->>Service: doThumb()
Service->>Service: synchronized 加锁
Service->>Cache: get() 查询点赞状态
Cache->>Caffeine: 查本地缓存
alt 命中
Caffeine-->>Cache: 返回缓存值
else 未命中
Cache->>Redis: HGET 查分布式缓存
Redis-->>Cache: 返回值
Cache->>Cache: HeavyKeeper 热点探测
alt 是热 Key
Cache->>Caffeine: 写入本地缓存
end
end
Cache-->>Service: 返回点赞状态
Service->>MySQL: 更新 thumbCount + 插入 thumb 记录
Service->>Redis: HSET 写入点赞状态
Service->>Cache: putIfPresent 更新本地缓存
Service-->>Controller: 返回成功
方案对比¶
| 维度 | 方案一(Redis 异步) | 方案二(二级缓存) |
|---|---|---|
| Bean 名称 | thumbServiceRedis |
thumbService |
| 写入方式 | Redis Lua 脚本,异步落库 | 实时写数据库 |
| 并发控制 | Lua 脚本原子性 | synchronized + 编程式事务 |
| 缓存层次 | 一级缓存(Redis only) | 二级缓存(Caffeine + Redis) |
| 一致性模型 | 最终一致性 | 强一致性(单机内) |
| 多实例支持 | 支持(Redis 天然分布式) | 不支持(单机锁失效) |
| 性能 | 高(写 Redis 内存,批量落库) | 中(每次请求都写库) |
| 适用场景 | 超高并发写入 | 读多写少 |
🔧 配置与工具¶
Redis 序列化配置¶
// Key: StringRedisSerializer
// Value: Jackson2JsonRedisSerializer(开启 DefaultTyping.NON_FINAL)
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.activateDefaultTyping(
LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL
);
用户认证¶
简化版 Session 认证,通过 userId 直接查库存入 Session。Session 数据通过 spring-session-data-redis 存入 Redis,实现分布式 Session。
跨域配置¶
完全放开 CORS 限制,允许所有来源(开发阶段配置)。
🗂️ 项目文件清单¶
thumb-up/
├── pom.xml
├── src/main/java/cn/hezhaohui/thumb/
│ ├── Application.java # 启动类
│ ├── controller/
│ │ ├── HealthController.java # 健康检查
│ │ ├── UserController.java # 用户登录
│ │ ├── BlogController.java # 博客查询
│ │ └── ThumbController.java # 点赞操作
│ ├── service/
│ │ ├── UserService.java # 用户服务接口
│ │ ├── BlogService.java # 博客服务接口
│ │ ├── ThumbService.java # 点赞服务接口
│ │ └── impl/
│ │ ├── UserServiceImpl.java
│ │ ├── BlogServiceImpl.java
│ │ ├── ThumbServiceImpl.java # 二级缓存方案
│ │ └── ThumbServiceRedisImpl.java # Redis 异步方案
│ ├── manager/cache/
│ │ ├── CacheManager.java # 二级缓存管理器
│ │ ├── HeavyKeeper.java # 热点探测算法
│ │ ├── TopK.java # Top-K 接口
│ │ └── Item.java # 数据项
│ ├── job/
│ │ ├── SyncThumb2DBJob.java # 定时同步任务
│ │ └── SyncThumb2DBCompensatoryJob.java # 补偿任务
│ ├── mapper/
│ │ ├── UserMapper.java
│ │ ├── BlogMapper.java
│ │ └── ThumbMapper.java
│ ├── model/
│ │ ├── entity/ (User, Blog, Thumb)
│ │ ├── dto/ (DoThumbRequest)
│ │ ├── vo/ (BlogVO)
│ │ └── enums/ (LuaStatusEnum, ThumbTypeEnum)
│ ├── config/ (CorsConfig, RedisConfig)
│ ├── constant/ (UserConstant, ThumbConstant, RedisLuaScriptConstant)
│ ├── common/ (BaseResponse, ResultUtils, PageRequest, DeleteRequest)
│ ├── exception/ (GlobalExceptionHandler, ErrorCode, BusinessException, ThrowUtils)
│ └── util/ (RedisKeyUtil)
└── src/main/resources/
├── application.yml
├── mapper/ (UserMapper.xml, BlogMapper.xml, ThumbMapper.xml)
└── logback.xml
🔗 相关链接¶
- 缓存系统设计 — 二级缓存架构、HeavyKeeper 算法详解
- 并发控制与数据一致性 — Lua 脚本、时间片分桶、最终一致性保障
- 简历技术要点 — 基于源码提炼的面试技术要点