消息队列重点要点一览
谈谈互联网后端基础设施 http://www.rowkey.me/blog/2016/08/27/server-basic-tech-stack/
为什么使用消息队列
解耦:一个事务,只关心核心的流程,需要依赖其他系统但不那么重要的事情,有通知即可,无须等待结果。
异步:指的是两个系统的状态保持一致,要么都成功,要么都失败,可以有一定的延迟,只要最终达到一致性即可。
削峰与流控:当上下游系统处理能力不同的时候就需要类似消息队列的方式做为缓冲区来隔开两个系统。
广播:这是消息队列最基本的功能。生产者只需要发布消息,无须关心有哪些订阅者来消费消息。
使用消息队列有什么缺点
系统可用性降低:本来其他系统只要正常运行,那么你的系统就是正常的。现在加了消息队列,如果消息队列挂了,你的系统就也挂了。因此系统可用性降低。
系统复杂性增加:要多考虑很多方面的问题,比如一致性问题、如何保证消息不被重复消费,如何保证保证消息可靠传输。因此需要考虑的东西更多,系统复杂性增大。
消息队列如何选型
目前主流的消息队列软件,主要有以下几种:
-
ActiveMQ:Java中最为简单的消息队列,是对JMS的实现,没有规定消息的顺序、安全、重发等特性。
-
RabbitMQ:是对AMQP协议的实现,对于消息的顺序性、安全、重发等都做了很好的支持。比较适合不允许数据丢失、有事务需求的业务场景下的消息传输。
-
Kafka:是基于Log的消息队列,底层依赖于文件的顺序读取,是append-only的。适合对数据丢失不敏感、强调性能的一些海量日志传输场景中。是最近几年大数据领域很火的一个技术。
-
ZeroMQ:是一个网络编程的Pattern库,将常见的网络请求形式(分组管理,链接管理,发布订阅等)模式化、组件化,简而言之socket之上、MQ之下。对于MQ来说,网络传输只是它的一部分,更多需要处理的是消息存储、路由、Broker服务发现和查找、事务、消费模式(ack、重投等)、集群服务等。
性能角度:Kafka>RocketMQ>RabbitMQ>ActiveMq
开源版本发布角度:RocketMQ>RabbitMQ>ActiveMq
实现语言角度:RabbitMQ->erlang语言,天生具备高并发的特性,但erlang开发人员少。rocketMq和kafka->java,支持分布式,java开发资源充足,可以公司自己维护。
如何保证消息队列是高可用的
以rcoketMQ为例,集群有多master 模式、多master多slave异步复制模式、多 master多slave同步双写模式。NameServer集群用来保存和发现master和slave用(在kafka中是用zookeeper代替)。
Producer 与 NameServer集群中的其中一个节点(随机选择)建立长连接,定期从 NameServer 获取 Topic 路由信息,并向提供 Topic 服务的 Broker Master 建立长连接,且定时向 Broker 发送心跳。Producer 只能将消息发送到 Broker master,但是 Consumer 则不一样,它同时和提供 Topic 服务的 Master 和 Slave建立长连接,既可以从 Broker Master 订阅消息,也可以从 Broker Slave 订阅消息。
如何保证消息不被重复消费
为什么会造成重复消费?
正常情况下,消费者在消费消息时候,消费完毕后,会发送一个确认信息给消息队列,消息队列就知道该消息被消费了,就会将该消息从消息队列中删除。只是不同的消息队列发送的确认信息形式不同,例如RabbitMQ是发送一个ACK确认消息,RocketMQ是返回一个CONSUME_SUCCESS成功标志,kafka消费过消息后,需要提交offset,让消息队列知道自己已经消费过了。那造成重复消费的原因?,就是因为网络传输等等故障,确认信息没有传送到消息队列,导致消息队列不知道自己已经消费过该消息了,再次将该消息分发给其他的消费者。
如何保证消息队列的幂等性?(一般来讲都从业务接受方保证幂等)
- 如果是拿到这个消息做数据库的insert操作。给这个消息做一个唯一主键,那么就算出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据。
- 如果是拿到这个消息做redis的set的操作(即幂等操作),因为你无论set几次结果都是一样的,set操作本来就算幂等操作。
- 如果不是上面两种情况,准备一个第三方介质,来做消费记录。以redis为例,给消息分配一个全局id,只要消费过该消息,将<id,message>以K-V形式写入redis。那消费者开始消费前,先去redis中查询有没消费记录即可。
如何保证消费的可靠性传输
每种MQ都要从三个角度来分析:生产者弄丢数据、消息队列弄丢数据、消费者弄丢数据。
生产者丢数据
从生产者弄丢数据这个角度来看,RabbitMQ提供transaction和confirm模式来确保生产者不丢消息。
transaction机制就是说,发送消息前,开启事物(channel.txSelect()),然后发送消息,如果发送过程中出现什么异常,事物就会回滚(channel.txRollback()),如果发送成功则提交事物(channel.txCommit())。然而缺点就是吞吐量下降了。
生产上用confirm模式的居多。一旦channel进入confirm模式,所有在该信道上面发布的消息都将会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后,rabbitMQ就会发送一个Ack给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了.如果rabiitMQ没能处理该消息,则会发送一个Nack消息给你,你可以进行重试操作。
消息队列丢数据
处理消息队列丢数据的情况,一般是开启持久化磁盘的配置。这个持久化配置可以和confirm机制配合使用,你可以在消息持久化磁盘后,再给生产者发送一个Ack信号。这样,如果消息持久化磁盘之前,rabbitMQ挂了,那么生产者收不到Ack信号,生产者会自动重发。
那么如何持久化呢:
- 将queue的持久化标识durable设置为true,则代表是一个持久的队列
- 发送消息的时候将deliveryMode=2
这样设置以后,rabbitMQ就算挂了,重启后也能恢复数据
消费者丢数据
消费者丢数据一般是因为采用了自动确认消息模式。这种模式下,消费者会自动确认收到信息。这时rabitMQ会立即将消息删除,这种情况下如果消费者出现异常而没能处理该消息,就会丢失该消息。解决方案,采用手动确认消息即可。
如何保证消息的顺序性
通过某种算法,将需要保持先后顺序的消息放到同一个消息队列中(kafka中就是partition,rabbitMq中就是queue)。然后只用一个消费者去消费该队列。
那如果为了吞吐量,有多个消费者去消费怎么办?
比如我们有一个微博的操作,发微博、写评论、删除微博,这三个异步操作。如果是这样一个业务场景,那只要就行。
比如你一个消费者先执行了写评论的操作,但是这时候微博都还没发,写评论一定是失败的,等一段时间。等另一个消费者,先执行写评论的操作后再执行就可以成功。
总之,针对这类问题,没有固定的回答。我的观点是保证入队有序就行,出队以后的顺序交给消费者自己去保证。
作者:孤独烟 出处: http://rjzheng.cnblogs.com/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。