做黑马点评这个项目的人,这几年真的是肉眼可见地多。我身边投Java岗的朋友,十个里有六七个简历上都挂着它,剩下几个在做苍穹外卖或者自己魔改的商城。黑马点评这个项目之所以这么受追捧,原因其实很简单:它是少有的、把Redis用到了“有点过分”程度的单体项目。别人还在讲缓存穿透怎么解决,它已经让你自己动手写布隆过滤器、自己做分布式锁、自己做Stream消息队列。对于校招和初中级社招来说,只要你把这里面的逻辑真正吃透,面试官很难在缓存和并发这个话题上问倒你。
这篇内容不是给你复述黑马课程里的代码,而是把整个项目拆成面试官真正会追问的知识点,再结合我自己的面经和辅导过的同学遇到的真实情况,告诉你每个模块应该怎么讲、哪些坑必须提前补。无论你是刚把项目敲完准备背题,还是已经在面试中被问到过“你这个分布式锁有没有问题”,这篇文章都值得你花半小时读完。
1. 项目整体梳理:从业务功能到技术亮点的映射
1.1 这个项目到底在做什么
黑马点评模拟的是一个类似大众点评的本地生活类App,核心功能包括短信验证码登录、商品查询与缓存、秒杀抢购、好友关注与Feed流推送、附近商铺查询以及UV统计。虽然项目体量不大,但它把一个典型的业务闭环完整地做了出来:用户登录、浏览、下单、互动、个性化查询,每一个环节都塞进了对应的Redis高级特性。
很多同学在准备面试时容易犯一个错误,就是把自己当成“代码的搬运工”,只记得项目里用了什么技术,但讲不出这些技术解决的到底是什么问题。比如你写了缓存,那缓存到底缓存了什么?为什么这个数据适合缓存?如果数据更新了怎么办?这些才是面试官想听的。
黑马点评的价值恰恰在于,它的每一个技术选型背后都有明确的业务痛点。比如商铺数据是典型的读多写少场景,用Redis缓存可以大幅降低数据库压力;秒杀活动是典型的瞬时高并发场景,必须用分布式锁+Lua脚本保证不超卖;Feed流则是典型的推拉结合场景,需要根据粉丝量级选择推送还是拉取模式。
面试时只要能把这些“技术-业务-效果”的映射关系讲清楚,就已经超过了一半只会背八股文的候选人。
1.2 技术栈与模块结构盘点
我先把项目的主要技术栈列出来,你对照着查漏补缺:
| 技术 | 在项目里承担的角色 | 面试常见追问点 |
|---|---|---|
| Redis | 缓存、分布式锁、消息队列、计数器、GEO | 缓存一致性、锁的可靠性、Stream和RabbitMQ的对比 |
| Spring Boot | 应用基础框架 | 自动配置原理、启动流程 |
| MyBatis Plus | 数据访问层 | 分页、条件构造器源码 |
| MySQL | 业务主数据库 | 索引优化、事务隔离级别 |
| Nginx | 前端静态资源部署 | 反向代理、负载均衡策略 |
| Lombok / Hutool | 开发效率工具 | 这俩一般不问,但Hutool里用到的工具类可以提一嘴 |
从功能模块上看,黑马点评最核心的几条线是:
- 登录模块:用Redis保存短信验证码和登录令牌(Token),替代传统的Session方案,这是分布式环境下会话管理的经典做法;
- 商铺查询模块:以商铺详情和商铺类型数据为例,引出缓存穿透、缓存击穿、缓存雪崩三大问题;
- 秒杀模块:基于乐观锁和分布式锁解决超卖问题,再引入Lua脚本保证原子性;
- 关注与Feed流模块:使用Redis的Set、ZSet、Stream实现关注关系和消息推送;
- 附近商铺模块:基于Redis GEO实现按距离范围搜索;
- UV统计模块:基于HyperLogLog做基数统计,解决海量去重计数问题。
这些模块单独拿出来任何一个,都能撑起一个五分钟的“项目亮点”介绍。但前提是你真的去看了源码,而不是只在Idea里Ctrl+C、Ctrl+V。下一步我逐个模块拆解,重点讲清楚底层原理和面试追问时的应对思路。
2. 核心知识点逐个拆解:面试官真正会深挖的地方
2.1 缓存三兄弟:穿透、击穿、雪崩的完整处理方案
缓存穿透、缓存击穿、缓存雪崩这“三兄弟”几乎是Java面试的必考题。黑马点评里商铺详情查询模块,把这三大问题的解决方案全部用上了,所以面试官特别喜欢借着这个项目问。很多人能背出定义,但一到“你项目里怎么解决的”就支支吾吾。下面我把每个问题的处理逻辑都展开讲一遍。
缓存穿透指的是查询一个根本不存在的数据,缓存和数据库里都没有,导致每次请求都直接打到数据库。恶意攻击时,大量这种请求足以拖垮数据库。黑马点评里的做法是:如果从数据库查询的结果为空,也把这个空值写入Redis,并设置一个较短的过期时间(比如2到5分钟)。这样后续同样的请求就直接命中空值缓存,不会穿透到数据库。
这个方案简单有效,但有一个很核心的漏洞:如果恶意请求每次都伪造不同的ID,空值缓存就完全失效了。所以在面试里一定要主动补充更进阶的方案——布隆过滤器。你可以在请求进来时先用布隆过滤器判断ID是否存在,布隆过滤器说“不存在”就一定不存在,直接拦截掉;说“存在”才放行继续查缓存和数据库。黑马点评的视频里没有写布隆过滤器,但这恰恰是你展示思考深度的机会,可以跟面试官说“生产环境我会在缓存前加一层布隆过滤器来兜底”,这就是加分项。
缓存击穿指某个热点key在缓存过期的瞬间,大量并发请求同时打到数据库。黑马点评里用的是互斥锁方案:在缓存失效后,不是让所有线程都去重建缓存,而是先让一个线程获取分布式锁去查数据库并重建缓存,其他线程先等待,等缓存重建完成后再次查询缓存。我用伪代码表示一下核心逻辑:
public Shop queryWithMutex(Long id) { // 1. 先查缓存 String shopJson = redisTemplate.opsForValue().get(CACHE_SHOP_KEY + id); if (isNotNull(shopJson)) { return JSONUtil.toBean(shopJson, Shop.class); } // 2. 缓存未命中,尝试获取互斥锁 String lockKey = "lock:shop:" + id; boolean isLock = tryLock(lockKey, 10, TimeUnit.SECONDS); if (!isLock) { // 3. 没拿到锁,休眠后重试 Thread.sleep(50); return queryWithMutex(id); } try { // 4. 拿到锁后再次查缓存(double check),防止上一个线程刚重建完缓存 shopJson = redisTemplate.opsForValue().get(CACHE_SHOP_KEY + id); if (isNotNull(shopJson)) { return JSONUtil.toBean(shopJson, Shop.class); } // 5. 查数据库并重建缓存 Shop shop = getById(id); // 模拟缓存重建耗时 Thread.sleep(200); redisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(shop), 30, TimeUnit.MINUTES); return shop; } finally { // 6. 释放锁 unlock(lockKey); } }这段代码里有两个细节面试官会追问:第一,为什么拿到锁之后还要再次查缓存?因为可能存在两个线程几乎同时获取锁,第一个线程释放锁后,第二个线程恰好拿到了锁,如果不做double check,就会重复查询数据库。这种“双重检查锁”的思路和单例模式里的DCL是一模一样的,可以顺便提一嘴,显得你基础扎实。第二,为什么休眠时间取50毫秒?这是经验值,太短会导致CPU空转严重,太长又会拖慢响应时间。更好的做法是使用Java的LockSupport.parkNanos来做更精细的等待控制。
缓存雪崩指大量Key同时过期或Redis宕机,导致请求全部打到数据库。黑马点评课里给出了两个方案:给缓存过期时间加随机值,避免同一时刻集体失效;以及用Redis集群保证高可用。这俩方案方向上没错,但在面试里最好补充一点——你项目里缓存过期时间是30分钟,实际生产环境更常见的做法是设置一个基础过期时间加一个随机偏移量,比如30分钟 + random(0, 300秒),这样可以把过期时间打散。另外,如果公司允许,还可以考虑服务降级:在数据库压力过大时,直接返回一些兜底数据(比如默认的商铺信息)而不是让用户看到报错页面。这样讲,面试官会觉得你不仅知道解决方案,还想到了异常场景下的兜底策略。
2.2 分布式锁的设计:为什么不能只用Synchronized
黑马点评里最容易暴露基础薄弱的地方,就是分布式锁这个模块。很多同学知道服务端要用锁来防止并发问题,但提到分布式锁只说“用了Redis的setnx”,这就太浅了。
先理解为什么单机锁不行:如果只有一台服务器,用JVM内置的synchronized或者ReentrantLock就够了。但生产环境不可能只有一台服务器,请求会通过负载均衡分发到不同机器,每一台机器都有自己的JVM锁,只能锁住本机的线程,锁不住其他机器的线程。这个时候就需要一个“所有人都能看到的、公共的锁”,Redis天然适合做这件事,因为所有服务器都连同一个Redis。
黑马点评第一版分布式锁是基于setnx命令实现的:SET lock 1 NX EX 10。NX表示只有当key不存在时才设置成功,EX表示设置10秒过期时间,防止获取锁的线程中途宕机导致死锁。这个逻辑本身没问题,但面试官一定会追问:“如果业务执行时间超过了锁的过期时间怎么办?”这就是分布式锁领域最容易翻车的点——锁过期释放,但业务还没执行完,另一个线程拿到了锁,导致并发问题。
解决思路有两种。第一种是给锁续期,也就是开一个守护线程定时检查锁是否还持有,如果业务没执行完就自动续期。Redisson框架的watchDog机制就是这个原理,默认每10秒检查一次,如果锁还在就续期到30秒。这是主流方案,也是你可以在面试里展示自己了解分布式锁生态的机会。第二种是不要用Redis实现分布式锁,直接用ZooKeeper的临时顺序节点,通过Watch机制实现锁的自动释放,可靠性和公平性都比Redis强,但性能略逊、运维成本更高。
还有一点必须提到:如果你们是Spring Boot项目,一般不需要自己手写锁,直接用Redisson的RLock,默认就带看门狗自动续期。黑马点评里为了教学是自己手写的,但面试时主动提一句“我知道生产环境会用Redisson来避免续期问题”,比傻乎乎背代码效果好得多。
另外关于锁的可重入性:setnx实现的简单锁是不可重入的。如果同一个线程在持有锁时又调用了一个也需要同一把锁的方法,就会死锁。Redisson的锁是基于Redis的Hash结构实现的,支持可重入。这些细节都应该提前整理到自己的面试笔记里,因为真被问到的时候,现场思考是来不及的。
2.3 秒杀下单全链路:从乐观锁到Lua脚本
秒杀这一块是整个项目里技术密度最高的部分,也是面试官最爱深挖的模块。它涉及四个核心问题:超卖问题、一人一单问题、下单流程的原子性问题、以及高并发下的性能优化。
超卖问题的根源在于数据库层面的查询-判断-扣减不是原子操作。比如库存还剩1件,两个请求同时查到库存为1,都判断可以扣减,最终都执行了UPDATE stock SET count = count - 1,结果库存变成-1。黑马点评里最开始用的方案是乐观锁:在更新时加上库存大于0的条件。
UPDATE tb_seckill_voucher SET stock = stock - 1 WHERE voucher_id = #{voucherId} AND stock > 0;这种写法利用数据库的行锁保证并发安全,stock > 0就是一个版本校验条件,只要库存减到0,后面的更新操作影响行数就是0,事务回滚。这是最经典的乐观锁思路。面试时要把这个sql解释清楚,说明影响行数为0时如何判断秒杀失败,以及为什么数据库本身的行锁能够解决并发问题。
一人一单问题解决的是同一个用户不能重复秒杀。黑马点评的判断逻辑是查询订单表里当前用户和商品ID是否存在记录,如果存在就不允许再次下单。但这里有一个隐蔽的问题:在高并发场景下,两个请求可能同时通过“订单不存在”的判断,接着同时插入订单,导致一人多单。这个问题的解决办法,在单体应用里可以对用户ID加synchronized锁来保证同一用户串行化,但生产环境是集群,就必须用前面说的分布式锁了。锁的粒度要细致,userId.toString().intern()可以作为锁的key,但intern在大量不同字符串时可能有性能问题,所以更稳妥的是直接用Redis分布式锁,key设计成lock:order:userId。
Redis + Lua脚本保证原子性是秒杀模块最亮的点。黑马点评的最终版本,把判断库存是否充足、判断用户是否已下单、扣减库存、保存订单这些步骤全部封装进一个Lua脚本,通过Redis执行。Lua脚本在Redis中是原子执行的,要么全部成功,要么全部失败,不会出现中间状态。面试时如果能直接背出或默写出Lua脚本关键片段,会特别加分:
-- 1. 判断库存是否充足 if (redis.call('exists', KEYS[1]) == 0) then return -1; end if (tonumber(redis.call('get', KEYS[1])) <= 0) then return -2; end -- 2. 判断用户是否已下单 if (redis.call('sismember', KEYS[2], ARGV[1]) == 1) then return -3; end -- 3. 扣库存 + 记录用户 redis.call('incrby', KEYS[1], -1); redis.call('sadd', KEYS[2], ARGV[1]); return 1;这个脚本执行完成后,还需要把订单信息发送到消息队列异步保存到数据库。黑马点评里用的是Redis的Stream数据结构,面试时如果被问到“为什么不用RabbitMQ”,可以从这几个角度回答:第一,项目本身已经引入了Redis,再额外引入RabbitMQ会增加部署和运维成本;第二,Redis Stream从Redis 5.0开始支持,具备消费组、消息持久化、ACK机制,足以应对秒杀这种异步削峰场景;第三,真正海量消息、需要复杂路由、可靠投递的场景,才会考虑引入专业的MQ中间件。这个回答既说明你会权衡技术选型,又给自己留了台阶。
2.4 关注Feed流与附近商铺:Redis数据结构的组合拳
除了缓存和秒杀,黑马点评还有两个值得讲的模块,一个是关注Feed流,另一个是附近商铺。这两个模块难度适中,但很能体现你对Redis数据结构的熟练度。
Feed流,简单说就是“刷动态”。用户关注了很多人,每个被关注的人发了笔记后,关注者如何看到这些笔记?黑马点评采用的是推模式,也就是当作者发布笔记后,系统把笔记ID写入每个粉丝的收件箱。收件箱在Redis里用ZSet存储,score就是时间戳,这样拉取动态时直接按时间倒序排列即可。
# 发布笔记时,向粉丝收件箱推送笔记ID ZADD feed:{followerId} {timestamp} {noteId} # 滚动分页查询收件箱 ZREVRANGEBYSCORE feed:{followerId} (maxScore minScore LIMIT 0 5这里有一个面试官特别喜欢的细节:Feed流的滚动分页为什么不用Page分页?因为Feed流的数据是实时变化的,如果用户不停发笔记,用LIMIT offset, size这种分页方式,在翻页时offset会随着新数据的插入而偏移,导致读到重复或漏掉内容。所以黑马点评用的是类似“上次查询的最小时间戳”作为游标,持续往前翻。这个小知识点非常值得展开,因为它把“Redis实战”和“系统设计”两个维度都拉满了。
附近商铺模块就简单一些,用的是Redis的GEO数据结构,底层基于Sorted Set实现。添加商铺时执行GEOADD shopGeo {lng} {lat} {shopId},查询附近时执行GEOSEARCH shopGeo FROMLONLAT {lng} {lat} BYRADIUS 5000 m ASC。GEO本质上是把经纬度编码成一个52位的整数作为score,所以可以用ZSet相关的命令去操作它。这个模块面试时不太容易深挖,但你至少要知道GEO底层是ZSet,以及为什么能支持范围查询。
3. 面试怎么讲这个项目才能拿高分
3.1 一句话版本的项目导流话术
我辅导过很多同学,他们最大的问题不是不会技术,而是不会“讲项目”。面试官一天面五六个人,每个人都说自己做过黑马点评,你要是照着课程里的介绍从头背到尾,面试官根本听不进去。
你需要在30秒内用一个“痛点-方案-效果”模型把项目定调。比如我常用的开场是:
“我做的是一个高并发的本地生活类应用,核心功能包括商铺查询和秒杀。这个项目最大的特点是大量使用Redis来解决高并发场景下的性能和数据一致性问题,比如用缓存重建逻辑处理缓存击穿、用分布式锁+Lua脚本保证秒杀不超卖、用Redis Stream做异步订单处理。整个项目在压测环境下,秒杀接口的QPS从最初的几百提升到几千的量级。”
这一段话看起来简单,但效果好是因为它同时完成了三件事:第一,告诉面试官项目的业务背景和核心场景;第二,主动抛出关键词——缓存击穿、分布式锁、Lua脚本,引导面试官往你准备过的方向问;第三,提到了压测数据,暗示你做过性能验证,而不是只会跟着视频敲代码。
注意,这里要如实陈述。如果没做过压测,就说“通过接口模拟并发测试验证了结果”,不要硬造一个QPS数字。面试官一旦追问“你怎么测的、并发数多少、Redis配置多少”,数据说谎很容易被戳穿。
3.2 面试官追问时,最值得主动展开的三个话题
除了被动回答问题,你还需要准备两个“主动加分点”。这个技巧很多人不知道:面试官问你一个开放性话题时,你可以把话题引导到自己准备最充分的模块上。
第一个推荐的主动展开话题是缓存击穿的互斥锁方案与Redisson看门狗的区别。你可以主动说“我项目里手写了一个互斥锁,但在生产环境我更倾向于用Redisson,因为它解决了锁自动续期的问题”。这句话能引出来的后续问题包括:Redisson的watchDog实现原理、Redis分布式锁的红锁争论(Redlock)、ZooKeeper锁和Redis锁的对比、可重入锁的实现方式。这些都是你提前背好就能稳稳拿下的题。
第二个推荐的主动展开话题是Stream消息队列与RabbitMQ的选型对比。这几乎是为黑马点评量身定做的高频追问,因为课里用了Stream,而很多公司实际用的是RabbitMQ或RocketMQ。你主动对比两者,既能展示自己会ORM之外的工具,又暴露出你了解技术特性的权衡。
第三个是GEO的底层结构为什么是ZSet,以及ZSet的跳表实现原理。这个问题可以把数据结构基础、Redis底层编码、业务应用串起来,属于一个“低成本、高回报”的话题。
3.3 容易被问挂的“死角”,提前自查
很多候选人死在项目本身之外的基础问题上。我整理几个黑马点评相关的高频死角,你对照自查:
第一,Redis的过期删除策略。黑马点评里设置了大量带过期时间的key,面试官大概率会问“Redis怎么清理过期key”。你要能回答:惰性删除(每次访问时检查是否过期)+ 定期删除(每秒随机抽查部分key清理过期key),以及内存淘汰机制(LRU、LFU、volatile-lru、allkeys-lru)的触发条件。
第二,ZSet的实现原理。Feed流和GEO都用了ZSet,所以你要能画出跳表的结构,说明为什么ZSet用跳表不用红黑树,以及压缩列表(ZipList)到跳表的转换条件。
第三,Lua脚本为什么原子。要能说清楚Redis是单线程模型,Lua脚本在执行期间不会被其他命令打断,所以整个过程不会有并发穿插。这是整个秒杀方案成立的前提,不能含混。
第四,MySQL和Redis的数据一致性。项目里大量数据先更新数据库再删除缓存,面试官会问“如果数据库更新成功但缓存删除失败怎么办”。你要能说出延迟双删、消息队列补偿、设置合理过期时间兜底这几个层面的方案,并且理解为什么用“删除缓存”而不是“更新缓存”。
第五,事务与Lua的对比。有人会问“为什么不用Redis事务(MULTI/EXEC)来保证原子性”。你要能说清楚:Redis事务虽然能保证多个命令按顺序执行,但不会回滚,而且中间不能根据前一个命令的结果做条件判断,而Lua脚本可以,所以秒杀场景下Lua完胜。
4. 实际面经复盘:我整理的高频问题与踩坑记录
4.1 高频面试问题速查表
我在各个技术社区、面试群里收集了很多黑马点评相关的问题,按照出现频率整理成下面这张表。你可以拿来自测,如果能不看答案把每个问题讲清楚两分钟以上,这个项目就算吃透了。
| 问题 | 考察点 | 回答要点 |
|---|---|---|
| 黑马点评里Redis解决了哪些业务痛点? | 项目整体理解 | 从缓存、分布式锁、消息队列、Feed流、GEO、UV统计六个方面回答 |
| 缓存穿透、击穿、雪崩的区别?你分别怎么处理? | 缓存基础 | 穿透用空值缓存+布隆过滤器;击穿用互斥锁+逻辑过期;雪崩用随机过期时间+集群 |
| 你的分布式锁是怎么实现的?有什么问题? | 分布式锁深入 | setnx + 过期时间 -> 可重入 -> 自动续期 -> 红锁争论 |
| 秒杀怎么防止超卖? | 并发与数据库 | 乐观锁升级为Lua脚本原子操作 |
| 怎么保证一人一单? | 锁粒度与分布式 | 用用户ID作为锁key做分布式锁 |
| Lua脚本为什么能保证原子性? | Redis单线程模型 | 脚本执行期间不会被其他命令中断 |
| Redis Stream和RabbitMQ怎么选? | 技术选型能力 | 从项目成本、功能需求、可靠性和消息堆积能力四个维度对比 |
| Feed流为什么用推模式而不用拉模式? | 系统设计 | 推模式写入多但读取快;拉模式存储省但查询慢;粉丝量大时可考虑推拉结合 |
| GEO底层是什么数据结构? | Redis底层原理 | Sorted Set + GeoHash编码 |
| 登录Token为什么要存Redis?和JWT比有什么优劣? | 登录方案设计 | Redis可主动失效、可做服务端状态管理;JWT无状态、无法主动踢人 |
4.2 我踩过的坑和总结的临场技巧
我在准备这个项目的时候,印象最深的一个坑就是只背代码不背原理。刚开始模拟面试时,面试官问我“为什么用setnx做锁而不是直接INCR”,我愣了几秒,因为我确实没想过这个问题。后来我才明白,setnx自带的过期时间是原子设置的,如果先SETNX再EXPIRE分两步执行,第二步失败就会造成死锁。INCR虽然也能实现锁,但判断“返回值为1才是成功”会有一个不太直观的语义,而且同样要配合EXPIRE使用。这些细节其实才是面试官区分“背过”和“理解”的关键。
另一个经验是,回答项目问题时一定要学会“结构化表达”,也就是先给结论、再给依据、最后补充细节。比如被问到“缓存击穿你如何解决”,不要上来就背代码。你可以先说“我用的是互斥锁方案,同时对比了逻辑过期方案,最后选择了前者”,然后再解释两者原理和优缺点。这样的回答框架,会让面试官觉得你思路清晰,而不是在背诵。
最后一个建议是:一定要自己做一次完整的项目源码阅读和笔记整理,不要只看视频。看视频是“输入”,面试是“输出”,中间需要靠笔记和模拟问答来衔接。你可以在自己的GitHub上写一个《黑马点评面试复盘》的文档,把每个模块画成图、把每段核心代码抄一遍、把每个可能的问题和答案写下来。这个整理的过程,本身就会逼着你去思考很多之前忽略的细节。
5. 项目还能怎么扩展:给你三个简历加分方向
黑马点评做得再好,毕竟是个教学项目,很多同学把课上的功能做完就停了。但面试官见的项目太多了,纯原版的黑马点评很难让他们眼前一亮。我个人建议,在基础版本之上至少加一个扩展点,让项目带上你的个人思考。下面分享三个投入产出比比较高的扩展方向。
第一个方向:给Feed流做推拉结合模式。原版是清一色推模式,作者发笔记后给所有粉丝推送。但你有10万粉丝的时候,往10万个收件箱里各写一份数据就太浪费了。这时可以改成:普通用户用推模式,大V用户用拉模式,普通用户刷动态时先查自己的收件箱(推),再查关注大V的最近笔记(拉),然后合并排序。这个扩展能引出“粉丝量分级”“读写分离权衡”“归并排序合并数据”等进阶话题,非常能体现系统设计能力。
第二个方向:给秒杀模块做消息队列削峰,把Redis Stream替换成一个真正的事前削峰方案。秒杀的核心问题是瞬时流量过高,直接打到Redis和MySQL。你可以在秒杀开始前先把参与资格预扣在Redis里,用户点秒杀时拿着一个令牌去执行Lua脚本扣减库存,然后再把订单发送到RabbitMQ,由消费者异步写库。这个扩展能引出“令牌桶限流”“消息不丢失”“消息幂等性”等话题,都是面试官喜欢深挖的。
第三个方向:给查询接口做多级缓存。原版是Redis作为缓存,你可以再加上本地缓存Caffeine,形成Caffeine -> Redis -> MySQL三级缓存结构。同时要说清楚缓存一致性怎么保证——Caffeine会通过发送删除事件让其他节点的本地缓存失效,Redis负责最终一致性。这个扩展能引出“JVM本地缓存与分布式缓存的对比”“缓存一致性方案”“缓存雪崩的兜底策略”等话题,非常契合目前大厂高并发场景。
我个人建议,三个方向里挑一个做深做透就行了,千万不要三个都写进简历,否则面试官会觉得你只是堆砌名词。做一个、讲清楚一个,比贪多嚼不烂强得多。
最后再说一个我最近整理面经时的体会:黑马点评这个项目本身不是目的,它是你通往“Redis实战经验”的一根拐杖。面试官真正想看到的,是你能否透过代码看到背后的设计思想和业务取舍。你把这个项目的代码删掉、把Redis和MySQL换成另一个中间件,你还能不能讲清楚为什么这么做?如果能,那黑马点评对你来说就真的过关了。准备面试的时候,不妨每学完一个模块就合上电脑问自己一句:“如果不看任何资料,我能把这个模块的来龙去脉讲给别人听吗?”多这样练习几次,面试场上你会比大多数候选人淡定得多。