1fbeae7c44
Deploy Docs / deploy (push) Successful in 10s
- 添加项目概览页(index.md),包含技术栈全景和报告索引 - 添加整体架构设计文档(architecture.md),涵盖分层架构、数据库设计、REST API、两套点赞方案对比 - 添加缓存系统设计文档(cache-system.md),详解二级缓存架构、HeavyKeeper 算法、缓存防护策略 - 添加并发控制与数据一致性文档(data-consistency.md),涵盖 Lua 脚本原子性、时间片分桶、定时任务、补偿任务 - 添加简历技术要点文档(resume.md),提炼 5 个核心技术要点 - 更新 mkdocs.yml 导航配置
34 lines
5.0 KiB
Markdown
34 lines
5.0 KiB
Markdown
# 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`)按需切换。
|