# 中间件面试题

# 一、Redis缓存数据库

# 1. Redis是什么?有什么特点和应用场景?

答案话术: Redis是一个高性能的键值对内存数据库,支持多种数据类型。

主要特点有三个:第一是基于内存操作,读写速度非常快,QPS可达10万+;第二是支持持久化,数据可以保存到磁盘;第三是支持多种数据结构,包括String、List、Set、Hash、ZSet等。

应用场景主要有四个:第一是缓存,减轻数据库压力;第二是分布式锁,解决并发问题;第三是计数器,比如点赞数、浏览量;第四是排行榜,用ZSet实现。

我在电商项目中,主要用Redis做商品详情缓存、用户Session缓存、秒杀库存预扣减等场景。


# 2. Redis的五种基本数据类型及使用场景?

答案话术: Redis有五种基本数据类型:

第一是String字符串,最常用,可以存储数字、字符串、对象JSON。应用场景是缓存、计数器、分布式锁。

第二是Hash哈希,存储对象的多个字段。应用场景是存储用户信息、商品信息。

第三是List列表,有序可重复。应用场景是消息队列、最新列表。

第四是Set集合,无序不重复。应用场景是去重、共同好友、点赞用户列表。

第五是ZSet有序集合,带分数排序。应用场景是排行榜、延迟队列。

我在电商项目中,用String缓存商品详情、Hash存储用户信息、ZSet实现销量排行榜。


# 3. 什么是缓存穿透?如何解决?

答案话术: 缓存穿透是指查询一个数据库不存在的数据,缓存没有,每次都会查数据库,导致数据库压力很大。

比如用户恶意查询id=-1的商品,数据库没有,Redis也没有,每次都穿透到数据库。

解决方案有两个:第一是缓存空值,查询数据库返回空也缓存起来,设置较短过期时间如5分钟。第二是使用布隆过滤器,在缓存前加一层,快速判断数据是否存在,不存在直接返回。

我在电商项目中,采用缓存空值的方式,有效防止了缓存穿透攻击。


# 4. 什么是缓存击穿?如何解决?

答案话术: 缓存击穿是指一个热点key突然过期,瞬间大量请求穿透到数据库。

比如爆款商品的缓存过期,瞬间几千个请求同时查数据库,可能把数据库打挂。

解决方案有三个:第一是设置热点数据永不过期;第二是使用互斥锁,第一个请求去查数据库时加锁,其他请求等待;第三是提前异步刷新,在key快过期时后台自动刷新。

我在电商项目中,对爆款商品采用永不过期策略,配合定时任务定期刷新,避免缓存击穿。


# 5. 什么是缓存雪崩?如何解决?

答案话术: 缓存雪崩是指大量key同时过期,导致大量请求瞬间打到数据库。

比如设置了一批商品缓存过期时间都是1小时,1小时后同时过期,数据库压力骤增。

解决方案有三个:第一是设置随机过期时间,在基础时间上加随机值,比如1小时±10分钟;第二是使用Redis集群,分散压力;第三是限流降级,请求过多时直接返回友好提示。

我在电商项目中,给所有缓存设置随机过期时间,有效避免了缓存雪崩问题。


# 6. Redis的持久化机制有哪些?

答案话术: Redis有两种持久化机制:RDB和AOF。

RDB是快照持久化,定期把内存数据生成快照保存到磁盘。优点是恢复速度快,缺点是可能丢失最后一次快照之后的数据。适合对数据完整性要求不高的场景。

AOF是日志持久化,记录每一条写命令。优点是数据完整性好,最多丢失1秒数据,缺点是文件较大恢复慢。适合对数据要求高的场景。

一般推荐两种都开启,RDB做定期备份,AOF保证数据完整性。我在电商项目中,开启了AOF每秒同步策略,平衡了性能和数据安全。


# 7. Redis如何实现分布式锁?

答案话术: Redis实现分布式锁主要用SETNX命令,核心是SET key value NX EX seconds。

NX表示key不存在才设置成功,相当于抢锁;EX设置过期时间防止死锁。

