跳转至

ThumbUP 整体架构设计

💡 一句话概述

基于 Spring Boot 3 + Java 21 构建的中台化高并发点赞系统,采用两套可切换的点赞方案(Redis 异步批量落库 vs Caffeine + Redis 二级缓存),通过 HeavyKeeper 算法实现热点 Key 自动探测,支撑亿级数据规模与万级 QPS 流量。


🔑 核心概念

  1. 中台化设计 — 点赞服务作为独立基础服务,支撑多业务场景的点赞交互与数据查询
  2. 双方案架构 — Redis 异步方案(高并发写入)与二级缓存方案(读多写少)可按需切换
  3. 最终一致性 — Lua 脚本原子操作 + 时间片分桶暂存 + 定时批量落库 + 补偿任务兜底
  4. 热点自动探测 — 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>:

{
    "code": 0,
    "data": {},
    "message": "ok"
}

错误码体系

错误码 说明
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

🔗 相关链接