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)按需切换。