完整流程是:第一步用SET命令加锁,设置唯一标识和过期时间;第二步执行业务逻辑;第三步删除key释放锁,删除前要校验是不是自己的锁。

注意两个问题:第一是要设置过期时间防止死锁;第二是释放锁时要校验是不是自己的锁,可以用Lua脚本保证原子性。

我在电商项目秒杀场景中,用Redis分布式锁保证库存扣减的准确性,避免超卖。


# 8. Redis的过期策略有哪些?

答案话术: Redis过期策略有三种:定期删除+惰性删除+内存淘汰。

定期删除是Redis每100ms随机抽取一些key检查是否过期,过期就删除。

惰性删除是查询key时检查是否过期,过期就删除。

内存淘汰是内存不足时根据策略删除key,常用的策略有:LRU删除最近最少使用的key,LFU删除使用频率最低的key,TTL删除快过期的key。

我在电商项目中,设置了allkeys-lru淘汰策略,内存不足时自动淘汰最少使用的缓存。


# 9. Redis如何保证高可用?

答案话术: Redis高可用主要通过主从复制+哨兵模式+集群实现。

主从复制是一主多从,主负责写,从负责读,实现读写分离。从节点自动同步主节点数据。

哨兵模式是监控主从节点,主节点宕机时自动故障转移,选举新的主节点。

Redis Cluster集群模式是多个主节点,数据分片存储,每个主节点负责一部分数据,支持水平扩展。

我在电商项目中,使用了1主2从+哨兵的架构,主节点挂了能自动切换,保证了高可用。


# 10. Redis的内存淘汰策略有哪些?

答案话术: Redis有8种内存淘汰策略,常用的有4种:

第一是noeviction默认策略,内存不足时直接报错不淘汰。

第二是allkeys-lru,从所有key中淘汰最近最少使用的。

第三是volatile-lru,从设置了过期时间的key中淘汰最近最少使用的。

第四是allkeys-random,从所有key中随机淘汰。

推荐使用allkeys-lru策略,符合二八定律,热点数据会被保留。

我在电商项目中,配置了maxmemory 2GB和allkeys-lru策略,内存不足时自动淘汰冷数据。


# 11. Redis单线程为什么这么快?

答案话术: Redis单线程快主要有四个原因:

第一是基于内存操作,内存读写速度远超磁盘,不涉及磁盘IO。

第二是单线程避免了线程切换和锁竞争的开销。

第三是采用IO多路复用技术,一个线程可以处理多个网络连接,高效处理并发请求。

第四是数据结构简单高效,Redis的数据结构都经过优化。

虽然是单线程,但Redis的QPS可以达到10万+。注意Redis 6.0后引入了多线程,但只用于网络IO,核心数据操作还是单线程。


# 12. 如何保证缓存和数据库的一致性?

答案话术: 保证缓存和数据库一致性主要有三种策略:

第一是先更新数据库再删除缓存,最常用也最推荐。更新成功后删除缓存,下次查询会重新加载最新数据。

第二是先删除缓存再更新数据库,但可能有并发问题。

第三是延迟双删,先删缓存、更新数据库、延迟1秒再删缓存,解决并发问题。

对于强一致性要求的场景,可以使用分布式事务或订阅数据库binlog异步更新缓存。

我在电商项目中,采用先更新数据库再删除缓存的策略,对于商品价格等重要数据,可以容忍短暂的不一致。


# 13. Redis的事务如何使用?

答案话术: Redis事务通过MULTI、EXEC、WATCH命令实现。

使用流程是:第一步MULTI开启事务,后续命令会进入队列;第二步输入多条命令;第三步EXEC执行事务,一次性执行队列中的所有命令。

WATCH命令用于乐观锁,监控key在事务执行前是否被修改,被修改则事务失败。

需要注意Redis事务不支持回滚,某条命令失败不影响其他命令执行。

我在电商项目中,用Redis事务配合WATCH实现了库存扣减的原子操作,避免超卖。


# 14. Redis如何实现消息队列?

答案话术: Redis实现消息队列主要有三种方式:

第一是用List,LPUSH生产消息,BRPOP消费消息,阻塞等待。优点是简单,缺点是不支持消息确认。

