51mee - AI智能招聘平台Logo
模拟面试题目大全招聘中心会员专区

请设计一个高可用的服务,该服务需要频繁读取用户信息(如用户等级、积分),并支持高并发写入(如用户登录、积分变更),说明缓存策略、数据库读写分离、消息队列的作用,以及如何处理缓存雪崩、缓存穿透、缓存击穿问题。

Tencent软件开发-后台开发方向难度:困难

答案

1) 【一句话结论】针对高并发读写(频繁读用户信息+高并发写登录/积分变更)的高可用服务,核心采用“多级缓存(本地+分布式)+数据库读写分离(主从)+消息队列异步写”架构,通过缓存雪崩(分布式锁+随机TTL)、缓存穿透(布隆过滤器+空值缓存)、缓存击穿(互斥锁+热点预热)的解决方案,实现服务高可用。

2) 【原理/概念讲解】(老师口吻)

  • 缓存策略:频繁读用户信息(等级、积分)需低延迟,采用多级缓存:
    • 本地缓存(如Java的ConcurrentHashMap):缓存热数据(如登录状态),响应快,无需网络通信;
    • 分布式缓存(如Redis):缓存共享数据(如用户等级、积分),支持高并发读,数据一致性通过“本地→Redis→数据库”双写保证。
  • 数据库读写分离:高并发写入(登录、积分变更)时,主库压力过大,采用主从复制:主库负责写,从库负责读,通过读写分离代理(如MyBatis-Plus的Ribbon+从库)分散读压力。从库延迟可能导致数据不一致,敏感数据(如积分)需双写,非敏感数据可接受延迟(或设置缓存TTL)。
  • 消息队列:高并发写入时,直接写数据库可能导致阻塞,采用消息队列(如Kafka)异步处理:写入流程为“更新数据库→发送消息→消费者异步更新缓存”,解耦写操作,提高吞吐量。选择依据:Kafka适合高吞吐、持久化(适合大规模异步处理,如积分变更);RabbitMQ适合点对点、可靠性(适合小规模、需精确投递的场景)。
  • 缓存问题处理:
    • 缓存雪崩:缓存全过期时,大量请求直接到数据库。解决方案:分布式锁(如Redis的SETNX)控制并发更新缓存,随机设置TTL(如5分钟+随机偏移,避免集中过期)。
    • 缓存穿透:查询不存在的数据(如用户ID为0),导致所有请求都到数据库。解决方案:布隆过滤器(存储不存在的key)拦截请求,对于存在的key再查缓存;对于不存在的key,缓存空值(如“null”)并设置短TTL(如1分钟)。
    • 缓存击穿:热点数据(如热门用户积分)过期时,大量请求同时访问。解决方案:互斥锁(如Redis锁)控制并发更新缓存,提前预热热点数据(长TTL,如1小时)。

3) 【对比与适用场景】

对比项本地缓存(ConcurrentHashMap)分布式缓存(如Redis)数据库读写分离(主从)消息队列(如Kafka)
定义应用进程内的内存缓存多节点共享的分布式缓存主库写+从库读的数据库架构异步消息传输中间件
特性响应快(无网络延迟),数据一致性依赖应用;内存大小受限于JVM支持高并发读,需网络通信;需双写保证一致性;支持数据持久化分散读压力,从库延迟可能导致数据不一致;主库压力仍大解耦写操作,支持高吞吐异步处理;需考虑消息丢失、重试机制
使用场景热数据(如登录状态、会话),读多写少,频繁访问共享数据(如用户等级、积分),读多写少,需跨服务共享高并发写入(如登录、积分变更)的读/写分离,降低主库压力高并发写入时的异步处理(如积分变更、订单创建),避免数据库阻塞
注意点本地缓存数据丢失(进程重启),需备份;内存大小限制(如JVM堆内存)分布式缓存故障(Redis宕机),需主从或哨兵;网络延迟影响性能从库延迟导致读数据不一致(如积分变更后,从库未同步,读旧值);敏感数据需双写消息丢失(如Kafka分区故障),需重试;消息积压(如消费者处理慢),需扩容

4) 【示例】(积分变更流程伪代码)

