面试官:什么是 Push 模式和 Pull 模式?各自的优缺点?

面试回答

消息队列里的 PushPull,核心区别在于 消息由谁来驱动投递

Push 模式是 Broker 主动把消息推给消费者。消费者订阅队列后,只要 Broker 有新消息,就会尽快投递给消费者。它的优点是实时性好,消费者不需要反复轮询;缺点是如果 Broker 推得太快,消费者处理不过来,就容易产生消费端积压、内存压力甚至雪崩,所以必须依赖限流、确认机制和预取窗口来做流控。RabbitMQ 的消费者订阅模型就更接近 Push,但它会通过 prefetch 控制单个消费者同时未确认的消息数量。

Pull 模式是消费者主动向 Broker 拉取消息。消费者可以按自己的处理能力决定什么时候拉、一次拉多少、从哪个位点开始拉。它的优点是消费端节奏可控,天然适合批量消费、消息回放和高吞吐场景;缺点是如果拉取间隔不合适,可能增加延迟或产生空轮询开销。Kafka 的消费模型就是典型的 Pull,消费者通过 offset 控制自己的消费进度。

所以面试时可以总结为:Push 更强调低延迟和主动投递,Pull 更强调消费端可控、批量处理和位点管理。真实系统里通常不会绝对二选一,比如 Kafka 的消费者会使用长轮询降低空轮询成本,RabbitMQ 的 Push 也会用 prefetch背压

系统讲解

核心区别

PushPull 不是消息是否经过网络传输的区别,而是投递节奏由谁控制。

维度Push 模式Pull 模式
驱动方Broker 主动投递Consumer 主动拉取
消费节奏Broker 更主动,消费者需要流控消费者自己控制拉取频率和批量大小
实时性通常更好,有消息就推取决于拉取间隔和长轮询策略
吞吐能力适合及时投递,批量能力依赖实现适合批量拉取和高吞吐消费
背压处理需要预取窗口、限流、确认机制消费者可以减少拉取或降低批量大小
典型代表RabbitMQ 消费订阅模型Kafka Consumer

真正要理解这道题,重点不是背定义,而是能讲清楚:当消费者处理能力跟不上生产速度时,系统如何避免被打垮。

推模式

Push 模式下,消费者通常先向 Broker 注册订阅关系。之后只要队列里有消息,Broker 就会主动把消息发送给消费者。这个模型的直觉很简单:消息来了就通知消费者,消费者不用主动探测。

它的优势是延迟低。对于任务队列、业务通知、订单状态变更这类希望尽快触发处理的场景,Push 模式很自然。消费者只要在线并且有处理能力,就可以立即收到消息。

Push 模式的风险也来自“主动”。如果 Broker 只管推送,不考虑消费者处理速度,那么慢消费者会被消息压垮。成熟的 Push 系统通常会配合下面几类机制:

  • 确认机制:消费者处理完成后再发送 ack,Broker 才认为消息消费成功。
  • 预取窗口:限制消费者同一时间最多持有多少条未确认消息。
  • 限流和连接管理:避免单个消费者或单个连接被过量消息打满。

RabbitMQ 的 basic.qosprefetch count 就是典型例子。它看起来是 Broker 主动推消息,但并不是无限推,而是在未确认消息数量达到阈值后暂停继续投递。

拉模式

Pull 模式下,Broker 不主动把消息塞给消费者,而是等待消费者发起拉取请求。消费者可以根据自己的处理能力决定拉取节奏,例如一次拉 100 条、拉不到就等待一段时间、处理变慢时减少拉取。

这个模型更适合高吞吐和批量处理。Kafka Consumer 会主动从对应的分区拉取消息,并维护自己的消费位点 offset。因为消费进度由消费者控制,所以 Kafka 很适合消息回放、多消费者组独立消费和批量读取。

Pull 模式的主要问题是延迟和空轮询。如果消费者频繁请求但没有新消息,会浪费网络和 Broker 资源;如果拉取间隔太长,又会增加消息到达消费者的延迟。常见优化方式是长轮询:消费者发起请求后,如果暂时没有消息,Broker 不立即返回空结果,而是等待一小段时间,有消息后立即返回。

Kafka 的 fetch.min.bytesfetch.max.wait.msmax.poll.records 等参数,本质上都是在延迟、吞吐和消费者处理能力之间做权衡。

代码示例

下面用一个简化的 Go 示例模拟 Pull 模式:消费者按批次从 Broker 拉取消息,处理能力由消费者自己控制。

package main
 
import "fmt"
 
type Broker struct {
	messages []string
}
 
func (b *Broker) Pull(offset, batchSize int) []string {
	if offset >= len(b.messages) {
		return nil
	}
 
	end := offset + batchSize
	if end > len(b.messages) {
		end = len(b.messages)
	}
 
	return b.messages[offset:end]
}
 
func main() {
	broker := Broker{
		messages: []string{"order-created", "payment-success", "stock-deducted"},
	}
 
	offset := 0
	batchSize := 2
 
	for {
		batch := broker.Pull(offset, batchSize)
		if len(batch) == 0 {
			break
		}
 
		for _, message := range batch {
			fmt.Println("consume:", message)
		}
 
		offset += len(batch)
	}
}

这个例子展示了 Pull 模式的关键点:消费者不只是“拿消息”,还控制了拉取批量和消费位点。真实的 Kafka Consumer 还会处理分区分配、offset 提交、重平衡、批量拉取和长轮询。

选型理解

如果业务更关心消息一到就尽快触发处理,而且消息量相对可控,Push 模式会比较自然。例如后台任务、邮件发送、业务事件通知等场景,经常希望 Broker 主动唤醒消费者。

如果业务更关心高吞吐、批量消费、消息回放和消费者自主控制处理节奏,Pull 模式更合适。例如日志采集、埋点数据、实时计算链路和事件流处理,通常会选择 Kafka 这类拉取模型。

不过工程上不要把二者理解成非黑即白。很多系统都是混合形态:Push 系统会引入预取窗口做背压;Pull 系统会引入长轮询来接近实时通知。面试时能讲出这些折中,比只说“一个主动推,一个主动拉”更有深度。

常见追问

追问:为什么 Kafka 不采用纯 Push 模式?

Kafka 的核心目标是高吞吐、批量消费和可回放的分区日志。用 Pull 模式后,消费者可以自己控制 offset、批量大小和处理节奏,不同消费者组也可以独立读取同一份日志。如果改成纯 Push,Broker 就需要为大量消费者维护更复杂的投递状态和流控策略,不利于 Kafka 的事件流模型。

追问:Push 模式一定比 Pull 模式实时吗?

不一定。Push 模式通常更容易做到低延迟,但如果有严格流控、消费者繁忙或队列堆积,消息也会等待。Pull 模式如果使用长轮询,并且拉取参数配置合理,也能做到很低的端到端延迟。

追问:Pull 模式会不会一直空轮询?

朴素的短轮询会有这个问题。实际系统通常使用长轮询、指数退避或最小批量等待来优化。长轮询可以让请求在 Broker 端等待一段时间,有消息就返回,没有消息再超时返回,从而减少空请求。

追问:消费者处理不过来时,两种模式分别怎么处理?

Push 模式主要依赖预取窗口、限流和 ack 控制投递速度;Pull 模式则可以通过减少拉取频率、降低批量大小、暂停分区消费或扩容消费者来处理。两者都需要监控消费延迟、未确认消息数、消费位点落后量和失败重试情况。