第二是用Pub/Sub发布订阅,但消息不持久化,消费者离线会丢消息。

第三是用Stream,Redis 5.0新增的数据类型,支持消息持久化、消费组、消息确认,功能最完善。

我在电商项目中,对于简单的异步任务用List实现轻量级消息队列,对于重要业务还是用专业的MQ如Kafka。


# 15. Redis的慢查询如何排查?

答案话术: Redis慢查询排查主要通过slowlog命令。

第一步配置慢查询阈值,slowlog-log-slower-than设置微秒,比如10000表示超过10ms就记录。

第二步用slowlog get命令查看慢查询记录,可以看到执行时间、命令内容。

第三步分析慢查询原因,常见的是操作大key、使用了keys *等O(n)复杂度命令、集合操作数据量太大。

第四步优化,拆分大key、用scan替代keys、分批处理大量数据。

我在电商项目中,发现有个获取所有商品的命令很慢,查慢查询日志后发现用了HGETALL操作了几万条数据,改成分页查询后解决。


# 16. 什么是大key问题?如何解决?

答案话术: 大key是指value占用内存很大的key,比如包含几万个元素的Set、几MB的String。

大key的危害有三个:第一是占用大量内存;第二是操作慢,删除大key会阻塞Redis;第三是可能导致内存不均衡。

解决方案有三个:第一是拆分大key,比如把大Set拆成多个小Set;第二是压缩数据,对String类型的大value进行压缩;第三是定期清理,删除不需要的数据。

我在电商项目中,用户浏览历史存在一个List里越来越大,优化后改成只保留最近100条,定期清理旧数据。


# 17. Redis如何实现限流?

答案话术: Redis实现限流主要有两种方式:

第一是计数器算法,用String记录次数,配合EXPIRE设置过期时间。比如每分钟限制100次,key是user:123:minute,INCR计数,超过100就拒绝。

第二是滑动窗口算法,用ZSet实现,score是时间戳。统计最近1分钟的请求数,超过限制就拒绝。更精确但稍复杂。

我在电商项目中,对短信验证码发送接口做了限流,每个手机号每分钟只能发1次,用计数器算法实现。


# 18. Redis集群的数据分片原理是什么?

答案话术: Redis Cluster采用哈希槽分片机制。

总共有16384个哈希槽,每个key通过CRC16算法计算哈希值,然后对16384取模得到槽位。不同的槽分配给不同的主节点。

比如3个主节点,节点A负责0-5460槽,节点B负责5461-10922槽,节点C负责10923-16383槽。

客户端根据key计算槽位,直接访问对应节点,不需要代理。如果访问错了节点,会返回MOVED重定向。

这种方式实现了数据分片和水平扩展,增加节点时只需要迁移部分槽。


# 19. Redis性能优化有哪些方法?

答案话术: Redis性能优化主要有六个方向:

第一是避免大key,拆分大key减少单次操作时间。

第二是设置合理的过期时间,避免内存占用过多。

第三是使用pipeline批量操作,减少网络往返次数。

第四是避免使用复杂度高的命令,如keys *、HGETALL大hash等。

第五是使用连接池,避免频繁创建连接。

第六是合理设置maxmemory和淘汰策略,防止内存溢出。

我在电商项目中,通过这些优化手段,Redis QPS从5万提升到10万。


# 20. Redis在你们电商项目中具体怎么用的?

答案话术: 在我们电商项目中,Redis主要用在四个场景:

第一是商品详情缓存,把商品基本信息、价格、库存等缓存到Redis,过期时间1小时+随机10分钟,缓存命中率达到95%,大大减轻了数据库压力。

第二是用户Session缓存,用户登录后把Session信息存Redis,设置30分钟过期,支持分布式Session共享。

第三是秒杀库存预扣减,秒杀商品的库存先存到Redis,用Lua脚本原子扣减,成功后异步更新数据库,支撑了5000并发秒杀。

第四是分布式锁,在库存扣减等关键环节用Redis分布式锁保证并发安全,避免超卖问题。

通过合理使用Redis,系统整体性能提升了60%,响应时间从500ms降到100ms。

# 一、Mq消息队列

