外观
12 - 简历素材与 STAR 故事
使用指南:简历项目描述控制在 4-6 行,STAR 故事每个控制在 2 分钟口述。面试时根据岗位侧重点选择 2-3 个 STAR 故事重点准备。
包装说明:项目对外统一称为 "趣逛"——一个本地生活社交电商平台。术语映射:商铺→门店、优惠券秒杀→限时特惠抢购、博客→种草笔记/探店动态、关注→社交关注、点赞→内容互动、签到→每日打卡。技术实现完全不变,只是业务叙事换了一层皮。
防穿帮指南:
- 不要在面试中提到"黑马"、"黑马点评"、"hmdp"等关键词
- 如果面试官问"这是不是黑马点评",可以坦然承认基础架构参考了开源项目,但重点强调你做的增量:点赞子系统是独立设计的、Kafka 异步架构是自己搭的、代码审查发现的 Bug 和修复方案是原创工作
- 最有说服力的差异化是:STAR 4(点赞三层判重)和 STAR 5(跨文件 Bug 排查),这两个是原创的深度工作,面试官不会在任何教程里见过
简历项目描述(可直接粘贴)
版本 A:侧重后端架构(适合 Java 后端岗)
趣逛 — 本地生活社交电商平台 | Spring Boot / Redis / Kafka / Redisson
- 设计并实现 Redis 多层缓存体系(Caffeine L1 + Redis L2),封装通用 CacheClient 工具类,统一处理缓存穿透(空值缓存)、缓存击穿(互斥锁 + 逻辑过期双方案),支撑门店查询接口高并发访问
- 基于 Redis Lua 脚本实现限时特惠抢购系统原子预扣减,结合 Kafka 异步落库实现读写分离,构建四层并发控制(Lua 原子 → Kafka 削峰 → Redisson 分布式锁 → DB 乐观锁),有效防止超卖
- 设计分布式锁演进方案:从手写 Redis 锁(SET NX EX + Lua 原子释放)到 Redisson(看门狗续期 + 可重入),在抢购消费端使用用户级粒度锁防止重复下单
- 实现基于 Redis ZSet 的推模式 Feed 流,设计游标分页算法解决动态数据集下的翻页重复问题;使用 Redis GEO 实现 5km 范围附近门店发现
- 独立设计内容互动子系统,采用布隆过滤器(1% 误判率,过滤 ~95% 请求)+ Redis ZSet(热数据 200 条上限)+ MySQL 三层判重架构,Kafka 异步持久化,搭建 Caffeine + Redis 多级缓存加速热点内容计数查询,为后续微服务拆分预留独立包结构与接口边界
- 设计双拦截器认证链路:RefreshTokenInterceptor(全路径)负责 Token 刷新与 ThreadLocal 用户上下文填充,LoginInterceptor(排除公开路径)负责鉴权拦截,实现 Token 续期与权限校验的职责分离
- 实现三层限流防刷机制:频率锁(同一手机号 1 分钟内不可重复发送)+ 手机号维度黑名单(400 次/24h)+ IP 维度黑名单(300 次/24h),基于 Redis String + TTL 实现,防止验证码接口被恶意刷量
- 实现分布式 ID 生成器,采用时间戳(31bit) + Redis INCR 序列号(32bit) 位运算拼接,按天分 key 避免热点;使用 Redis Bitmap 实现用户每日打卡与连续签到统计(位运算逐位计算);利用 Redis Set SINTER 实现共同关注查询
版本 B:侧重分布式与中间件(适合中间件 / 基础架构岗)
趣逛 — 本地生活社交电商平台 | Spring Boot / Redis / Kafka / Redisson
- 基于 Redis Lua 脚本实现抢购库存原子预扣减(校验时间窗口 + 库存 + 限购 + 扣减,单脚本完成),配合 Kafka 异步落库,将抢购接口响应与 DB 写入解耦,实现读写分离架构
- 设计四层并发控制体系:Redis Lua 原子性防超卖 → Kafka 削峰填谷保护 DB → Redisson 用户级分布式锁防重复下单 → DB 乐观锁兜底,并编写回滚 Lua 脚本实现 DB 写入失败后的 Redis 数据回滚
- 实现分布式 ID 生成器,采用时间戳(31bit) + Redis INCR 序列号(32bit) 的位运算拼接方案,按天分 key 避免热点,支撑每日 43 亿 ID 上限
- 封装通用 CacheClient,基于泛型 + 函数式接口提供缓存穿透(空值缓存)和缓存击穿(互斥锁 / 逻辑过期)两种策略,并搭建 Caffeine + Redis 多级缓存架构
- 独立设计内容互动子系统,布隆过滤器快速否定 + Redis ZSet 热数据确认 + MySQL 兜底三层判重,将 DB 查询量降至请求总量的 ~1%;通过 Kafka 异步持久化解耦写入,Lua 脚本保证计数原子更新
- 设计双拦截器认证架构,将 Token 续期(全路径刷新 Redis Hash TTL + ThreadLocal 填充)与鉴权校验(公开路径排除)解耦为两个独立拦截器,通过 order 控制执行顺序
- 实现三层限流防刷:Redis String 频率锁(1 分钟冷却)+ 手机号/IP 双维度滑动窗口计数(24h 上限),防止验证码接口被恶意调用
- 综合运用 Redis 多种数据结构:Bitmap 实现签到与连续天数统计(BITFIELD + 位运算)、GEO 实现附近门店范围查询(GEOSEARCH + 手动分页)、Set SINTER 实现共同关注
- 通过代码审查发现并修复多个跨文件一致性问题:回滚脚本的 WRONGTYPE 错误、Kafka 手动 ACK 未调用、抢购 Lua 脚本非幂等等,建立 Bug 优先级分类体系
版本 C:精简版(适合简历空间有限 / 实习岗)
趣逛 — 本地生活社交电商平台 | Spring Boot / Redis / Kafka / Redisson
- 实现限时抢购系统:Redis Lua 原子扣减 + Kafka 异步下单 + Redisson 分布式锁 + DB 乐观锁,四层并发控制防止超卖
- 封装通用缓存工具类 CacheClient,解决缓存穿透/击穿/雪崩问题;搭建 Caffeine + Redis 多级缓存
- 基于 Redis ZSet 实现 Feed 推模式 + 游标分页,Redis GEO 实现附近门店,Bitmap 实现用户每日打卡与连续签到统计
- 独立设计内容互动子系统:布隆过滤器 + ZSet + DB 三层判重,Kafka 异步持久化 + Caffeine 多级缓存,预留微服务拆分边界
- 设计双拦截器认证链路(Token 续期与鉴权解耦)+ 三层限流防刷(频率锁 + 手机号/IP 双维度黑名单);利用 Set SINTER 实现共同关注查询
STAR 故事
STAR 1:抢购系统 — 从超卖到四层防护
适用问题:"讲一个你解决过的技术难题"、"你做过的最有挑战的功能"、"如何防止超卖"
Situation(背景)
平台需要实现限时特惠抢购功能——商家发放限量优惠券,用户在指定时间窗口内抢购。核心挑战是高并发场景下的库存一致性:抢购开始瞬间大量请求涌入,如果在 Java 层做"先查库存再扣减",由于读写不是原子操作,必然导致超卖。同时数据库无法承受瞬时的写入压力。
Task(任务)
设计一套既能防超卖、又能保护数据库的抢购方案,支持时间窗口校验、库存扣减和每人限购的原子性。
Action(行动)
我设计了四层并发控制体系:
Redis Lua 原子预扣减:将时间窗口校验、库存检查、限购验证、库存扣减、购买计数更新五个操作写入一个 Lua 脚本,利用 Redis 单线程模型保证原子执行。用 Hash 结构存储库存和时间窗口信息,通过状态码(0-5)区分成功、未开始、已结束、库存不足、超过限购等场景。
Kafka 异步落库:Lua 校验通过后,用自研的分布式 ID 生成器生成订单号,将订单事件发送到 Kafka,立即返回给用户。这样抢购接口只做 Redis 操作(微秒级),DB 写入由消费者异步完成,实现读写分离。
Redisson 分布式锁:Kafka 消费端用
lock:order:{userId}粒度的 Redisson 锁串行化同一用户的订单创建,防止 Kafka 重复投递导致重复下单。选择 Redisson 而非手写锁,是因为它提供看门狗自动续期,避免业务执行超时导致锁提前释放。DB 乐观锁兜底:
UPDATE SET stock = stock - N WHERE stock > N,即使前三层全部失效,数据库层面也不会超卖。
此外,我编写了回滚 Lua 脚本用于 DB 写入失败后的 Redis 数据回滚(恢复库存、购买计数、移除订单 ID)。
Result(结果)
- 抢购接口响应纯 Redis 操作,耗时从 DB 直写方案的百毫秒级降至微秒级
- 四层防护互为兜底,任何单层失效都不会导致超卖
- 代码审查中我还发现了回滚脚本用 String 命令操作 Hash 类型的 Bug(会导致 WRONGTYPE 运行时异常)以及字段名与抢购脚本不一致的问题,提出了修复方案
预设追问
Q: Lua 脚本会阻塞 Redis 吗?
会,但这个脚本只有几个 HGET/HINCRBY/SADD 操作,执行时间在微秒级,影响极小。Redis 有
lua-time-limit(默认 5 秒)配置作为保护。
Q: Kafka 消息丢了怎么办?
三个环节:生产端配置 retries=10 重试;Broker 端应配置多副本(当前项目是单节点,属于改进点);消费端配置了手动 ACK 但代码中未调用 acknowledge(),这也是我发现的一个待修复问题。
Q: 如果 Kafka 发送失败但 Lua 已经扣了库存?
这是"部分提交"问题。应该在 catch 块中立即执行回滚脚本恢复 Redis 数据。更完善的方案是使用本地事务表(Outbox 模式)。
STAR 2:缓存体系设计 — 穿透/击穿/雪崩的工程解法
适用问题:"说说缓存穿透/击穿/雪崩"、"你怎么设计缓存的"、"CacheClient 怎么封装的"
Situation(背景)
门店信息查询是平台最高频的接口——用户打开首页、搜索附近、查看详情都会触发。每次都查 MySQL 无法支撑。但简单加缓存后面临三个经典问题:恶意请求查询不存在的 ID 直接打穿缓存到 DB(穿透);热门门店缓存过期瞬间大量请求击穿到 DB(击穿);大量缓存同时过期导致 DB 压力骤增(雪崩)。
Task(任务)
设计一套通用的缓存解决方案,能够同时应对这三类问题,并且足够通用,让所有业务模块都能复用。
Action(行动)
我封装了一个通用的 CacheClient 工具类,利用 Java 泛型和 Function<ID, R> 函数式接口实现类型安全和业务解耦:
防穿透 —
queryWithPassThrough:采用三段式判断逻辑。isNotBlank(json)有数据直接返回;json != null但 blank 说明是之前缓存的空值"",返回 null;json == null才是真正的缓存未命中,查 DB。DB 也没有就缓存空值,TTL 设 2 分钟(短 TTL 兼顾数据最终创建的场景)。防击穿 — 互斥锁
queryWithMutex:基于SET NX EX实现互斥锁,获取锁失败的线程 sleep 50ms 后重试。获取锁成功的线程查 DB 并重建缓存。防击穿 — 逻辑过期
queryWithLogicalExpire:不设 Redis TTL(数据永不真正过期),在数据中嵌入逻辑过期时间。发现逻辑过期后,尝试获取锁,成功则提交异步重建任务到独立线程池,失败则直接返回旧数据。保证了高可用性(始终有数据返回)。防雪崩:在写入缓存时给 TTL 加随机偏移量,避免大量 key 同时过期。
同时,在内容互动子系统中引入了 Caffeine + Redis 多级缓存:Caffeine 作为 L1 本地热点缓存(100 条,2 小时过期),Redis 作为 L2 分布式缓存,MySQL 作为 L3 兜底。通过 @Scheduled 定时任务每 2 小时从 Redis 预热热点数据到 Caffeine。
Result(结果)
- CacheClient 被门店查询、券查询等多个模块复用,避免了重复代码
- 互斥锁方案保证数据一致性(等到最新数据),逻辑过期方案保证高可用(始终有返回),业务方根据场景选择
- 代码审查中发现 CacheClient 内部锁的
unlock()直接DELETE而没有 owner 校验(与其他锁实现的 Lua 原子释放形成对比),属于可改进点
预设追问
Q: 互斥锁和逻辑过期怎么选?
数据一致性要求高用互斥锁(如库存),高可用优先用逻辑过期(如门店信息)。前者会阻塞等待,后者可能返回短暂旧数据。
Q: queryWithMutex 的递归重试有什么问题?
无最大重试次数限制,极端情况下可能 StackOverflow。应改为 while 循环 + 计数器 + 最大重试次数。
Q: 多级缓存的一致性怎么保证?
当前方案 L1 更新依赖定时任务,有最多 2 小时延迟。更好的方案是 Redis Pub/Sub 通知所有实例失效本地缓存,或用 Canal 监听 Binlog 驱动更新。
STAR 3:分布式锁演进 — 从 Bug 到 Lua 原子释放
适用问题:"讲讲分布式锁"、"你遇到过什么并发 Bug"、"为什么要用 Lua 脚本"
Situation(背景)
抢购消费端需要用分布式锁保证同一用户不会重复下单。最初实现的锁存在一个隐蔽的并发 Bug——释放锁时先 GET 判断 owner 再 DELETE,这两步不是原子操作,中间可能发生 GC 暂停或线程切换,导致误删别人的锁。
Task(任务)
修复分布式锁的原子性问题,并从中抽象出一套安全的锁实现方案。
Action(行动)
我梳理了分布式锁的五步演进路线:
- V1 — 基础 SETNX:
SETNX lock 1。问题:无过期时间,进程崩溃导致死锁。 - V2 — 加过期时间:先 SETNX 再 EXPIRE。问题:两个命令不是原子的,SETNX 成功但 EXPIRE 前宕机 → 死锁。
- V3 — SET NX EX:
SET lock value NX EX 10,一条命令原子完成。问题:value 固定,释放锁时无法区分 owner → 误删。 - V4 — 加 owner 标识:value =
UUID-ThreadId,释放前 GET 比对。问题:GET 和 DELETE 之间不是原子的。具体场景:线程 A GET 确认是自己的锁 → GC 暂停 → 锁过期 → 线程 B 获取锁 → 线程 A 恢复后 DELETE → 删了线程 B 的锁。 - V5 — Lua 原子释放:将 compare-and-delete 封装到 Lua 脚本中(
if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) end),Redis 单线程执行保证原子性。
锁的 value 设计:static final UUID(类加载时生成,区分 JVM 实例)+ Thread.currentThread().getId()(区分线程)。UUID 用 static final 而非每次新建,确保同一 JVM 内同线程的不同锁对象被视为同一 owner。
在抢购消费端,由于还需要可重入和自动续期,最终选择了 Redisson。Redisson 内部使用 Hash 结构(lock_key → {owner: count})实现可重入,看门狗每 10 秒检查并续期到 30 秒。
Result(结果)
- 锁释放从非原子的 GET+DELETE 改为 Lua 脚本原子执行,消除了并发误删风险
- 代码中保留了 Bug 版本作为对照,在代码审查时也发现了缓存工具类中同样存在的无 owner 校验问题
- 面试中能清晰讲出五步演进路线,展示对分布式锁本质问题的深入理解
预设追问
Q: Redis 主从切换时锁会丢吗?
会。Redis 主从异步复制,主节点 SET NX 成功后宕机,从节点晋升后没有这条锁记录。Redisson 提供 RedLock 算法在多个独立实例上加锁,但 Martin Kleppmann 和 Antirez 有过关于其正确性的争论。
Q: 手写锁支持可重入吗?
不支持。同一线程再次 tryLock 时 SET NX 会失败。Redisson 用 Hash 的 count 字段实现可重入:加锁 count++,解锁 count--,count=0 时才真正释放。
STAR 4:内容互动子系统 — 三层判重与独立模块设计
适用问题:"你怎么设计一个高并发的点赞功能"、"讲讲布隆过滤器的应用"、"你有没有做过模块拆分的设计"
Situation(背景)
平台的种草笔记支持点赞互动,这是一个高频操作,需要即时反馈且不能重复计数。如果每次点赞都查 MySQL 判断是否已赞,数据库压力太大;如果只用 Redis 存全部历史记录,内存会爆炸。同时,互动功能在架构上应该具备独立演化的能力,为将来拆分微服务做准备。
Task(任务)
设计一个高性能、可扩展的内容互动系统:低延迟的重复判断、异步持久化、合理的内存控制,并在代码层面预留微服务边界。
Action(行动)
三层判重架构:
布隆过滤器(Redisson RBloomFilter):100 万容量、1% 误判率,仅占 ~1.2MB 内存。判"不存在"时 100% 准确,直接返回"未赞"。过滤掉约 95% 的请求,大幅减少后续层的压力。
Redis ZSet(热数据层):按用户维度和内容维度各建一个 ZSet,score 为时间戳。限制每个 ZSet 最多 200 条(异步裁剪旧数据),兼顾精确性和内存控制。100 万用户 × 200 条 × 20 bytes ≈ 4GB,可接受。
MySQL(兜底层):取用户对某篇内容的最新一条记录,通过
type字段判断当前状态。只有布隆过滤器误判(~1%)且 ZSet 未命中(历史互动超过 200 条)时才查 DB。
异步持久化:点赞/取消赞操作通过 Kafka 异步写入 DB,接口只做布隆过滤器标记 + 发 Kafka 消息,保证低延迟。消费者端批量更新 Redis 计数和 ZSet,并回填 Caffeine 本地缓存。
模块独立设计:将互动功能放在独立的包下,拥有自己的 Controller、Service、Mapper 和 Kafka Event,与主应用只通过 UserService 共享用户数据。通过 scanBasePackages 统一扫描,在不改架构的情况下保持独立性。
Result(结果)
- 三层过滤使 DB 查询量降低到请求总量的 ~1%,判重响应时间从 MySQL 查询的毫秒级降至布隆过滤器的微秒级
- 独立的包结构和接口边界,未来拆分微服务时只需独立部署并把 UserService 调用改为 RPC
- 代码审查中发现了 ZSet 裁剪逻辑的 Bug(
reverseRangeByScore误用分数范围而非下标范围)和计数器非原子更新的问题
预设追问
Q: 布隆过滤器的假阳性有什么影响?
假阳性意味着"没赞但误判为赞了",会多走 ZSet + DB 两层查询,但不影响正确性。取消赞后布隆过滤器不支持删除,但后续层通过查最新记录的 type 字段兜底。
Q: 为什么不用 Counting Bloom Filter 支持删除?
Counting Bloom Filter 将每个 bit 扩展为计数器(通常 4 bit),内存是标准布隆过滤器的 4-8 倍。对于点赞场景,误判只是多查一次 DB,用标准布隆过滤器的性价比更高。
STAR 5:代码审查 — 跨文件一致性 Bug 排查
适用问题:"你是怎么保证代码质量的"、"你发现过什么隐蔽的 Bug"、"你对 Code Review 有什么经验"
Situation(背景)
项目中抢购、缓存、互动等模块涉及多个文件的协作(Java 代码、Lua 脚本、Kafka 配置)。单看某一个文件时逻辑看起来正确,但跨文件对照后发现了多个隐蔽的一致性问题。
Task(任务)
系统性地审查项目代码,特别是跨文件数据流的一致性,识别并归类所有潜在问题。
Action(行动)
我按照"端到端数据流追踪"的方法进行审查:从 HTTP 请求入口出发,跟踪数据经过 Controller → Service → Redis/Lua → Kafka → Consumer → DB 的完整链路,重点对比同一份数据在不同环节中的操作方式是否一致。
发现的关键问题(按优先级排列):
P0 — 回滚脚本 WRONGTYPE:抢购脚本中库存用
HINCRBY(Hash 操作),但回滚脚本中用INCRBY(String 操作),执行时 Redis 直接报 WRONGTYPE 异常,回滚完全失效。P0 — 缺少 @Transactional:抢购消费端的订单创建方法中,DB 扣库存和保存订单不在一个事务中,步骤 1 成功但步骤 2 失败会导致库存永久丢失。catch 块只回滚 Redis,不回滚 DB。
P1 — Kafka ACK 未调用:配置了
ack-mode: manual但消费者方法签名中没有Acknowledgment参数,offset 不提交导致重启后重复消费全部消息。P1 — 抢购 Lua 非幂等:抢购脚本没有检查 orderId 是否已处理,重复执行会重复扣库存。应在扣减前加
SISMEMBER检查。P1 — Hash 字段名不一致:抢购脚本中 buyCount 字段用
userId:123格式,回滚脚本中用123,导致回滚时 HGET 返回 nil,限购计数不恢复。
我为每个 Bug 编写了修复方案和影响分析,并按 P0/P1/P2/P3 分级,形成了完整的问题清单。
Result(结果)
- 建立了一套"跨文件一致性审查"方法:对比同一 Redis key 在不同 Lua 脚本/Java 代码中的操作命令和字段名
- 这些都是典型的"单文件看不出、端到端才能发现"的 Bug,展示了系统性代码审查的价值
- 总结出经验:涉及多语言协作(Java + Lua + YAML 配置)的场景,应建立统一的常量/Key 管理机制,避免硬编码导致的不一致
预设追问
Q: 你怎么防止这类问题再次出现?
三个措施:第一,Redis key 和字段名集中定义在常量类,Lua 脚本中也通过 ARGV 传入而非硬编码;第二,关键数据流编写集成测试,覆盖正常路径和失败回滚路径;第三,Kafka 配置与代码必须联合审查,配了 manual ACK 就必须在代码中体现。
STAR 6:Feed 流设计 — 推模式与游标分页
适用问题:"你怎么实现 Feed 流的"、"说说推模式和拉模式"、"分页怎么做的"
Situation(背景)
平台的种草社区需要 Feed 流功能:用户关注的达人发布探店笔记后,应该出现在自己的关注页时间线中。Feed 流的核心挑战是数据实时性、分页正确性和高粉丝量场景下的性能。
Task(任务)
设计一套 Feed 流方案,支持实时推送、正确分页(不重复不遗漏),并考虑扩展性。
Action(行动)
选择推模式(写扩散):发布笔记时,查询所有粉丝,将笔记 ID 写入每个粉丝的 Redis ZSet 收件箱,score 为发布时间戳。选推模式是因为本平台用户粉丝量中等(非微博级),写扩散成本可控,且读取零延迟。
用 ZSet 而非 List 的原因:Feed 是动态数据集,传统的
ZREVRANGE(按下标分页)在有新数据插入时会导致下标偏移,出现翻页重复。改用ZREVRANGEBYSCORE(按 score 范围查询),以上一页最小时间戳作为下次查询的上界,不受新元素插入影响。游标分页算法:核心是处理同分数元素(多篇笔记在同一毫秒发布)。每次查询后记录最小 score(
minTime)和该 score 出现的次数(offset)。下次查询时max=minTime, LIMIT offset count,跳过已返回的同分数元素。ORDER BY FIELD保序:MySQL 的IN查询不保证顺序,用ORDER BY FIELD(id, ...)强制按 Redis 返回的时间排序。
Result(结果)
- 游标分页算法完全解决了动态数据集下的翻页重复和遗漏问题,无论查询期间有多少新笔记推送都不会影响正确性
- 后续可以通过将同步推送改为 Kafka 异步分批推送来支持大 V 场景
- 识别了推模式的局限性:Feed ZSet 无限增长需要定期清理、同步推送阻塞发布接口、大 V 场景需要推拉结合
预设追问
Q: 大 V 有 100 万粉丝怎么办?
同步推送 100 万次 ZADD 会阻塞发布接口。方案一:推拉结合,粉丝数超过阈值的用户走拉模式。方案二:发布时只写 DB + 发 Kafka 消息,消费者分批推送,每批 1000 个粉丝。
Q: Feed 数据无限增长怎么处理?
三种方案:定期
ZREMRANGEBYSCORE清理 N 天前的数据;ZREMRANGEBYRANK限制 ZSet 最多保留 1000 条;近期 Feed 在 Redis、历史 Feed 查 DB。
通用追问准备
"你遇到过什么困难?"
可以根据面试氛围选择以下方向回答:
- 技术挑战:→ 讲 STAR 1(抢购四层防护)或 STAR 3(分布式锁演进)
- 排查能力:→ 讲 STAR 5(跨文件一致性 Bug)
- 设计取舍:→ 讲 STAR 2(互斥锁 vs 逻辑过期)或 STAR 6(推模式 vs 拉模式)
"如果让你重新设计,你会改什么?"
- 抢购链路:加入 Kafka 发送失败时的 Redis 回滚 + 幂等消费表 + 手动 ACK 修复
- 缓存一致性:引入 Canal 监听 Binlog,替代当前的"先更新 DB 再删缓存"
- 监控体系:当前项目没有任何监控指标,应加入 Prometheus + Grafana 监控 Redis 连接池、Kafka 消费 lag、线程池队列积压
- 模块拆分:将互动子系统拆为独立微服务,通过 RPC 或消息队列与主应用通信
"这个项目你最得意的部分是什么?"
两个层面:
技术层面:抢购的四层并发控制设计,每一层都有明确的职责和失效兜底,不是简单的加锁了事,而是考虑了从 Redis 到 Kafka 到 DB 整条链路的一致性。
工程层面:通过端到端的代码审查发现了多个跨文件的隐蔽 Bug(回滚脚本 WRONGTYPE、Kafka ACK 未调用等)。这些问题单看某一个文件都发现不了,需要追踪完整的数据流才能暴露,这让我认识到跨文件、跨语言的一致性审查在实际项目中的重要性。
"介绍一下你的项目"
趣逛是一个面向城市年轻用户的本地生活社交电商平台,核心功能包括附近门店发现、限时特惠抢购、种草笔记社区和社交关注。
技术上用 Spring Boot + Redis + Kafka + Redisson 构建,重点解决了几个典型分布式问题:
- 缓存穿透/击穿/雪崩:封装通用 CacheClient,提供空值缓存、互斥锁、逻辑过期三种策略
- 高并发抢购:Redis Lua 脚本原子扣减 + Kafka 异步落库,四层并发控制防超卖
- 分布式锁:从手写锁演进到 Redisson,重点是 Lua 脚本原子释放
- Feed 流:基于 Redis ZSet 的推模式 + 游标分页
- 内容互动:独立子系统,布隆过滤器 + ZSet + DB 三层判重
关联文档
- 00-项目总览与架构 — 项目全景
- 01-Redis缓存设计 — STAR 2 背景
- 02-分布式锁 — STAR 3 背景
- 03-秒杀系统 — STAR 1 背景
- 05-Feed流与Timeline — STAR 6 背景
- 07-点赞子系统 — STAR 4 背景
- 11-已知问题与优化方向 — STAR 5 背景