消息队列
核心作用:将数据保存到一个中转点,设计实现参考各种快递站点。
使用场景 : 异步、削峰、 解耦。
核心功能:上传数据、保存数据、获取数据。
涉及到的角色 : 生产者、消息队列服务、消费者。
主要工作流程:生产者上传数据到消息队列服务,消息队列服务保存数据,消费者获取消息队列服务保存的数据。
需要解决的问题 :
1、生产者怎么上传数据?涉及到通信协议、消息路由、消息编码、怎么界定上传成功还是失败(做的不好会丢失消息)?
2、消息队列服务怎么保存数据?存到内存还是存到磁盘?存多久?怎么保证高效可靠?怎么横向扩展?怎么查询?
3、消费者怎么知道有消息需要消费(定时拉取还是等待推送?)、怎么知道哪些消息是给自己的?怎么记录消费到了哪个地方(做得不好会导致漏消费或者重复消费)?
带着这几个问题去分析、理解常见的几种消息队列、针对自己的业务场景做技术选型、甚至设计自己的消息队列会容易很多。
Kafka篇:
Kafka使用基于TCP的二进制协议。
生产者
生产的消息包括 主题、 键、值三部分, 通过主题来指定消息发送给哪个主题。 通过键和分区器来指定消息存储到主题中的哪个分区中。 键也可以不指定。界定消息是否发送成功有三种配置方式。
- 消息发送给消息队列服务之后就认为成功了, 立马返回,让消息队列服务异步的去处理消息的存储、复制。 这种方式非常块,但是不能保证消息队列服务对消息的存储、复制成功, 所以这种方式性能好但可靠性没那么高。
- 消息队列服务在将消息存储完并保证所有副本都复制成功之后认为是成功了。 这种方式可靠性高,但是存储和复制是阻塞的,所以性能会差一些。
- 消息队列服务在将消息存储到主要分区之后就认为成功了,立马返回, 分区间复制异步处理,复制可能会失败, 这种方式性能和可靠性介于前两种之间。
消息队列服务
存储方式 : 存到磁盘中, 在系统的页面缓存中也会有数据。消费者会优先从系统的页面缓存中读取数据避免磁盘io操作, 所以内存越大, 能为消费者缓存的数据越多。
Kafka中的主题可以理解为es中的索引,分区可以理解为es中的类型,消息可以理解为es中的文档。发布消息就是往指定的主题下指定的分区中写入一条记录。
Kafka中存储的消息同一个分区中的消息是有序的, 同一个主题中不同分区中的消息是无序的,不能直接把他们的顺序拼接还原。
存多久: 可以设置存一定时间(默认7天), 也可以设置存多大空间(比如只保存1G)。
怎么保证高效: 消息顺序写入磁盘避免写入时的查找和非零拷贝。
怎么保证可靠:通过副本对数据进行备份, 通过多个broker为服务提供主备。
怎么横向扩展:设置很多分区,慢慢增加broker和减少单个 broker中分区的数量。
数据怎么查询:采用稀疏索引的方式查询。
还可以补充消息的格式、消息存储的文件等。
消费者
怎么知道哪些消息是给自己的:消费者通过主题订阅(监听)消息队列服务中的某个主题, 然后消费者组会协调组内的消费者去订阅主题中的哪些分区(相当于建立一个组内消费者和主题中分区多对对的关系)。如果消费者组中消费者数量或者主题中分区的数量发生变化,则原来的多对对关系会被打破, 触发再均衡。 再均衡可能会导致漏消费或者重复消费,具体看消费者策略。
怎么知道有消息要消费:消费者通过轮询去查分区中是否有新的消息到达(通过消息的偏移量判断有没有新消息)。
怎么记录消费到了哪个地方:消费者会将topic每个分区中消费过的消息的偏移量也存储到消息队列服务中,注意这是一个独立的操作!!!
具体的记录方式有有以下几种:
- 定时自动提交:消费者隔5秒或者多久把自己这点时间pull下来的消息中偏移量的最大值提交上去。 这种方式的下如果提交前消费者挂了或者触发了再均衡,会导致期间消费的消息不被记录,出现重复消费。但是好处是提交次数少,性能较好。
- 提交当前偏移量:每pull一次就提交一次,broker响应提交之前会阻塞,提交次数多,但是性能差点。我们如果对消息的可靠性要求比较高可以选择这种发生,消费成功了才提交,但是失败一直重试的话也不可取,后面的消息被阻塞了,具体看业务怎么处理。
- 异步提交:也是提交当前偏移量的一种,每次都提交, 提交给broker之后里面返回, 不关心broker是否成功存储本次提交的偏移量。性能和可靠性介于前两者之间,
- 同步异步组合:消费者关闭前使用。
- 还可以提交指定偏移量。
不管哪种模式, 提交消息的时候, 如果最新提交的消息的偏移量大于实际消费的消息的偏移量,会导致漏消费。 如果最新提交的偏移量小于实际消费的消息的偏移量,会导致重复消费。还有就是kafka没办法记录消息是消费成功了还是失败了, 只能通过一个游标来记录消息有没有被消费过!!! 这是导致kafka可靠性不高的原因。
整体来说kafka是一个性能非常好的数据库,提供的功能和架构也比较简单。他发送消息的确认机制和消费消息的记录机制决定了他为了追求性能在数据可靠性方面有所舍弃,适用与追求高性能、高吞吐,但是对数据可靠性要求不是很高的场景, 比如日志等。
RabbitMQ篇
RabbitMQ实现了AMQP协议, 这个主要定义他的工作流程以及各种角色(AMQP 0.9.1 模型解析 · RabbitMQ in Chinese)
RabbitMQ直接和通过使用插件支持多种消息传递协议。
1、生产者怎么上传数据?涉及到通信协议、消息路由、消息编码、怎么界定上传成功还是失败(做的不好会丢失消息)?
2、消息队列服务怎么保存数据?存到内存还是存到磁盘?存多久?怎么保证高效可靠?怎么横向扩展?怎么查询?
3、消费者怎么知道有消息需要消费(定时拉取还是等待推送?)、怎么知道哪些消息是给自己的?怎么记录消费到了哪个地方(做得不好会导致漏消费或者重复消费)?
生产者发送消息流程:
(1) 生产者连接到RabbitMQ Broker , 建立一个连接( Connection) ,开启一个信道(Channel)
(2) 生产者声明一个交换器,并设置相关属性,比如交换机类型、是否持久化等
(3) 生产者声明一个队列井设置相关属性,比如是否排他、是否持久化、是否自动删除等
( 4 ) 生产者通过路由键将交换器和队列绑定起来
( 5 ) 生产者发送消息至RabbitMQ Broker,其中包含路由键、交换器等信息
(6) 相应的交换器根据接收到的路由键查找相匹配的队列。
( 7 ) 如果找到,则将从生产者发送过来的消息存入相应的队列中。
(8) 如果没有找到,则根据生产者配置的属性选择丢弃还是回退给生产者
(9) 关闭信道。
(1 0) 关闭连接。
怎么界定成功还是失败:
https://www.cnblogs.com/mfrank/p/11380102.html
默认情况下,生产者发送消息给消息队列服务后消息队列服务是不会返回任何信息给生产者的。Rabbitmq提供以下两种方式来界定成功还是失败。
1、使用事务机制来让生产者感知消息被成功投递到服务器。只有消息成功被broker接受,事务提交才能成功,否则我们便可以在捕获异常进行事务回滚操作同时进行消息重发,这个是一个阻塞的过程,但是使用事务机制的话会降低RabbitMQ的性能。
2、通过生产者确认机制实现(消息队列服务给生产者一个回调)。消息发送成功或失败后由生产者自己处理。
消费者
消费者接收消息的过程:
(1)消费者连接到RabbitMQ Broker ,建立一个连接(Connection ) ,开启一个信道(Channel) 。
(2) 消费者向RabbitMQ Broker 请求消费相应队列中的消息,可能会设置相应的回调函数,
以及做一些准备工作
(3)等待RabbitMQ Broker 回应并投递相应队列中的消息, 消费者接收消息。
(4) 消费者确认( ack) 接收到的消息。
(5) RabbitMQ 从队列中删除相应己经被确认的消息。
(6) 关闭信道。
(7) 关闭连接。
消费者绑定指定队列, 队列中有消息之后会被推送给消费者,消费者收到消息后会对这个消息进行一个ack, 消息被确认之后会被【删除】!!
如何保证只被消费一次?
RabbitMQ不会为未ack的消息设置超时时间,它判断此消息是否需要重新投递给消费者的唯一依据是消费该消息的消费者连接是否已经断开。一条消息被获取之后,未ack且获取他的消费者未断开连接之前,这条消息不会被其他消费者消费。如果消费者处理完业务逻辑当是确认消息之前连接断开了, 消息会被其他消费者重新获取,也会造成重复消费。
如果设置为自动确认,确认后业务逻辑处理不了也会导致消息丢失。
rabbitmq消费者怎么判断消息是否被消费过?提供ack来修改消息的状态?还是说没有被删除的都是没被消费过的??
【注意】rabbitmq的数据是存在内存里面的,你也可以设置让他持久化到磁盘。