# 1. 什么是消息队列?有什么作用?

答案话术: 消息队列是一种异步通信机制,生产者发送消息到队列,消费者从队列获取消息处理。

主要作用有三个:第一是异步处理,提升响应速度,比如用户下单后不用等待发短信就能返回;第二是削峰填谷,秒杀时把大量请求放到队列慢慢处理,避免系统崩溃;第三是解耦,订单系统和库存系统通过MQ通信,互不依赖。

我在电商项目中,主要用MQ处理订单创建后的异步任务,包括发送短信通知、扣减库存、增加积分等,大大提升了下单接口的响应速度。


# 2. 常见的消息队列有哪些?有什么区别?

答案话术: 常见的消息队列主要有Kafka、RabbitMQ、RocketMQ三种。

Kafka吞吐量最高,百万级TPS,适合大数据和日志采集场景,但功能相对简单。

RabbitMQ功能最丰富,支持多种消息模式,但吞吐量较低,适合金融等对可靠性要求高的场景。

RocketMQ是阿里开源的,吞吐量十万级,功能丰富,支持事务消息和延迟消息,适合电商场景。

我在电商项目中主要用Kafka,因为订单量大需要高吞吐量,用来处理订单消息、日志采集等场景。


# 3. 消息队列如何保证消息不丢失?

答案话术: 保证消息不丢失需要从三个环节考虑:

第一是生产者端,发送消息后要确认是否成功,Kafka使用acks=all确保消息写入所有副本。发送失败要重试。

第二是MQ服务端,消息要持久化到磁盘,Kafka通过副本机制保证,至少2个副本。

第三是消费者端,消费成功后才提交offset确认,失败要重试。不能先提交offset再处理,否则处理失败消息就丢了。

我在电商项目中,订单消息采用这三层保障,确保消息不丢失,关键业务数据的可靠性达到99.99%。


# 4. 消息队列如何保证消息不重复消费?

答案话术: 消息重复消费主要通过幂等性设计解决。

第一种方案是利用业务唯一ID,比如订单号,消费时先查询该订单是否已处理,已处理就跳过。可以用Redis记录已处理的消息ID。

第二种方案是数据库唯一索引,把消息ID设为唯一索引,重复消费会插入失败。

第三种方案是版本号机制,更新数据时带版本号,重复更新会失败。

我在电商项目中,订单消息用订单号做唯一标识,消费前先查Redis是否已处理,保证了幂等性,避免了重复扣库存、重复发短信等问题。


# 5. 什么是消息积压?如何解决?

答案话术: 消息积压是指生产速度大于消费速度,队列中的消息越堆越多。

解决方案有四个:第一是增加消费者数量,提高并行消费能力;第二是优化消费逻辑,去掉不必要的同步等待,提升单个消费者的处理速度;第三是扩容队列分区,Kafka增加partition提高并行度;第四是批量消费,一次拉取多条消息批量处理。

我在电商项目中,遇到过订单消息堆积10万条,排查后发现消费逻辑中有同步调用外部接口很慢。优化方案:消费者从5个增加到10个,外部接口改异步调用,堆积很快消化,吞吐量提升3倍。


# 6. 消息队列如何保证消息的顺序性?

答案话术: 保证消息顺序主要有两个关键点:

第一是发送时保证同一业务的消息发到同一个分区,Kafka可以按key hash路由到固定分区。

第二是消费时单线程消费或者按业务ID分组消费,同一个分区只能有一个消费者,保证顺序处理。

但要注意,严格顺序会牺牲性能和吞吐量。

我在电商项目中,订单状态变更消息需要保证顺序,按订单号hash到固定分区,同一订单的消息在同一个分区串行处理,保证了创建-支付-发货的顺序。


# 7. 什么是死信队列?如何使用?

答案话术: 死信队列是存放无法正常消费的消息的队列。

消息变成死信主要有三种情况:第一是消费失败达到最大重试次数;第二是消息过期TTL超时;第三是队列满了。

死信队列的作用是隔离有问题的消息,避免阻塞正常消息处理,方便后续人工介入处理。

