这是一个非常好的切入点。要理解为什么有的操作是“精准删除”,有的操作是“全部删除”,我们需要先回顾一下C端(用户端)是如何查询套餐缓存的。
1. 缓存的存储结构(核心前提)
在C端(用户浏览页面),套餐是**按照“分类”**来进行缓存的。
-
Key的格式:
setmealCache::分类ID -
举例:
- 分类ID=100(超值套餐):Redis里存的是
setmealCache::100->[套餐A, 套餐B...] - 分类ID=200(儿童套餐):Redis里存的是
setmealCache::200->[套餐C, 套餐D...]
- 分类ID=100(超值套餐):Redis里存的是
理解了这个前提,我们再来看那几个接口的处理逻辑:
2. 为什么 save (新增) 是精准删除?
Java
@CacheEvict(cacheNames = "setmealCache", key = "#setmealDTO.categoryId")
-
场景:你要在“超值套餐(ID=100)”这个分类下,新增一个“牛肉汉堡套餐”。
-
逻辑:
- 前端传过来的
setmealDTO里明确包含了categoryId = 100。 - 后端非常清楚:只有“超值套餐”这个分类的数据变了,其他“儿童套餐”、“饮料”等分类的数据没受影响。
- 决策:所以我只需要精准删掉
setmealCache::100这一条缓存即可。
- 前端传过来的
-
优点:影响范围小,效率高。
3. 为什么 delete, update, startOrStop 是全量删除?
Java
@CacheEvict(cacheNames = "setmealCache", allEntries = true)
这里使用 allEntries = true(删除 setmealCache 下的所有 key),主要是出于实现成本和数据一致性的考量。
原因一:入参信息不足(针对 delete 和 startOrStop)
-
场景:你要删除 ID=5 的套餐,或者停售 ID=5 的套餐。
-
参数:前端只传了一个
ids=5或者id=5。 -
问题:代码不知道这个 ID=5 的套餐属于哪个分类!
- 如果不查数据库,我根本不知道该删
setmealCache::100还是setmealCache::200。 - 如果为了删缓存,先去查一次数据库找分类ID,又多了一次数据库IO,得不偿失。
- 如果不查数据库,我根本不知道该删
-
决策:干脆把所有套餐缓存都清空,反正下次用户查的时候会重新加载,这样代码最简单,也不会出错。
原因二:存在“修改分类”的复杂情况(针对 update)
-
场景:你要修改一个套餐。
-
复杂逻辑:
- 假设“牛肉套餐”原本属于“超值套餐(ID=100)”。
- 管理员把它改到了“今日推荐(ID=300)”。
-
问题:
- 如果你只删除了新分类(ID=300)的缓存,那么旧分类(ID=100)的缓存里可能还残留着这个套餐数据(脏数据)。
- 要想精准清除,你得知道它修改前是哪个分类,这又需要查数据库比对。
-
决策:为了防止这种跨分类修改导致的数据不一致,直接清空所有缓存是最安全的做法。
4. 总结:这是一种“用性能换简单”的权衡
| 操作 | 难度 | 为什么这么做? |
|---|---|---|
| 新增 | 简单 | 我手里正好有 categoryId,顺手把它对应的缓存删了,精准又高效。 |
| 删除/起停 | 困难 | 我手里只有 id,不知道 categoryId。为了不查库,索性全删了。 |
| 修改 | 复杂 | 套餐可能从一个分类跳到另一个分类,为了防止旧分类留有脏数据,索性全删了。 |
这种做法在生产环境有问题吗?
在“外卖点餐”这种场景下,完全没问题。
- 读多写少:用户查几万次,管理员才改一次。
- 重建快:就算把缓存全删了,用户并发查进来,也只是多查几次数据库,很快就又缓存上了。
- 安全性:数据一致性大于微小的性能损耗。全删能100%保证用户不会看到错误的数据。