1.基本概念
使用消息队列
- 对峰值流量削峰填谷
- 对次要业务逻辑业务处理
- 对不同模块做解耦合
消息队列引入的新问题:
- 消息丢失
- 消息重复
2.消息为什么会丢失
如果要保证消息只被消费一次,首先就要保证消息不会丢失。
可能丢失消息的场景:
- 消息从生产者写入到消息队列的过程
- 消息在消息队列中的存储场景
- 消息被消费者消费的过程
2.1生产环节丢失
- 问题:消息队列在独立的机器上,消息可能因为网络抖动导致消息丢失
- 解决办法:每次消息发送失败后,消息重传(重试次数有限)
- 缺点:可能造成消息重复
2.2消息队列的丢失(以kafka为例)
- kafka:消息存储在磁盘上,为了避免不停的对磁盘随机IO,将消息写到操作系统的 Page Cache 中,然后再找合适的时机刷新到磁盘上。
- 异步刷盘:时间达到阈值+累计一定的消息后,统一将内存中消息刷入磁盘
- 问题:机器掉电或者异常重启,到此内存中的消息丢失
- 解决办法
- 将刷盘时间设置很短,每条消息都刷盘(不建议)
- 考虑使用集群方式部署kafka服务,通过部署多个备份数据保证消息不丢失
- Leader 负责消息的写入和消费
- 多个 Follower 负责数据的备份
- 其中Follower有一个ISR集合,主库异常后(从ISR从选举一个机器)
- 仍然存在异步复制到Follower消息丢失
- acks=all 当主库和所有ISR确认成功后才算成功
- 集群方案>同步刷盘
- 对于消息丢失有一定容忍度,可以不设置acks=all
- 消息丢失后,业务保证就好(log)(业务场景对消息丢失要求严格)
2.3消费者丢失
- 消费者消费过程
- 接收消息
- 处理消息
- 更新消费进度
- 一定要等到消息结束后更新消费进度
3.如何保证消息只被消费一次
- 避免消息丢失
- 性能消耗
- 消息重复消费
- 即使消息重复消费需要保证消费是幂等的
3.1什么是幂等
多次执行同一个操作和执行一次操作,最终得到的结果是相同的(无状态的)
一件事儿无论做多少次都和做一次产生的结果是一样的,那么这件事儿就具有幂等性
3.2生产者消息幂等
原因:超时重传导致消息重复
解决:Kafka0.11 版本和 Pulsar 中都支持“producer idempotency”的特性,翻译过来就是生产过程的幂等性
实现原理:
- 给每一个生产者一个唯一的 ID,并且为生产的每一条消息赋予一个唯一 ID,消息队列的服务端会存储 < 生产者 ID,最后一条消息 ID> 的映射
- 某一个生产者产生新的消息时,消息队列服务端会比对消息 ID 是否与存储的最后一条 ID 一致,如果一致就认为是重复的消息,服务端会自动丢弃。
3.3消费者消息幂等
- 通用层(AOP)
- 为每条消息生成一个全局唯一ID
- 每次处理消息前,判断消息是否被处理过
- 幂等性由数据库保证(更严格的情况下需要使用事务)
- 业务层
- 可以增加乐观锁
- 账号数据中增加一个版本号的字段,更新条件带上版本号
- 由于重复消息版本号都是一致的,所以不会重复消费
4.总结
- 消息丢失
- 生产者重试
- 消息队列集群配置
- 更新消息状态在最后
- 解决消息丢失引入消息重复性
- 需要消费者保证消费的幂等性
方案设计看场景,这是一切设计的原则(因地制宜)