模块一:高并发点赞 & 幂等性
- 问题:高并发点赞,方案怎么设计?
核心答案:
缓存层:使用Redis的SET,Key为article:likes:{文章ID},Value为用户ID。
优点:SADD天然幂等防重;SCARD O(1)复杂度快速计数;SREM快速取消点赞。
持久化:操作Redis后,发消息到RabbitMQ,由消费者异步写入/删除数据库。
- 问题:如何保证消息消费的幂等性,防止重复点赞?
核心答案:
创建幂等记录表:表里有一个唯一键 message_id。
利用数据库事务:在同一个事务里,先INSERT消息ID到幂等表,再执行真正的业务操作(如INSERT点赞记录)。
原理:如果消息重复,INSERT幂等表会因为唯一键冲突而失败,事务回滚,业务代码不执行,从而保证幂等。
- 问题:幂等记录表会无限增大,怎么办?
核心答案:
最佳方案:使用数据库分区表,按天或按周分区。
清理方式:定期DROP或TRUNCATE旧的分区,这是一个DDL操作,速度极快,系统开销小。
兜底方案:增加created_at时间戳字段,定期跑批DELETE删除过期记录。
模块二:两级缓存 & 分布式一致性
4. 问题:为什么需要 Caffeine + Redis 两级缓存?
核心答案:
极致性能:Caffeine是JVM堆内缓存,纳秒级响应,消除了网络开销。
为Redis减压:拦截大部分热点数据请求,保护Redis。
- 问题:如何保证分布式环境下,所有节点的Caffeine缓存数据一致?
核心答案:
兜底:Caffeine设置较短的过期时间(TTL),保证最终一致性。
主动通知:放弃不可靠的Redis Pub/Sub,使用RabbitMQ的Fanout广播交换机。
流程:数据更新后,发送一条“缓存淘汰”消息到Fanout交换机,所有应用实例都能收到,并删除自己的本地缓存。
- 问题:如何管理每个实例的RabbitMQ队列,避免“僵尸队列”和“重启找不到队列”的问题?
核心答案:
稳定命名:通过环境变量给每个实例一个固定的INSTANCE_ID,用它来拼接队列名。
持久化队列:队列声明为durable=true,保证重启后队列和消息不丢失。
自动清理:声明队列时设置x-expires参数(队列TTL),当实例永久下线,队列在超时后会被RabbitMQ自动删除。
模块三:数据同步 & 异常处理
7. 问题:为什么用Canal+MQ同步数据到ES,而不是直接用MySQL的LIKE?
核心答案:
性能:LIKE '%keyword'无法使用索引,会全表扫描,性能极差。
功能:ES基于倒排索引,专为全文检索设计,支持分词、相关度排序等高级功能,速度是毫秒级。
- 问题:Canal、MQ、消费端在数据同步链路中各自的角色是什么?
核心答案:
Canal:伪装成MySQL从库,拉取并解析binlog,将其转换成JSON。
RabbitMQ:作为缓冲层,解耦和削峰填谷,并提供可靠消息传输。
消费端:
数据转换:处理字段名等格式问题。
数据富化:根据binlog中的ID,关联查询其他服务或数据库,组合成一个完整的ES文档。
写入ES:调用ES API,建立索引。
- 问题:消费端在“数据富化”时调用下游服务失败,怎么办?
核心答案:
梯度延迟重试:将消息投递到不同的延迟队列(如1分钟、5分钟、10分钟),给下游服务恢复的时间。利用RabbitMQ的TTL + 死信交换机实现。
最终处理:达到最大重试次数后,将消息投递到最终死信队列。
人工介入:专门的消费者监听死信队列,通过邮件、钉钉等方式告警,通知人工处理。
- 问题:重试机制导致消息延迟,如何解决“旧的UPDATE覆盖新的DELETE”这种消息乱序问题? * 核心答案: 1. 放弃保证顺序:接受消息会乱序的现实。 2. 版本控制:在MySQL表中增加一个update_time或version字段,每次更新时都刷新它。 3. 乐观锁逻辑:消费端处理消息时,先从ES中读出当前文档的版本,然后与收到的消息中的版本进行比较。 4. 丢弃旧数据:只有当消息中的版本大于ES中已存的版本时,才执行更新。否则,直接丢弃这条过期的消息。