我在电商项目中,配置了死信队列处理异常订单消息。消费失败重试3次后进入死信队列,后台有定时任务定期扫描死信队列,人工分析处理异常原因。


# 8. 什么是延迟消息?如何实现?

答案话术: 延迟消息是指消息发送后不立即消费,而是延迟一段时间后才能被消费。

实现方式有两种:第一是RocketMQ原生支持延迟消息,可以设置延迟级别,比如5秒、1分钟、5分钟等固定级别。

第二是用Redis+定时任务实现,消息先存Redis的ZSet,score是执行时间戳,定时任务扫描到期的消息再发送到MQ。

我在电商项目中,订单超时未支付自动取消就用延迟消息实现,下单时发送30分钟的延迟消息,到期后检查订单状态,未支付就自动取消并释放库存。


# 9. 消息队列如何实现高可用?

答案话术: 消息队列高可用主要通过集群和副本机制实现。

Kafka采用多副本机制,每个分区有多个副本,分布在不同的broker上。Leader负责读写,Follower同步数据,Leader宕机时自动选举新的Leader。

一般配置3个副本,允许2个副本故障,保证数据不丢失和服务可用。

集群模式下,客户端可以连接任意broker,自动发现集群拓扑。

我在电商项目中,Kafka集群有3个broker,每个topic设置3副本,任意一台机器宕机不影响服务,实现了高可用。


# 10. Kafka的分区和副本是什么?

答案话术: 分区Partition是Kafka的存储单元,一个topic可以分成多个分区,分布在不同的broker上。

分区的作用是提高并行度和吞吐量,每个分区可以独立读写,消费者数量不超过分区数。

副本Replica是分区的冗余备份,包括Leader副本和Follower副本。Leader负责读写,Follower只负责同步数据做备份。

我在电商项目中,订单topic设置了6个分区、3个副本,6个消费者并行消费,吞吐量达到10万条/秒,3副本保证了数据可靠性。


# 11. Kafka如何保证高吞吐量?

答案话术: Kafka高吞吐量主要有五个技术手段:

第一是顺序写磁盘,避免随机写,顺序写速度接近内存。

第二是零拷贝技术,数据从磁盘到网络不经过用户空间,减少拷贝次数。

第三是批量发送,生产者批量打包消息一次性发送,减少网络开销。

第四是消息压缩,支持GZIP、Snappy等压缩算法,减少网络传输和存储。

第五是分区并行,多个分区可以并行读写,提高并发度。

我在电商项目中,通过合理设置分区数和批量大小,Kafka单集群吞吐量达到百万级。


# 12. 消费者组是什么?有什么作用?

答案话术: 消费者组Consumer Group是Kafka的消费者管理机制,一个消费者组包含多个消费者。

消费者组有两个特点:第一是组内负载均衡,同一个分区只能被组内一个消费者消费,多个消费者分摊分区;第二是组间隔离,不同消费者组可以独立消费同一个topic的全部数据。

比如订单topic,订单服务消费者组处理订单逻辑,库存服务消费者组处理库存逻辑,两个组互不影响。

我在电商项目中,订单消息有3个消费者组:订单处理组、库存扣减组、积分增加组,实现了一条消息多个业务独立消费。


# 13. offset是什么?如何管理?

答案话术: offset是消费者在分区中的消费位置,类似于数组下标,记录消费到了第几条消息。

Kafka会把offset存储在内部topic __consumer_offsets中,消费者定期提交offset。

offset管理有两种模式:自动提交,消费者定期自动提交,简单但可能丢消息或重复消费;手动提交,消费成功后才提交,更可靠但需要自己管理。

我在电商项目中,使用手动提交模式,确保消息处理成功才提交offset,避免了消息丢失,偶尔重复消费通过幂等性设计解决。


# 14. 如何监控消息队列的健康状态?

答案话术: 监控消息队列主要关注四个指标:

第一是消息堆积量Lag,消费者offset和最新消息offset的差值,堆积过多说明消费能力不足。

第二是消费延迟,消息从生产到消费的时间间隔,正常应该在秒级。

第三是消费者状态,是否有消费者宕机或消费异常。

第四是集群资源,broker的CPU、内存、磁盘、网络使用率。