// 积分变更流程(异步写入+缓存更新)
public void addPoints(Long userId, int points) {
    // 1. 更新数据库(主库)
    userMapper.updatePoints(userId, points);
    
    // 2. 发送消息到消息队列(如Kafka)
    producer.send("user-points-topic", userId.toString(), points);
    
    // 3. 消费者处理(异步更新缓存)
    consumer.subscribe("user-points-topic");
    consumer.onMessage((topic, message, offset) -> {
        Long userId = Long.parseLong(message);
        int points = Integer.parseInt(message);
        // 更新本地缓存(热数据)
        localCache.put(userId, points);
        // 更新分布式缓存(Redis)
        redisTemplate.opsForValue().set("user:points:" + userId, points, 5, TimeUnit.MINUTES);
    });
}

5) 【面试口播版答案】
“面试官您好,针对高并发读写场景(频繁读用户信息+高并发写登录/积分变更),我的设计核心是构建‘多级缓存+数据库读写分离+消息队列异步处理’的架构,并针对缓存问题做针对性优化。具体来说:

  • 缓存策略:用本地缓存(ConcurrentHashMap)缓存热数据(如登录状态),响应快;用分布式缓存(Redis)缓存共享数据(如用户等级、积分),支持高并发读。更新时双写(本地→Redis→数据库),保证一致性。
  • 数据库读写分离:主库负责写(登录、积分变更),从库负责读(用户信息查询),通过读写分离代理分散读压力。从库延迟可能导致数据不一致,对于敏感数据(如积分)需双写,非敏感数据可接受延迟。
  • 消息队列:高并发写入时,先更新数据库,再发送消息到Kafka,消费者异步更新缓存,解耦写操作,提高吞吐量。
  • 缓存问题处理:缓存雪崩用分布式锁(Redis)控制并发更新,随机设置TTL(如5分钟+随机偏移);缓存穿透用布隆过滤器拦截不存在的key,缓存空值;缓存击穿用互斥锁控制热点数据更新,提前预热。
    这样设计后,服务既能满足高并发读(多级缓存+读写分离),又能处理高并发写(消息队列+双写),同时通过缓存策略和问题处理保证高可用。”

6) 【追问清单】

  • 问题1:消息队列的选择(如Kafka vs RabbitMQ)?
    回答要点:Kafka适合高吞吐、持久化(适合大规模异步处理,如积分变更);RabbitMQ适合点对点、可靠性(适合小规模、需精确投递的场景),根据业务场景选,比如Kafka更适合积分这类高并发异步写入。
  • 问题2:数据库读写分离的从库延迟如何处理?
    回答要点:从库延迟可能导致数据不一致,对于敏感数据(如积分)需双写(主库更新后,从库同步),对于非敏感读(如用户等级)可接受延迟,或设置缓存TTL(如5分钟)。
  • 问题3:缓存雪崩的分布式锁如何实现?
    回答要点:用Redis的SETNX命令实现分布式锁,控制单个缓存更新,避免全局锁阻塞,锁超时时间设为TTL+随机偏移。
  • 问题4:缓存击穿的热点数据预热怎么做?
    回答要点:提前将热点数据放入缓存(如用户登录时预热积分数据),设置长TTL(如1小时),减少过期时的并发压力。
  • 问题5:消息队列的可靠性(如消息丢失)如何保证?
    回答要点:消息队列设置重试机制(如Kafka的retries参数),或幂等性处理(如数据库更新时检查唯一键,避免重复处理)。

7) 【常见坑/雷区】

  • 缓存雪崩用全局锁:错误做法是用全局锁控制所有缓存更新,正确做法是用分布式锁(如Redis)控制单个缓存更新,避免阻塞。
  • 缓存穿透没布隆过滤器:错误做法是直接查数据库,正确做法是用布隆过滤器拦截不存在的key,减少数据库压力。
  • 数据库从库延迟导致不一致:错误做法是直接读从库,对于敏感数据(如积分)需双写,避免数据不一致。
  • 缓存击穿没互斥锁:错误做法是直接更新缓存,正确做法是用互斥锁(如Redis锁)控制并发更新,避免热点数据过期时大量请求。
  • 缓存一致性没考虑写后读:错误做法是只缓存读,没考虑更新数据库后需从缓存删除或更新,导致脏读。
51mee.com致力于为招聘者提供最新、最全的招聘信息。AI智能解析岗位要求,聚合全网优质机会。
产品招聘中心面经会员专区简历解析Resume API
联系我们南京浅度求索科技有限公司admin@51mee.com
联系客服
51mee客服微信二维码 - 扫码添加客服获取帮助
© 2025 南京浅度求索科技有限公司. All rights reserved.
公安备案图标苏公网安备32010602012192号苏ICP备2025178433号-1