当前市场常见的几种消息中间件就有比如说RabbitMQ、RocketMQ、Kafka,他们都各有优势,下面会介绍他们之间的差别和优缺点。这里不针对某种消息队列,常见消息队列糅杂在一起谈谈
常用消息中间件简单介绍
只是简单介绍几种常用消息中间件消息生产和消息消费的过程,具体底层架构和文件存储结构在这里就不做过多的阐述。
RabbitMQ
erlang开发,先说下RabbitMQ的几个关键词吧,如果你安装过RabbitMQ并且在他的可视化界面进行操作,一定对下面这几个关键词不陌生
- server:服务节点
- exchange:交换机
- virtual host:虚拟机
- queue:消息队列
- route_key:很关键,它的作用就是帮助我们的生产者路由到哪个队列
生产消息和消费消息过程:生产者发送消息到一个具体的exchange交换机上面并且还带着一个route_key,不同的队列还绑定者不同的route_key.通过生产者携带的route_key和交换机类型(主题交换机,扇形交换机,直接交换机)来判定将消息发送到哪个消息队列上去。然后消费者已经订阅了某个队列,只要那个队列来数据了。消费者就可以通过某些策略去消费消息。
RocketMQ
阿里的一个开源消息中间件,最大的特点就是支持事务消息,定时消息,Java开发。它跟Kafka有点类似,所以想理解Kafka的可以先了解下RocketMQ,可以一定程度上降低Kafka的学习难度(Kafka应该算是最复杂的一个消息中间件)。下面同样先介绍一下他的一些关键名词。
- name server:一个无状态的集群,name server的集群节点之间是无信息交流的,nameserver里面存储着所有的broker,producer和cosumer,每个节点都有一份。
- broker server:集群,主要作用是存储消息,转发消息。
- topic:主题,每个消息必定带着一个主题
生产消息和消费消息过程:生产者生产一条消息,带着topic,从name server里面拉取存着这个topic的broker节点列表,从节点列表里面选一个发送,然后在这一个broker里面选取一个queue发送,消费者通过绑定的topic从name server里面取一个broker里面的队列进行消费。当然这里面还有很多细节,比如拉取式消费,推动式消费,顺序消费,广播消费,集群消费,事务消息,同步发送,异步发送,消息存储架构,这些就不一一说了,后面可能会写一篇只针对于RocketMQ的博文。
Kafka
scala开发,性能强,适用于大数据场景,强依赖于Zookeeper。Kafka基本别的事也不做,他只存储消息,其他什么选举,转发都交给zookeeper去做了,所以性能极高。同样先介绍几个关键词
- broker:一个Kafka节点就是一个broker
- topic:对消息进行归类,每条消息都必须带着一个topic
- partition:一个topic可以分出很多partition,每个partition内部消息是有序的
- cosumer group:消费组,同一个partition里面的消息只能被一个消费组的一个消费者消费
生产消息和消费消息过程:生产者负责将消息发送到topic下面的某一个partition.然后一个消费组里面的消费者只能消费一个partition里面的消息,也就是说一条消息不能被同一个消费组里面的多个消费者消费。,大概就这样了
消息中间件对比
这里只针对于我所了解的消息中间件进行对比,比如
| RabbitMQ | RocketMQ | Kafka | |
|---|---|---|---|
| 持久化方式 | 内存/文件 | 磁盘 | 磁盘 |
| 集群管理 | name server | zookeeper | |
| 数据可靠性 | 好,producer支持同步异步ack | 很好,支持同步刷盘/异步刷盘/同步双写/异步复制 | 很好,同步刷盘/同步复制 |
| 性能 | 好,内存:RocketMQ的1/2,DIsk:RocketMQ的1/3 | 很好,10万/s | 极好,百万条/s |
| 性能稳定性 | 消息堆积,性能下降 | 单机最多5万队列 | 队列/分区增多,性能急剧下降 |
| 事务消息 | 不支持 | 支持 | 不支持 |
| 定时消息 | 不支持 | 支持 | 不支持 |
消息中间件场景
说到消息中间件一般就是异步处理,应用解耦,流量削峰,日志处理
异步处理
有时候我们处理业务代码希望更快的反应结果给客户端,而其他附带的业务可以利用消息队列慢慢处理去。比如我们处理订单业务逻辑,可能会附带其他逻辑比如积分服务,购物车服务,优惠卷服务等等。我们可以将这些服务发送到消息队列里面去慢慢处理,这就是异步处理。提高系统响应速度。
应用解耦
有时我们会有多个业务逻辑耦合,比如订单服务和库存服务耦合。有时会因为耦合导致库存服务的异常导致下订单失败。我们可以下订单然后发送消息到消息队列然后库存服务在去消费消息。
流量削峰
有时会因为流量的暴增而导致应用系统扛不住流量而导致应用宕掉。这时候我们就可以把流量打到消息队列,应用去消息队列拉取消息去消费。
日志处理
一般日志收集系统是ELK,但有时因为一些需求,会对ELK架构进行一些调整,利用Kafka去接收消息,然后将消息交给logstash去转化成json,然后logstash吧数据交给elastaticsearch实现数据存储,然后通过Kibana进行可视化管理。
消息中间件常见问题及解决思路
顺序消费
消息队列都有多个生产者和多个消费者,那么我们如何保证消费消息的顺序性呢。假设有一个需求是生产者生产了订单消息,加购物车消息,加积分消息,要求我们保证订单->购物车->积分顺序消费。解决办法就是一个生产者让一个消费者消费,此时消费一定是顺序的。
RabbitMQ就可以生产者通过routingkey和交换机类型使只有一个消费者可以消费到这条消息
RocketMQ是通过生产者往一个queue上面发,消费者也通过这个队列消费保证了消费的顺序性
Kafka有个特性就是一个消费组里的消费者只会消费一个partition里面的消息,要想保证消息的全局有序性,可以让partition数量为1和消费组消费者数量也是1就ok了。partition数量为1是为了生产者只往这一个partition消费,消费组消费者数量是1为了不让别的这个消费组里面的别的消费者消费partition中的消息。
重复消费
消息队列是通过网络进行消息的传输,由于网络的不可靠,比如网络抖动,必然会出现各种各样的问题,其中就有重复消费,重复消费首先你得保证发送端是有重试机制的,如果没有重试机制也就没有重复消费问题了。
发送端问题:试想如果你消费了消息,正要告诉exchange或nameserver我消费成功了,但exchange或nameserver由于网络抖动没有接受到消息回复确认,它们又发送了一条消息让你来消费,这就出现了重复消费的问题
消费端问题:消费了消息正要提交ack给exchange或nameserve但一个不小心消费端挂了,好,此时你已经消费了一条消息但没有告诉exchange,消费端重启发送端吧刚刚消费掉的消息给你消费了,出现了重复消费
解决重复消费问题就是消息幂等性问题:
- 很简单给每一条消息一个唯一id,确保了消息的唯一性自然就解决了重复消费的问题。
- 做一个消息消费的日志表记录消费成功的消息编号,如果将要消费的消息在日志表里面就不在消费这条消息。
消息丢失
消息丢失的问题通常和性能相挂钩,鱼和熊掌不可兼得,要想要性能就必然存在消息丢失的情况,要想要消息完全不丢失就必然会牺牲性能,这2者要想要哪一种看业务需求,这里拿Kafka举例。
发送端问题:发送端往往会有一些消息消费确认策略,在Kafka消息发送端有一个配置叫acks,
- acks=0:消息发送端不需要等待broker的确认收到消息就可以继续发送消息,性能很高,大多数用于日志收集,行为数据采集等场景,这些场景丢一些数据也是无所谓的,但由于数据的量级很大,所以性能要求极高。
- acks=1:消息发送端发送给broker会等待broker将消息写进log中不需要等待follower备份成功,后告诉发送端我收到消息了。此时如果leader挂了,由于没有follower没有备份数据,就发生了消息丢失的问题
- acks=-1或all:消息发送端发送给broker会等待所有备份都备份成功就告诉消息发送端我收到了,这时候即使你挂了leader,照样可以不丢失消息。等待备份个数由min.insync.replicas配置控制,当min.insync.replicas=1时效果和acks=1一样会产生丢失消息的情况。
消费端问题:如果消息发送端是配置的是自动签收的话,如果我消息发过来了,我直接签收了,但我并没有消费掉,此时消费端宕机这条还未被消费的消息就不会再消费了,而broker又认为你消费了不会再把消息给你消费。
消息堆积
消息堆积产生情况:
- 有时会因为消费端消费太慢导致堆积大量的消息。
- 当发送端发送了大量的消息在exchange或broker,而现在没有消费者消费。消费者一但启动一堆消息直接打到消费端严重会造成消费端宕机。
- 由于消费端有bug导致消息一直消费不成功。就会导致broker,exchange产生大量的消息。这种情况一般可以利用死信队列解决,消息消费失败多少次直接转移到死信队列。
消息堆积解决办法:
这里解决办法是在消费端无bug情况下,且消费端性能使用没问题的情况下。
- 最简单直接的办法加机器,做集群,消费端消费慢直接多加几个消费者消费不就ok了吗?
- 我们不可能一直加机器来解决这个问题吧,其实我们可以利用一个折中的办法,我们使用一个消费者消费,但这里我们并不消费消息,而是将消息转移到其他的topic上面,这个消费者消费速率会把大量的堆积消息以最快的速度给吃掉,然后让其他的消费者去消费去。这一般用于紧急处理大量消息使用。