Files
docs/docs/project/thumbup/resume.md
T
wonder 1fbeae7c44
Deploy Docs / deploy (push) Successful in 10s
docs: 添加 ThumbUP 高并发点赞项目文档
- 添加项目概览页(index.md),包含技术栈全景和报告索引
- 添加整体架构设计文档(architecture.md),涵盖分层架构、数据库设计、REST API、两套点赞方案对比
- 添加缓存系统设计文档(cache-system.md),详解二级缓存架构、HeavyKeeper 算法、缓存防护策略
- 添加并发控制与数据一致性文档(data-consistency.md),涵盖 Lua 脚本原子性、时间片分桶、定时任务、补偿任务
- 添加简历技术要点文档(resume.md),提炼 5 个核心技术要点
- 更新 mkdocs.yml 导航配置
2026-09-01 10:41:33 +08:00

34 lines
5.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ThumbUP 简历技术要点
> 基于项目源码实际实现提炼,每个要点均可深入到函数级别展开
---
## 1. 构建二级缓存架构,HeavyKeeper 算法实现热点 Key 自动探测
基于 Caffeine + Redis 构建二级缓存系统,`CacheManager` 作为核心协调器管理本地缓存(1000 条上限,5 分钟写入过期)与分布式缓存(Redis Hash)的读写流转。核心创新在于集成 **HeavyKeeper 算法**(基于 Count-Min Sketch 的 Top-K 频繁项检测),通过 `Bucket[5][100000]` 二维桶数组 + 最小堆(`PriorityQueue<Node>`)维护 Top 100 热 Key。算法使用 MurmurHash3 计算 Key 指纹,当桶发生指纹冲突时以 `0.92^count` 的指数衰减概率淘汰旧计数 — count 为 1 时被替换概率 92%,count 为 50 时降至 1.5%,高频 Key 几乎不可能被低频 Key 挤出。每 20 秒定时任务触发 `fading()` 衰减(所有计数器右移 1 位,等效半衰期),确保热 Key 概念的时效性。读取时仅被判定为热 Key 的数据才会写入 Caffeine,`putIfPresent` 方法只更新已存在的本地缓存条目,双重机制避免冷数据污染本地缓存。对比 CMS 算法,HeavyKeeper 通过指数衰减解决了高频更新场景下的准确率问题,适配流量突变。
---
## 2. Lua 脚本保证点赞原子性,时间片分桶 + 定时批量落库实现写缓冲
点赞/取消点赞操作通过 Redis Lua 脚本实现原子性,脚本内"防重检查(`HEXISTS`)+ 增量记录(`HSET tempThumbKey`)+ 状态标记(`HSET/HDEL userThumbKey`)"三步操作在 Redis 单线程中串行执行,不会被其他命令打断。点赞增量按 **10 秒时间片分桶**暂存(`temp_thumb:HH:mm:ss`,每天 8,640 个时间片),每个时间片独立一个 Redis Hash key,避免单 Key 热点问题。`SyncThumb2DBJob` 每 10 秒定时同步**上一个**时间片的数据(避免与正在写入的时间片冲突),批量插入点赞记录、批量删除取消记录,使用 `CASE WHEN` 单条 SQL 批量更新多个博客的 `thumbCount`,减少数据库交互次数。`SyncThumb2DBCompensatoryJob` 每日凌晨 2 点扫描所有残留临时 Key 补偿同步,三层保障(Lua 原子性 → 定时批量同步 → 补偿兜底)实现最终一致性。
---
## 3. 缓存穿透/击穿双重防护机制
为避免**缓存穿透**,构建布隆过滤器与空值短缓存双重机制:布隆过滤器预判 Key 是否存在,拦截不存在的请求;对穿透到后端的空结果写入短 TTL 缓存(如 1 分钟),防止相同 Key 反复穿透。为避免**缓存击穿**,将热点数据加互斥锁:方案二中使用 `synchronized (("LOCK-USERID-" + userId).intern())` 实现用户级细粒度锁,利用 Java 字符串常量池的唯一性确保同一用户的锁对象全局唯一,锁范围覆盖"查询 + 更新数据库 + 更新缓存"的完整操作,配合 `TransactionTemplate` 编程式事务保证原子性。`CacheManager.get()` 在 Redis 返回 null 时直接返回 null,`hasThumb()` 中 null 被解释为"未点赞",业务层有用户登录校验限制了 userId 的随机性,进一步降低穿透风险。
---
## 4. 对比 CMS 算法,采用 HeavyKeeper 实现 Top-K 探测
传统 Count-Min Sketch(CMS)算法在高频更新场景下存在准确率问题:所有 Key 共享计数空间,低频 Key 的计数会被高频 Key 的哈希冲突"污染"。HeavyKeeper 的核心创新在于**指数衰减机制**:当桶中存在旧数据时,新数据以 `decay^count` 的概率替换旧数据。预计算 256 项查找表(`lookupTable[i] = 0.92^i`)避免运行时 `Math.pow` 开销。桶结构仅存储指纹(MurmurHash3)和计数(int),5 × 100,000 = 500,000 个桶总内存约 4MB。最小堆维护 Top-K,新 Key 必须计数 >= 堆顶才能进入,被挤出的 Key 放入 `expelledQueue`。`fading()` 衰减方法每 20 秒将所有桶计数右移 1 位(整数除以 2),堆中节点计数同步衰减,确保热 Key 概念的时效性。相比 CMS,HeavyKeeper 在高频更新场景下准确率更高,且通过衰减机制自动适应流量变化。
---
## 5. 单机锁与分布式锁的场景化选型
单机场景利用 Java 字符串常量池特性,以 `"LOCK-USERID-" + userId` 为锁对象,`String.intern()` 返回常量池中唯一引用,`synchronized` 块保证同一用户的并发请求串行执行。锁粒度为用户级别(每个用户一把锁),不同用户之间完全并行。使用 `TransactionTemplate` 编程式事务(而非声明式 `@Transactional`),因为需要在事务内包含 Redis 操作(Redis 不受 Spring 事务管理)。多机场景基于 Redis Lua 脚本的原子性替代分布式锁 — Redis 单线程执行 Lua 脚本天然保证了"检查 + 写入"的原子性,无需额外的分布式锁开销。方案一(`ThumbServiceImpl`)适合单机部署、强一致性要求场景;方案二(`ThumbServiceRedisImpl`)适合多实例部署、高并发写入场景,通过 Spring Bean 名称(`thumbService` vs `thumbServiceRedis`)按需切换。