一 什么是消息队列
消息是指在应用之间传送的数据,包含字符串,文本及对象等。
消息队列(MQ)是一种应用间的通信方式,消息发送后可以立即返回,有消息系统来确保信息的可靠专递,消息发布者只管把消息发布到MQ中而不管谁来取,消息使用者只管从MQ中取消息而不管谁发布的,这样发布者和使用者都不用知道对方的存在。
二 消息队列的优点
1.解耦
以任务系统为例,在有任务操作(创建,修改,完结等)的时候会发送短信提醒。采用的方法是任务业务中调用发生短信接口。在后期开发中比如添加了推送和系统消息的功能,所以需要找到所有任务操作的接口,在接口中添加调取推送和系统消息的接口。不利于接口维护和调用,耦合性太大了。
图例:
优化:添加消息队列,在任务操作的接口中只调用发送消息的接口,把消息发送到消息队列,这样任务操作的代码就可以不动了。不管是添加推送还是删除发送短信只需要在消费者中去更改就好了。实现了解耦也减少了多次更改代码带来的问题。
图例:
2.异步
同样以任务系统为例,在有任务操作(创建,修改,完结等)的时候会发送短信提醒,推送和系统消息等功能,而没有加入消息队列时全部是同步接口调用,当发生短信完成后才会执行以后的逻辑,时间响应上会有延迟,客户体验不好。
图例:
优化添加消息队列,在创建任务处理完任务业务的时候调取发送消息队列接口。之后就可以返回结果了,站内信等接口可以自行调用消息队列接口进行数据的拉取处理。这样减少了创建任务接口的运行时间,给用户端的体验也会友好。
图例:
3.削峰
比如秒杀系统。我们的数据库每秒中的处理数据是有限的。当请求的数据量大于最多处理量会导致数据库打死。然而消息队列同时处理数据的量是远远大于数据库的。我们可以通过向消息队列中存入大量的消息,我们的业务从消息队列中分批的拉取消息进行处理,这样就可以避免数据库打死的情况。
图例:
三 消息队列的缺点
1.系统可用性降低
系统引入的外部依赖越多,越容易挂掉。本来创建任务会调用站内信,短信推送等接口,现在又多加了一个消息队列,万一消息队列挂了,整套系统就会挂掉,创建舆情就同步不到站内信等系统中了。
2.系统复杂度
多加了一个消息队列可能出现很多情况需要保证,消费的顺序,是否存在多次消费,是否存在数据丢失,以及消费后的幂等性。
四 消息模型
Java消息服务(JMS)规范目前支持两种消息模型:点对点和发布/订阅
点对点为队列,不可重复消费
发布/订阅为主题,可以重复消费
图例:
五 消息队列对比
特性 | RocketMQ | Kafka | RabbitMQ |
单机吞吐量 | 10万级 | 10万级别 | 万级 |
topic数量对吞吐量的影响 | topic可以达到几百,几千个的级别,吞吐量会有较小幅度的下降 这是RocketMQ的一大优势,在同等机器下,可以支撑大量的topic | topic从几十个到几百个时,吞吐量会大幅度下降 所以在同等机器下,kafka尽量保证topic数量不要过多。如果要支撑大规模topic,需要增加更多的机器资源 |
|
时效性 可用性 可靠性 | ms级 非常高,分布式架构 可以做到0丢失 | ms级 非常高,kafka是分布式的,一个数据多个副本 可以做到0丢失 | 微秒级 高,基于主从架构实现高可用性 |
优劣势总结 | 日处理消息上百亿之多,可以做到大规模吞吐,性能也非常好,分布式扩展也很方便,社区维护还可以,可靠性和可用性都是ok的,还可以支撑大规模的topic数量,支持复杂MQ业务场景
而且一个很大的优势在于,阿里出品都是java系的,我们可以自己阅读源码,定制自己公司的MQ,可以掌控
| kafka的特点其实很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展
而且kafka唯一的一点劣势是有可能消息重复消费,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略 | erlang语言开发,性能极其好,延时很低;
吞吐量到万级,MQ功能比较完备 而且开源提供的管理界面非常棒,用起来很好用 问题在于,RabbitMQ确实吞吐量会低一些。
|
六 如何保证消息队列的高可用
kafka
由多个broker组成,每个broker就是一个节点;创建一个主题,这个主题可以划分为多个分区,每个分区可以存在于不同的broker上,每个分区存放一部分数据。
kafka 0.8以后,每个分区的数据都会同步到其他机器上,形成自己的多个副本。然后所有副本会选举一个主体出来,那么生产和消费都跟这个主打交道,然后其他副本就是从。写的时候,主会负责把数据同步到所有从上去,读的时候就直接读主上数据即可。kafka会均匀的将一个分区的所有副本分布在不同的机器上,这样才可以提高容错性。
写数据的时候,生产者就写主,然后主将数据写本地磁盘,接着其他从主动从主来拉取数据。一旦所有从同步好数据了,就会发送ack给主,主收到所有从的ack之后,就会返回写成功的消息给生产者。
图例:
rocketMQ
主从配合,主支持读写,从只读,生产者只能和主连接写消息,消费者可以连接主和从。
消费者高可用,当主不可用或者繁忙时,消费者会被自动切换到从读。即使主出现故障,消费者仍然可以从从读消息,不受影响。
生产者高可用,创建 topic 时,把消息队列创建在多个 broker 组上(brokerName 一样,brokerId 不同),当一个 broker 组的主不可用后,其他组的主仍然可以用,生产者可以继续发消息。
七 如何保证消息队列消费的幂等性
假设向数据库中写入一条数据,当消息队列发生来重复的消息就会导致数据库中存在俩条一样的数据,产生脏数据。
解决:
1.设置数据库唯一索引,使添加重复数据失败。
2.向数据库添加数据前,先向数据库查询一下,存在修改,不存在删除
3.通过天然的幂等性技术进行去重(Redis,Set)
八 如何保证消息的可靠性传输
kafka
1)消费端弄丢数据唯一可能是消费者那边自动提交了offset,让kafka以为你已经消费好了这个消息,其实你刚准备处理这个消息,还没处理就挂了,此时这条消息就丢了。
解决办法是关闭自动提交offset,在处理完之后自己手动提交offset
2)kafka弄丢了数据
kafka某个主宕机,然后重新选举主时。从服务还没有同步主,导致丢失数据。
所以此时一般是要求起码设置如下4个参数:
给topic设置replication.factor参数:这个值必须大于1。要求每个分区必须有至少2个副本
给kafka服务端设置min.insync.replicas参数:这个值必须大于1。这个是要求一个主至少感知到有至少一个从副本还跟自己保持联系,没掉队,这样才能确保主挂了还有一个从
在生产者端设置acks=all:要求每条数据,必须是写入所有副本,才能认为写成功了。
在生产者端设置retries=MAX:这个是要求一旦写入失败,就无限重试,卡在这里了。
rocketMQ
生产者的可靠性保证:生产者发送消息后返回发送结果,如果返回true,则表示消息已经确认发送到服务器并被服务器接收保存。整个发送过程是一个同步过程。
服务器的可靠性:消息生产者发送的消息,RocketMQ服务收到后在做必要的校验和检查之后马上保存到磁盘,写入成功后返回给生产者。
消费者的可靠性:消费者是一条一条顺序消费的,之后在成功消费一条后才会消费下一条。如果在消费某一条消息时失败则会重试消费这条消息,默认为5次,如果超过最大次数仍然无法消费,则将消息保存到本地,后台线程继续重试消费,主线程则会继续往后走,消费队列后面的消息。
九 如何保证消息的顺序性
kafka
保证消费的顺序,可以分为俩部分,生产的顺序与消费的顺序。其中kafka中分区的存储是有顺序的。所有生产者在写的时候,其实可以指定一个 key,那么这个key相关的数据,一定会被分发到同一个分区中去,而且这个分区中的数据一定是有顺序的。消费者从分区中取出来数据的时候,也一定是有顺序的。只是如果用多线程处理消费会打乱顺序。所有写 N 个内存队列,具有相同key的数据都到同一个内存队列;然后对于 N 个线程,每个线程分别消费一个内存队列即可,这样就能保证顺序性。
图例:
rocketMQ
rocketMQ是支持顺序消费的,但是顺序消费不是全局顺序,只是分区顺序。要全局顺序只能一个分区。
之所以出现不是顺序消费,是因为发送消息的时候,消息发送默认是会采用轮询的方式发送到不通的队列分区,而消费端消费的时候,是会分配到多个队列的,多个队列是同时拉取提交消费的。
图例:
所以要实现顺序需要把消息确保投递到同一个队列中。