消息队列是怎样设计的
Posted EnjoyMoving
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了消息队列是怎样设计的相关的知识,希望对你有一定的参考价值。
之前阅读过一篇美团的技术文章《消息队列设计精要》,觉得写的不错,可以让人更加了解消息队列
消息队列的设计要点,大致总结整理如下:
最简单的消息队列可以做成一个消息转发器,把一次RPC做成两次RPC。
构建一个整体的数据流,例如producer发送给broker,broker发送给consumer,consumer回复消费确认,broker删除/备份消息等。
利用RPC将数据流串起来(可以使用开源RPC框架,比如Dubbo等)
负载均衡、
服务发现、
通信协议:Thrift,Dubbo
序列化协议
存储子系统:通过存储承载消息堆积,然后在合适的时机投递消息
持久化、非持久化;
从速度来看,文件系统 > 分布式KV(持久化)> 分布式文件系统 > 数据库,而可靠性却截然相反。需要根据具体业务选择
消费关系的保存:
单播:点对点
多播:一点对多点
广播关系的维护,一般由于消息队列本身都是集群,所以都维护在公共存储上,如config server、zookeeper等。
消费确认(任务执行完整性):
把消息的送达和消息的处理分开,这样才真正的实现了消息队列的本质-解耦。
对于没有特殊逻辑的消息,默认Auto Ack也是可以的
但一定要允许消费方主动进行消费确认ack,并与broker约定下次投递时间
高级特性:
可靠投递(最终一致性):
高可用:超时重发与消息重复、消息队列幂等设计(broker多机器共享一个DB或者一个分布式文件/kv系统)、定时任务补偿
不是所有的系统都要求最终一致性或者可靠投递。任何基础组件要服务于业务场景。
消息可能会重复,并且在异常情况下,要接受消息的延迟。
每当要发生不可靠的事情(RPC等)之前,先将消息落地,然后发送。
当消息发送失败或者不知道成功失败(比如超时)时,消息状态是待发送。
对于各种不确定(超时、down机、消息没有送达、送达后数据没落地、数据落地了回复没收到),其实对于发送方来说,都是一件事情,就是消息没有送达。
定时任务不停轮询所有待发送消息,最终一定可以送达。
重复消息:
如何鉴别消息重复,并幂等的处理重复消息:
版本号
状态机
一个消息队列server如何尽量减少重复消息的投递:
鉴别消息重复:broker记录MessageId,直到投递成功后清除,重复的ID到来不做处理。
顺序消息:
顺序消息要求:允许消息丢失;从发送方到服务方到接受者都是单点单线程。
pull模式实现比较容易(见下)
一个主流消息队列的设计范式里,应该是不丢消息的前提下,尽量减少重复消息,不保证消息的投递顺序。
事务特性:
在与本地业务的同一个事务中,本地消息落地(落库、需要业务方提供数据库)
消息只要投递到服务端确认后本地才做删除
定时任务扫描本地消息库表进行补偿发送
性能优化
异步:
对于客户端来说,同步与异步主要是拿到一个Result,还是Future(Listenable)的区别。实现方式可以是线程池,NIO或者其他事件机制。
服务端异步需要RPC协议支持。参考servlet 3.0规范,服务端可以吐一个future给客户端,并且在future done的时候通知客户端。
实践:
客户端必须等待服务端消息成功落地,才算是消息发送成功。
我们不希望消息的发送阻塞客户端的主流程,所以可以先使用线程池提交一个发送请求,主流程继续往下走。
服务端是纯异步。客户端的线程池wait在服务端吐回的future上,直到服务端处理完毕,才解除阻塞继续进行。
批量:消费者合适消费消息:通过网络请求小包合并成大包提高性能
消息消费是push还是pull:
push:push模型最大的致命伤是慢消费。
如果消费者的速度比发送者的速度慢很多,势必造成消息在broker的堆积。
最致命的是broker给consumer推送一堆consumer无法处理的消息,consumer不是reject就是error,然后来回踢皮球
所以对于建立索引等慢消费,消息量有限且到来的速度不均匀的情况,pull模式比较合适。
pull:pull模式存在消息延迟与忙等问题
pull模式如果想做到全局顺序消息,就相对容易很多:
producer对应partition,并且单线程。
consumer对应partition,消费确认(或批量确认),继续消费即可。
所以对于日志push送这种最好全局有序,但允许出现小误差的场景,pull模式非常合适。如果你不想看到通篇乱套的日志~~Anyway,需要顺序消息的场景还是比较有限的而且成本太高,请慎重考虑。
(完)
EnjoyMoving,与你共同成长~
如果觉得有收获,欢迎转发,谢谢~
EnjoyMoving
以上是关于消息队列是怎样设计的的主要内容,如果未能解决你的问题,请参考以下文章
RabbitMQ中有大批量的消息,此时多个消费者同时访问消息队列是怎样取里面的消息的?