MST

星途 面试题库

面试题:缓存设计之专家难度:复杂业务场景下的缓存失效策略优化

在一个具有高并发读写且业务逻辑复杂的电商系统中,既要保证商品信息的缓存一致性,又要兼顾系统性能,避免因缓存失效带来的各种问题,你将如何设计和优化缓存失效策略?请从数据结构、失效算法、更新机制等多方面深入分析。
41.6万 热度难度
后端开发缓存设计

知识考点

AI 面试

面试题答案

一键面试

数据结构

  1. 选择合适的缓存数据结构
    • 哈希表:对于商品信息,使用哈希表结构可以快速通过商品ID定位到对应的缓存数据。例如在Redis中,使用哈希(Hash)数据类型存储商品详情,将商品ID作为键,商品的各个属性(如名称、价格、库存等)作为哈希表的字段和值。这样在读取和写入时,时间复杂度都接近O(1),能满足高并发读写的性能需求。
    • 布隆过滤器:为了减少缓存穿透问题(查询不存在的数据时每次都穿透到数据库),可以使用布隆过滤器。它能以极小的误差率快速判断一个商品ID是否存在于缓存中。当布隆过滤器判断商品ID不存在时,直接返回,避免查询数据库。其占用空间小,查询效率高,适用于高并发场景。
  2. 分层缓存结构
    • 一级缓存:采用本地缓存(如Guava Cache),将访问频率极高的商品信息缓存在应用服务器本地。由于数据在本地内存,读取速度极快,可以快速响应部分读请求,减轻后端缓存和数据库的压力。但本地缓存容量有限,且不同应用服务器之间数据同步困难,所以适合存储相对固定、更新频率低的热点数据。
    • 二级缓存:使用分布式缓存(如Redis),存储大部分商品信息。它具有高可用、可扩展的特点,能满足电商系统大规模数据和高并发的需求。一级缓存未命中时,再查询二级缓存。

失效算法

  1. LRU(最近最少使用)算法
    • 原理:LRU算法的核心思想是如果数据最近被访问过,那么将来被访问的几率也更高。在缓存空间满时,移除最近最少使用的数据。例如在Redis中,可以通过配置maxmemory-policy allkeys - lru来启用LRU策略。这样在缓存达到最大内存限制时,Redis会根据LRU算法淘汰最近最少使用的键值对,为新的商品信息缓存腾出空间。
    • 优化:标准LRU算法实现简单,但存在一些缺点,如无法处理“偶尔使用”的热点数据。可以采用近似LRU算法,如Redis的惰性删除和定期删除策略结合。惰性删除在每次访问键时检查键是否过期,如果过期则删除;定期删除按一定时间间隔随机检查一些键,删除过期键。这样既避免了集中删除过期键带来的性能问题,又能有效清理过期数据。
  2. LFU(最不经常使用)算法
    • 原理:LFU算法根据数据的历史访问频率来淘汰数据,认为访问频率低的数据将来被访问的可能性也低。与LRU不同,它不仅仅关注最近的访问情况,还统计数据的总访问次数。在一些缓存框架中,可以通过维护一个访问频率计数器来实现LFU。例如,当商品信息被访问时,对应的计数器加1,在缓存空间不足时,淘汰访问频率最低的商品信息。
    • 适用场景:在电商系统中,如果某些商品的访问频率相对稳定,LFU算法可以更准确地淘汰不常用的数据,相比LRU能更好地利用缓存空间,适合用于缓存那些访问模式相对稳定的商品数据。

更新机制

  1. 写后更新
    • 策略:在商品信息更新时,先更新数据库,再更新缓存。例如,当商品价格发生变化时,首先将新价格写入数据库,然后立即更新对应的缓存数据。这种方式实现简单,能保证缓存最终一致性。
    • 问题及解决:但是在并发场景下可能出现缓存和数据库数据不一致的情况。比如,在更新数据库后、更新缓存前,有读请求到来,此时读取到的是旧的缓存数据。可以通过设置缓存过期时间来解决,即使缓存数据更新失败,在过期时间后也能从数据库重新加载最新数据。另外,可以使用消息队列(如Kafka),将更新操作发送到消息队列,由消息队列异步处理缓存更新,保证更新操作的顺序性,避免并发更新导致的数据不一致问题。
  2. 读写锁机制
    • 原理:在缓存读写操作时,使用读写锁。读操作可以并发进行,而写操作时需要获取写锁,此时其他读写操作都被阻塞。例如在Java中,可以使用ReentrantReadWriteLock。在电商系统中,当读取商品信息时,多个线程可以同时获取读锁进行读取;当更新商品信息时,线程需要获取写锁,只有获取到写锁才能进行更新操作,同时其他读写线程被阻塞,直到写操作完成释放写锁。
    • 优化:为了提高性能,可以对读写锁进行优化,如采用读写锁升级和降级策略。在某些情况下,持有读锁的线程可以尝试升级为写锁,避免重复获取锁的开销;持有写锁的线程在满足一定条件下可以降级为读锁,让其他读操作可以并发执行,提高系统的并发性能。
  3. 双缓存机制
    • 实现:维护两个缓存,如缓存A和缓存B。在更新商品信息时,先更新缓存B,然后将缓存B切换为当前使用的缓存,同时将原来的缓存A标记为待更新状态。这样在更新过程中,读请求仍然可以从当前使用的缓存(更新前的缓存A)读取数据,保证了读操作的连续性。更新完成后,下次更新时再将缓存A作为更新目标,如此循环。
    • 优点:这种机制能在更新缓存时不影响读操作,极大地提高了系统的可用性和性能。但它增加了系统的复杂性,需要额外管理两个缓存的切换和数据一致性。可以通过版本号机制来辅助管理,每次更新缓存时,版本号递增,读操作可以根据版本号判断是否需要从新的缓存中读取数据。