MST
星途 面试题库

面试题:缓存设计之高级难度:复杂场景下的缓存命中率优化

假设在一个高并发的电商系统中,商品详情页数据需要缓存以提高访问速度。但商品的价格和库存信息变化频繁,且不同地区的用户看到的商品信息可能存在差异。在这种复杂场景下,你会如何设计缓存结构和更新策略来最大化缓存命中率,同时保证数据的一致性和实时性?
31.3万 热度难度
后端开发缓存设计

知识考点

AI 面试

面试题答案

一键面试

缓存结构设计

  1. 按地区与商品ID进行缓存分区
    • 以地区和商品ID作为缓存的复合键。例如,使用{region}_{productId}作为缓存键,这样不同地区的用户可以快速定位到自己对应的商品缓存数据。
    • 优点是能快速满足不同地区用户对商品信息的请求,避免不同地区数据的相互干扰。
  2. 分层缓存
    • 一级缓存(如内存缓存,如Redis):存储热门商品的详情页数据,包括基本信息、描述等相对稳定的数据,以及价格和库存的缓存版本号。这一层缓存用于快速响应大部分请求,因为内存缓存的读写速度极快。
    • 二级缓存(如分布式文件系统或磁盘缓存):存储全量的商品详情页数据,包括价格和库存。当一级缓存未命中时,从二级缓存获取数据,并将数据加载到一级缓存中。二级缓存容量较大,可容纳更多商品数据。
  3. 缓存数据结构
    • 对于商品详情页数据,采用JSON格式进行存储,这样可以方便地存储各种类型的数据,如字符串、数字、数组等。例如:
    {
        "productName": "示例商品",
        "description": "这是一个示例商品的描述",
        "price": 100,
        "stock": 1000,
        "cacheVersion": 1
    }
    
    • 其中cacheVersion用于标识价格和库存的版本,每次价格或库存更新时,该版本号递增。

更新策略设计

  1. 写后更新

    • 当商品价格或库存发生变化时,首先更新数据库中的数据。
    • 然后,根据变化的商品ID和地区,更新对应的缓存数据。更新缓存时,同时递增cacheVersion。例如,使用Redis的SET命令更新缓存数据:
    SET {region}_{productId} '{"productName": "示例商品", "description": "这是一个示例商品的描述", "price": 120, "stock": 990, "cacheVersion": 2}'
    
    • 优点是操作相对简单,能保证数据库和缓存最终一致性。缺点是在更新数据库和更新缓存之间存在短暂的数据不一致。
  2. 读写锁机制

    • 在更新商品价格或库存时,获取写锁。只有获取到写锁的请求才能进行更新操作。在更新数据库和缓存完成后,释放写锁。
    • 读请求获取读锁,允许多个读请求同时获取读锁进行数据读取。当有写锁存在时,读请求等待写锁释放。
    • 这种机制可以保证在更新数据时,不会有其他读请求读取到旧数据,从而保证数据的一致性。例如,在Java中可以使用ReentrantReadWriteLock实现读写锁。
  3. 缓存失效策略

    • 设置短缓存过期时间:对于价格和库存数据,设置较短的过期时间,如1 - 5分钟。这样即使缓存更新出现问题,过期后也能从数据库重新加载最新数据。
    • 基于版本号的失效:当客户端读取缓存数据时,检查cacheVersion。如果本地缓存的cacheVersion与服务器缓存中的版本号不一致,说明数据已更新,需要重新获取数据。
  4. 异步更新缓存

    • 使用消息队列(如Kafka)。当商品价格或库存变化时,将更新消息发送到消息队列。
    • 有专门的消费者从消息队列中读取更新消息,并异步更新缓存。这样可以避免同步更新缓存带来的性能问题,同时保证缓存的最终一致性。