面试题答案
一键面试设计思路
- 多种缓存淘汰算法结合:
- LRU(最近最少使用):适用于大多数普通商品数据。因为商品访问频率会随时间波动,LRU能有效淘汰长时间未被访问的商品缓存,为新的热门商品数据腾出空间。例如,一些季节性商品在季节过后访问量急剧下降,LRU可以将其缓存淘汰。
- LFU(最不经常使用):对于库存数据,因其一致性要求高且实时更新,使用LFU算法。库存数据的访问通常相对稳定,LFU能基于访问频率淘汰访问次数最少的库存缓存,保证高访问频率的库存数据始终在缓存中,减少对数据库的直接访问,同时也有利于保证库存数据的实时性。
- 缓存分层设计:
- 一级缓存:采用本地缓存(如Guava Cache),使用LRU算法。本地缓存的优点是访问速度极快,能快速响应高频的商品数据请求。对于商品详情页数据等访问频率高且一致性要求相对不那么严格的数据,优先从一级缓存获取。
- 二级缓存:使用分布式缓存(如Redis),商品数据用LRU,库存数据用LFU。分布式缓存具有高可用性和可扩展性,能应对大规模的并发请求。二级缓存作为一级缓存的补充,当一级缓存未命中时,从二级缓存获取数据。同时,库存数据在二级缓存中使用LFU,确保库存数据的实时性和一致性。
- 缓存更新策略:
- 写后更新:对于商品数据,采用写后更新策略。即当商品数据发生变化时,先更新数据库,再更新缓存。这样可以保证数据的最终一致性,且操作相对简单。
- 读写锁:对于库存数据,使用读写锁机制。读操作可以并发进行,写操作时加锁,保证库存数据更新的原子性,避免并发写操作导致的数据不一致问题。
关键技术点
- 缓存数据结构:
- 商品数据:在Redis中可以使用Hash结构存储商品信息,以商品ID为Key,商品详细信息(如名称、价格、描述等)为Hash的Field和Value。这样可以方便地进行单个商品数据的查询和更新。
- 库存数据:同样在Redis中,使用String结构,以库存ID为Key,库存数量为Value。结合LFU算法实现库存数据的缓存管理。
- 缓存与数据库交互:
- 数据库连接池:使用数据库连接池(如HikariCP)管理与数据库的连接,提高数据库访问效率,减少连接创建和销毁的开销。
- 异步更新:对于商品数据的缓存更新,可以采用异步方式,如使用消息队列(如Kafka)。当商品数据更新时,先发送消息到队列,由消费者异步更新缓存,这样可以减少对业务流程的阻塞。
潜在风险与应对措施
- 缓存穿透:
- 风险:恶意请求访问不存在的商品数据,每次都绕过缓存直接访问数据库,导致数据库压力过大。
- 应对措施:
- 布隆过滤器:在缓存之前使用布隆过滤器。将所有存在的商品ID预先加入布隆过滤器,当请求到来时,先通过布隆过滤器判断商品ID是否存在。如果不存在,则直接返回,不再访问数据库。
- 空值缓存:当数据库查询到商品不存在时,将该商品ID对应的空值也缓存起来,并设置较短的过期时间,防止后续重复查询数据库。
- 缓存雪崩:
- 风险:大量缓存同时过期,导致大量请求直接访问数据库,可能使数据库崩溃。
- 应对措施:
- 随机过期时间:为缓存设置随机的过期时间,避免大量缓存同时过期。例如,商品数据缓存的过期时间设置在一个时间区间内(如1 - 2小时)随机取值。
- 多级缓存:如前文提到的分层缓存设计,一级缓存可以在二级缓存失效时提供一定的缓冲,减少直接访问数据库的请求量。
- 缓存预热:在系统启动时,预先加载一部分热门商品数据到缓存中,避免系统刚启动时大量缓存未命中的情况。