RabbitMQ
保证消息不丢失
生产者确认机制: 消息发送到 MQ 后会返回一个结果给发送者, 表示消息是否处理成功.
消息失败后处理方法:
- 回调方法即时重发
- 记录日志
- 保存到数据库然后定时重发, 成功发送后即时删除表中的数据
消息持久化, 默认内存存储消息, 开启持久化确保缓存在 mq 中的消息不丢失:
- 交换机持久化
- 队列持久化
- 消息持久化, SpringAMQP 中的消息默认时持久的, 可以通过
MessageProperties
中的DeliveryMode
指定
消费者确认: 消费者处理消息后向 mq 发送 ack, mq 接受到 ack 后才会删除该消息, SpringAMQP 有三种确认模式:
- manual: 手动 ack, 业务代码结束后调用 api 发送 ack
- auto: 自动 ack, 由 spring 检测 listen 代码是否出现异常, 没有异常返回 ack, 否则返回 nack
- none: 关闭 ack, mq 假定消费者获取消息后会成功处理, 消息投递后立即被删除
利用重试机制, 消费者出现异常时重试. 达到重试次数后, 如果依然失败则将消息投递到异常交换机, 人工介入处理.
消息重复消费
- 每条消息设置一个唯一标识 id
- 加锁: 分布式锁, 数据库锁
延迟队列
延迟队列 = TTL + 死信交换机
消息成为死信的条件:
- 消费者使用
basic.reject
或者basic.nack
声明消费失败, 并且消息的requeue
设为false
- 消息时一个过期消息, 超时无人消费
- 要投递的队列消息堆积满了, 最早的消息可能成为死信
队列配置了dead-letter-exchange
属性, 制定了一个交换机, 则队列中的死信就会投递到这个交换机种, 这个交换机被称为死信交换机 (DLX)
ttl: 一个队列中的消息 ttl 结束仍未消费, 则会变为死信, ttl 超时的两种情况:
- 消息所在队列设置了存活时间
- 消息本身设置了存活时间
或者安装延迟队列插件使用延迟队列
消息堆积解决
三种方案:
- 增加更多消费者, 提高消费速度
- 在消费者内开启线程池加快消息处理速度
- 扩大队列容积, 提高堆积上限, 使用惰性队列
惰性队列:
- 接收到消息后直接存入到磁盘中而非内存
- 消费者要消费消息时才会从磁盘中读取并加载到内存
- 支持数百万条的消息存储
高可用机制
普通集群
标准集群, 会在集群的各个节点之间共享部分数据, 包括: 交换机, 队列元信息. 不包含队列中的消息.
- 当访问集群中某节点时, 如果队列不在该节点, 会从数据所在节点传递到当前节点并返回.
- 队列所在节点宕机, 队列中的消息就会丢失.
一个节点宕机就不可用, 没有高可用
镜像集群
主从模式:
- 交换机, 队列, 队列中的消息会在各个 mq 镜像节点之间同步备份
- 创捷队列的节点被称为该队列的主节点, 备份到的其他节点叫做该队列的镜像节点
- 一个队列的主节点可能是另一个队列的镜像节点
- 所有操作都是在主节点完成, 然后同步给镜像节点
- 主节点宕机后, 镜像节点会代替成为新的主节点
同步期间宕机会丢失数据
仲裁队列
用来代替镜像队列:
- 与镜像队列一样, 主从模式, 支持主从数据同步
- 主从同步基于 Raft 协议, 强一致性
Kafka
保证消息不丢失
生产者发送消息到 broker 丢失:
- 设置异步发送, 接受异常记录日志并处理
- 消息重试
消息在 broker 中存储丢失:
-
发送确认机制 acks
消费者从 broker 接收消息丢失:
kafka 分区机制: 将每个主题划分为多个分区 (partition)
topic 分区中的消息只能由消费者组中的唯一一个消费者处理, 不同分区分配给同一消费者组的不同消费者
关闭自动提交, 使用手动提交避免消息没有消费/重复消费:
同步+异步提交:
保证消费顺序性
一个 topic 有多个分区, 多个分区无法保证顺序性, 但是分区内偏移量顺序递增, 所以可以:
- 发送消息时指定分区号
- 发送消息时按照相同的业务设置相同的 key, kafka 会对 key 哈希来指定分区号
高可用机制
集群: 多个 broker 组成一个 kafka 集群
复制机制:
- 一个 topic 有多个分区, 每个分区有多个副本, 一个 leader, 其余都是 follower, 副本存储在不同的 broker 中
- 所有分区副本内容相同, leader 故障时会将一个 follower 提升为 leader
- follower 有 ISR(in-sync replica, 同步复制) 和普通副本 (异步复制), 优先从 ISR 中选取副本作为 leader
数据清理机制
存储结构:
- topic 数据存储在分区上, 分区文件过大就会分段存储
- 每个分段都在磁盘上以索引(xxx.index)和日志文件(xxx.log)形式存储
分段好处:
- 减少单个文件内容大小,查找数据方便
- 便于 Kafka 进行日志清理
日志清理策略:
- 根据消息的保留时间,消息保存时间超过指定时间(默认 168 小时)就会触发清理
- 根据 topic 存储的数据大小,当日志文件大于阈值则会开始删除最久的消息(默认关闭)
高性能设计
- 消息分区:存储不受单台服务器限制,处理更多数据
- 顺序读写:磁盘顺序读写,提高读写效率
- 页缓存:把磁盘中的数据缓存到内存中,把对磁盘的访问变为对内存的访问
- 零拷贝:减少上下文切换以及数据拷贝
- 消息压缩:减少磁盘 IO 和网络 IO
- 分批发送:将消息打包批量发送,减少网络开销
减少了 页缓存 -> Kafka -> socket 缓存 -> 网卡
的过程