4、消息队列核心:消息队列模型(点对点、发布订阅)、RabbitMQ 与 Kafka 延迟对比、磁盘顺序写与页缓存机制
4.1 两种消息模型:点对点 vs 发布订阅
消息队列的模型,说白了就两种:点对点和发布订阅。我刚开始接触消息中间件时,总觉得这两者差不多,直到有一次在项目里用错了模型,导致消息被多个消费者重复消费,数据全乱了。嗯,从那以后我再也不敢混淆它们了。
点对点模型(P2P)
点对点模型,也叫队列模型。一条消息只能被一个消费者消费。消息生产者把消息发到队列里,消费者从队列里拉取。谁抢到算谁的。
我在项目中遇到过这样的场景:订单系统需要异步处理库存扣减。多个库存服务实例都监听同一个队列,但一条订单消息只需要被一个实例处理。这时候点对点模型就非常合适。
- 一条消息只能被一个消费者消费
- 消息一旦被消费,就从队列中移除
- 适合任务分发、负载均衡场景
发布订阅模型(Pub/Sub)
发布订阅模型就完全不一样了。生产者把消息发到主题(Topic)上,所有订阅了这个主题的消费者都能收到这条消息。你想想看,这不就是广播吗?
我记得有一次做实时数据同步,需要把用户行为数据同时推送给推荐系统、监控系统和日志系统。如果用点对点模型,我得复制三份消息分别发到三个队列。用发布订阅模型就简单了,一条消息发到主题,三个订阅者各自消费。
- 任务分发、负载均衡 → 点对点
- 事件广播、数据同步 → 发布订阅
- 如果拿不准,先问自己:这条消息需要被多个消费者处理吗?
4.2 RabbitMQ 与 Kafka 延迟对比
说到延迟,很多人一上来就问:RabbitMQ 和 Kafka 哪个延迟更低?其实这个问题没那么简单。我做过不少压测,咱们直接看数据。
| 维度 | RabbitMQ | Kafka |
|---|---|---|
| 端到端延迟(P99) | 微秒级(10-100μs) | 毫秒级(1-10ms) |
| 吞吐量 | 万级/秒 | 百万级/秒 |
| 消息确认机制 | 逐条确认 | 批量确认 |
| 存储方式 | 内存+磁盘 | 磁盘顺序写 |
为什么会这样?说白了就是设计哲学不同。
RabbitMQ 追求的是低延迟和可靠性。每条消息都要确认,确认完才处理下一条。这种设计让延迟非常稳定,但吞吐量上不去。我在金融交易系统里用过 RabbitMQ,延迟基本控制在 50μs 以内,但单机吞吐量也就几万条每秒。
Kafka 追求的是高吞吐。它把消息攒一批再批量写入磁盘,用顺序写的方式把磁盘性能发挥到极致。代价就是延迟会高一些,毕竟要等一批消息凑够才处理。我曾经在日志采集系统里用 Kafka,单机吞吐量轻松跑到 50 万条/秒,但延迟在 2-5ms 左右。
4.3 磁盘顺序写与页缓存机制
Kafka 为什么能那么快?核心秘密就两个:磁盘顺序写和页缓存。
磁盘顺序写
传统观念里,磁盘很慢。但那是随机读写。顺序写完全是另一回事。我做过测试,机械硬盘的顺序写速度能达到 150MB/s,而随机写只有 1MB/s 左右。差了 150 倍!
Kafka 充分利用了这个特性。它把消息追加到文件末尾,不做任何随机写入。你想想看,这就像往笔记本后面一页页加内容,而不是翻来翻去到处写。
- 磁盘寻道时间几乎为零
- 充分利用磁盘带宽
- 减少磁盘碎片
- 配合页缓存,性能接近内存
页缓存机制
Kafka 还有一个骚操作:它不自己做缓存,而是直接利用操作系统的页缓存(Page Cache)。
消息写入时,先写到页缓存,然后由操作系统异步刷盘。消息读取时,如果数据还在页缓存里,直接从内存返回,根本不用走磁盘。这招高明在哪?
- 省去了 JVM 堆内缓存的管理开销
- 避免了 GC 带来的停顿
- 操作系统比应用更懂内存管理
我记得有一次排查 Kafka 性能问题,发现消费者消费速度突然变慢。一看监控,页缓存命中率从 99% 降到了 60%。原来是另一台机器在大量读写文件,把页缓存给挤占了。嗯,这就是共享页缓存的副作用。
- 给 Kafka 留足内存,页缓存越大越好
- 不要和 Kafka 共用机器跑其他 IO 密集的应用
- 监控 /proc/meminfo 中的 Cached 和 Dirty 指标
- 如果页缓存命中率低于 90%,考虑增加内存或减少分区数
4.4 核心逻辑关系图
下面这张图展示了消息队列的核心知识体系,我画了很久才理清楚它们之间的关系。
这张图把消息队列的核心分成了三块:消息模型决定了消息怎么分发,延迟对比帮你做技术选型,磁盘顺序写和页缓存则是 Kafka 高性能的底层支撑。三者环环相扣,缺一不可。
公众号:蓝海资料掘金营,微信deep3321