我在电商项目中,用Kafka Manager和Prometheus+Grafana监控,设置告警:Lag超过10万条告警,消费延迟超过1分钟告警,消费者离线立即告警。


# 15. 消息队列的推模式和拉模式有什么区别?

答案话术: 推模式Push是MQ主动推送消息给消费者,拉模式Pull是消费者主动拉取消息。

推模式优点是实时性好,缺点是MQ不知道消费者处理能力,可能压垮消费者。

拉模式优点是消费者可以控制速度,按自己能力拉取,缺点是可能有延迟。

Kafka采用拉模式,消费者主动poll拉取消息,可以批量拉取提高效率,消费者自己控制消费速度。

我在电商项目中,Kafka的拉模式配合批量消费,一次拉取100条消息批量处理,吞吐量很高。


# 16. 什么是事务消息?如何使用?

答案话术: 事务消息是保证本地事务和消息发送的一致性,要么都成功,要么都失败。

比如下单扣库存,扣库存成功要发消息通知下游,如果扣了库存但消息发送失败就不一致了。

RocketMQ的事务消息流程是:第一步发送半消息,MQ收到但不投递;第二步执行本地事务;第三步提交或回滚消息,成功就提交让消费者可见,失败就回滚删除消息。

我在电商项目中了解事务消息的原理,实际项目用的是最终一致性方案,通过消息重试和补偿机制保证一致性。


# 17. 如何设计一个高可用的消息队列架构?

答案话术: 设计高可用消息队列架构主要考虑四个方面:

第一是集群部署,至少3个broker节点,避免单点故障。

第二是副本机制,每个分区至少2个副本,保证数据不丢失。

第三是生产消费保障,生产者重试+消息持久化+消费者手动提交,保证消息可靠性。

第四是监控告警,实时监控消息堆积、消费延迟、集群资源,异常及时告警。

我在电商项目中,Kafka集群3个broker,每个topic 3副本,生产者配置重试机制,消费者手动提交offset,配合完善的监控告警体系,消息可靠性达到99.99%。


# 18. 消息队列的性能优化有哪些方法?

答案话术: 消息队列性能优化主要有六个方向:

第一是批量发送,生产者攒批发送减少网络请求次数。

第二是批量消费,消费者一次拉取多条消息批量处理。

第三是异步发送,生产者异步发送不阻塞业务线程。

第四是消息压缩,减少网络传输和存储开销。

第五是增加分区数,提高并行度和吞吐量。

第六是优化消费逻辑,去掉不必要的同步等待,能并行就并行。

我在电商项目中,通过这些优化手段,Kafka吞吐量从5万条/秒提升到10万条/秒。


# 19. 如何处理消费失败的消息?

答案话术: 处理消费失败主要有四种策略:

第一是立即重试,消费失败立即重新消费,适合偶发性错误。

第二是延迟重试,失败后延迟一段时间再重试,避免频繁失败。

第三是进入死信队列,重试多次还失败就放到死信队列,人工介入处理。

第四是记录日志告警,关键消息失败要记录日志并告警。

我在电商项目中,订单消息消费失败会重试3次,每次间隔递增:1秒、5秒、30秒。3次都失败进入死信队列,同时发送告警通知,运营人工处理。


# 20. MQ在你们电商项目中具体怎么用的?

答案话术: 在我们电商项目中,MQ主要用在四个场景:

第一是订单异步处理,用户下单后发送订单消息,下游消费者异步处理发短信、扣积分、发优惠券等操作,下单接口响应时间从2秒降到200ms。

第二是秒杀削峰,秒杀时大量请求先进入MQ排队,消费者按能力慢慢消费,避免瞬时流量把数据库打挂,支撑了1万人同时抢购。

第三是库存扣减解耦,订单系统和库存系统通过MQ解耦,订单创建后发消息通知库存系统扣减,两个系统独立部署互不影响。

第四是数据同步,订单数据变更后发消息同步到数据仓库、搜索引擎等下游系统,实现数据最终一致性。

通过合理使用MQ,系统性能提升了60%,可用性提升到99.9%,实现了系统解耦和削峰填谷。