作者 | 葛贤亮,单位:中国移动智慧家庭运营中心
Labs 导读
近年来,互联网技术发展迅猛,各行各业的信息量急剧膨胀。随着云计算和算力网络时代的到来,消息中间件在国内许多行业的关键应用中越来越受到重视。在高并发分布式场景下,合理地利用消息中间件往往能起到突破性能瓶颈与化繁为简的效果。
前期从“作用”与“协议”出发,使读者对消息中间件技术有了初步认识。本期由“传输模式”与“消费模式”切入,期望能以点见面使读者对消息中间件有更深入的了解。
1 消息中间件的传输模式
从传输视角看,MQ的消息模式可分为“点对点”和“发布/订阅”两种模式。
1.1 点对点(Point-to-Point,PTP)
顾名思义,PTP模型用于消息生产者和消息消费者之间点对点的通信。消息生产者基于某种规则将消息发送到特定的消费者,通常是某个固定的消息队列(Queue),在消息传递给消费者之前它被存储在这个队列中。队列消息可以放在内存中也可以持久化,以保证在消息服务出现故障时仍然能够传递消息。
PTP模型中,一个队列可以有多个生产者和多个消费者。消息服务器按照收到消息的先后顺序,将消息放到队列中。队列中的每一条消息,只能由一个消费者进行消费,消费之后就会从队列中移除。
图1.1 点对点模型
需要注意的是,尽管使用Queue的概念,但并代表先进入队列的消息,一定会被先消费。为了提升系统并发性,很多MQ实现方案中引入了“消费者组”的概念,队列中的消息会被分发到不同的消费者进行并行处理。这意味着消息在发送的时候是有序的,但是在消费的时候就变成无序了。一些MQ提供了“专有消费者”或者“排他消费者”的概念,在这种情况下,队列中的消息仅允许一个消费者进行消费。但是,这也牺牲了消息的并发消费能力,消息量很大的情况下,将会产生严重的消息积压。为了解决这一问题,一些MQ方案(如 Kafka、RocketMQ)又引入了分区的概念,相同主题的消息使用不同分区进行隔离,同一分区内的消息可以有序消费,不同分区内的消息可以并行消费。
1.2 发布/订阅(Publish-and-Subscribe,Pub/Sub)
Pub/Sub模式类似广播消息,生产者将消息发布到一个主题(Topic)中,订阅了该Topic的所有下游消费者,都可以接收到这条消息。
在这种模型下,发布者和订阅者彼此无感知。相较于PTP模型中一个消息只能被一个消费者消费,Pub/Sub模型中一个消息会被发送至所有订阅了该Topic的消费者进行消费。
图1.2 发布/订阅模型
Queue与Topic区别:
- Queue实现了负载均衡,将生产者生产的消息发送到消息队列中,由多个消费者消费。但一个消息只能被一个消费者接受,当没有消费者可用时,这个消息会被保存直到有一个可用的消费者。
- Topic实现了发布和订阅,当发布一个消息时,所有订阅这个Topic的服务都能得到这个消息;所以从1到N个订阅者都能得到这个消息的拷贝。
2 消息中间件的消费模式
从消费视角看,MQ的消费模式可分为“Push”和“Pull”两种模式。
2.1 推消息模型(Push)
消息生产者将消息发送给消息服务器后,消息服务器主动地将消息“推送”给消费者。
2.1.1 Push模型优点
Push模型最大的好处就是实时性。因为服务端可以做到只要有消息就立即推送,所以消息的消费没有“额外”的延迟。
2.1.2 Push模型缺点
Push模型看上去很美好,但在实际使用中却存在很多硬伤,以至于有些MQ的Push模型底层也是依托Pull方式实现的。
❖ Push模型存在以下问题:
- 在Broker端需要维护Consumer的状态,不利于Broker去支持大量Consumer的场景;
- Consumer的消费速度不一致,由Broker进行推送难以根据不同的Consumer状况选择相应的推送策略(时机);
- Broker难以处理Consumer无法消费消息的情况(Broker无法确定Consumer的故障是短暂的还是永久的);
- 在Consumer消费能力不足的情况下,大量的推送消息会加重Consumer的负载或者冲垮Consumer。
2.2 拉消息模型(Pull)
消息生产者将消息发送给消息服务器后,由消息消费者主动从消息服务器中拉取该消息。
2.2.1 Pull模型优点
相较于Push模型,Pull模型存在以下优势:
- Broker不再需要维护Consumer的状态(每一次pull都包含了偏移量等必要信息);
- 由Consumer维护状态,Consumer可以很容易的根据自身的负载等状态来决定从Broker获取消息的频率;
- 可以实现消息聚合。Push模型中,Broker无法预测写一条消息产生的时间,所以在收到消息之后只能立即推送给Consumer,所以无法对消息聚合后再推送给Consumer。而Pull模型中由Consumer主动获取消息,每一次Pull时都能批量获取Broker中的消息。
2.2.2 Pull模型缺点
优势的反面即时劣势,Pull模型存在以下缺点:
- 实时性差。因为由Consumer主动Pull消息,所以实时性和Pull的周期相关,这样就产生了“额外”延迟。如果通过提升Pull的执行频率来降低延迟,又会在没有消息的时候产生大量的Pull请求(消息中间件是完全解耦的,Broker和Consumer无法预测下一条消息在什么时候产生);
- Pull模型中由Consumer维护状态,所以多个Consumer之间需要相互协调,这样就需要引入ZooKeeper(Kafka)或者自己实现 NameServer(RocketMQ)之类的服务来完成Consumer之间的协调工作。
2.3 Push/Pull区别
❖ 在实际应用场景中:
- 大吞吐量的消息队列都是采用Pull模式,而非Push模式;
- 采用Push模式,消费端的性能会影响整个消息队列服务器的性能;
- 采用Push模式,容易造成broker的消息积压,因为broker控制消息的推送速率,消息数量大的话,很难使每个消费者很难适应消息推送速率;
2.4 常用消息中间件支持模型
2.5 Long-Polling
如上所述,虽然很多消息中间件都支持Push模式,但是在实现时其实也是采用pull方式实现的push语义。
比较成熟的做法是采用Long-Polling的方式,基于Push/Pull方案的优缺点,在性能与时效性之间寻找到一个平衡点。
Long-Polling是Pull模式的变种。Pull模型中不管服务端数据有无更新,客户端每隔定长时间拉取一次数据,可能有更新数据返回,也可能什么都没有。Long Polling是指客户端发起Long Polling,此时如果服务端没有消息,会hold住请求,直到服务端有可消费的消息,或者到达超时时间才会返回请求。返回后,客户端又会立即再次发起下一次Long Polling。这种方式解决了Pull模式数据通知不及时的问题,且减少了大量的无效轮询次数。
hold住请求指的服务端暂时不回复结果,保持住相关请求,不关闭请求连接,等相关数据准备好,再写回客户端。
由此可见,Long-Polling模式下:
- 在Broker一直有可读消息的情况下,Long-Polling就等价于执行间隔为0的Pull模式(每次收到Pull结果就发起下一次Pull请求,当然也可根据实际情况设置最小间隔保护时间或单批次最小消息数量);
- 在Broker没有可读消息的情况下,请求阻塞在了Broker,在产生下一条消息或者请求“超时之前”响应请求给Consumer。
3 总结
虽然Push模式在语义上更符合事件驱动架构风格,但在当前互联网大数据量高并发的背景下,Pull模式(含Long-Polling)逐步成为主流实现方案。对于使用方来说,综合考虑两者的差异与特性,才能做好技术选型与分析决策。