rabbitmq知识点总结

AMQP

是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。目标是实现一种在全行业广泛使用的标准消息中间件技术,以便降低企业和系统集成的开销,并且向大众提供工业级的集成服务。主要实现有 RabbitMQ。

生产者、消费者、消息

	生产者:消息的创建者,发送到rabbitmq;
	消费者:连接到rabbitmq,订阅到队列上,消费消息,持续订阅(basicConsumer)和单条订阅(basicGet).
	消息:包含有效载荷和标签,有效载荷指要传输的数据,,标签描述了有效载荷,并且rabbitmq用它来决定谁获得消息,消费者只能拿到有效载荷,并不知道生产者是谁。

信道

信道,概念:信道是生产消费者与rabbit通信的渠道,生产者publish或是消费者subscribe一个队列都是通过信道来通信的。信道是建立在TCP连接上的虚拟连接,什么意思呢?就是说rabbitmq在一条TCP上建立成百上千个信道来达到多个线程处理,这个TCP被多个线程共享,每个线程对应一个信道,信道在rabbit都有唯一的ID ,保证了信道私有性,对应上唯一的线程使用。

	疑问:为什么不建立多个TCP连接呢?原因是rabbit保证性能,系统为每个线程开辟一个TCP是非常消耗性能,每秒成百上千的建立销毁TCP会严重消耗系统。所以rabbitmq选择建立多个信道(建立在tcp的虚拟连接)连接到rabbit上。

交换器、队列、绑定、路由键

队列通过路由键(routing key,某种确定的规则)绑定到交换器,生产者将消息发布到交换器,交换器根据绑定的路由键将消息路由到特定队列,然后由订阅这个队列的消费者进行接收。

常见问题

	如果消息达到无人订阅的队列会怎么办?
		消息会一直在队列中等待,RabbitMq默认队列是无限长度的。
	多个消费者订阅到同一队列怎么办?
		消息以循环的方式发送给消费者,每个消息只会发送给一个消费者。
	消息路由到了不存在的队列怎么办?
		一般情况下,凉拌,RabbitMq会忽略,当这个消息不存在,也就是这消息丢了。

消息的确认

	消费者收到的每一条消息都必须进行确认(自动确认和自行确认)。
	消费者在声明队列时,可以指定autoAck参数,当autoAck=false时,RabbitMQ会等待消费者显式发回ack信号后才从内存(和磁盘,如果是持久化消息的话)中移去消息。否则,RabbitMQ会在队列中消息被消费后立即删除它。
	采用消息确认机制后,只要令autoAck=false,消费者就有足够的时间处理消息(任务),不用担心处理消息过程中消费者进程挂掉后消息丢失的问题,因为RabbitMQ会一直持有消息直到消费者显式调用basicAck为止。
	当autoAck=false时,对于RabbitMQ服务器端而言,队列中的消息分成了两部分:一部分是等待投递给消费者的消息;一部分是已经投递给消费者,但是还没有收到消费者ack信号的消息。如果服务器端一直没有收到消费者的ack信号,并且消费此消息的消费者已经断开连接,则服务器端会安排该消息重新进入队列,等待投递给下一个消费者(也可能还是原来的那个消费者)。
	RabbitMQ不会为未ack的消息设置超时时间,它判断此消息是否需要重新投递给消费者的唯一依据是消费该消息的消费者连接是否已经断开。这么设计的原因是RabbitMQ允许消费者消费一条消息的时间可以很久很久。

交换器类型

	共有四种direct,fanout,topic,headers,其种headers(几乎和direct一样)不实用,可以忽略。
		Direct:
			路由键完全匹配,消息被投递到对应的队列,每个amqp的实现都必须有一个direct交换器,包含一个空白字符串名称的默认交换器。声明一个队列时,会自动绑定到默认交换器,并且以队列名称作为路由键:channel->basic_public($msg,’ ’,’queue-name’)

		Fanout:
		消息广播到绑定的队列
		
		Topic:
			通过使用“*”和“#”,使来自不同源头的消息到达同一个队列,”.”将路由键分为了几个标识符,“*”匹配1个,“#”匹配一个或多个。例如日志处理:user.#(匹配user开头的所有的),user.*.team

虚拟主机

虚拟消息服务器,vhost,本质上就是一个mini版的mq服务器,有自己的队列、交换器和绑定,最重要的,自己的权限机制。Vhost提供了逻辑上的分离,可以将众多客户端进行区分,又可以避免队列和交换器的命名冲突。Vhost必须在连接时指定,rabbitmq包含缺省vhost:“/”,通过缺省用户和口令guest进行访问。
rabbitmq里创建用户,必须要被指派给至少一个vhost,并且只能访问被指派内的队列、交换器和绑定。Vhost必须通过rabbitmq的管理控制工具创建。

消息发布时的权衡

	失败确认:
		在发送消息时设置mandatory标志,告诉RabbitMQ,如果消息不可路由,应该将消息返回给发送者,并通知失败。可以这样认为,开启mandatory是开启故障检测模式。
		注意:它只会让RabbitMQ向你通知失败,而不会通知成功。如果消息正确路由到队列,则发布者不会受到任何通知。带来的问题是无法确保发布消息一定是成功的,因为通知失败的消息可能会丢失。

	监听器:
		在信道关闭和连接关闭时,还有两个监听器可以使用

	事务:
		事务的实现主要是对信道(Channel)的设置,主要的方法有三个:
			1.channel.txSelect()声明启动事务模式;
			2.channel.txComment()提交事务;
			3.channel.txRollback()回滚事务;
		但是事务在rabbitmq中效率很低,所以不建议使用,可以用失败确认和发送方确认模式代替。

	发送方确认模式:
		基于事务的性能问题,RabbitMQ团队为我们拿出了更好的方案,即采用发送方确认模式,该模式比事务更轻量,性能影响几乎可以忽略不计。
		原理:生产者将信道设置成confirm模式,一旦信道进入confirm模式,所有在该信道上面发布的消息都将会被指派一个唯一的ID(从1开始),由这个id在生产者和RabbitMQ之间进行消息的确认。不可路由的消息,当交换器发现,消息不能路由到任何队列,会进行确认操作,表示收到了消息。如果发送方设置了mandatory模式,则会先调用addReturnListener监听器。
		可路由的消息,要等到消息被投递到所有匹配的队列之后,broker会发送一个确认给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么确认消息会在将消息写入磁盘之后发出,broker回传给生产者的确认消息中delivery-tag域包含了确认消息的序列号。confirm模式最大的好处在于他可以是异步的,一旦发布一条消息,生产者应用程序就可以在等信道返回确认的同时继续发送下一条消息,当消息最终得到确认之后,生产者应用便可以通过回调方法来处理该确认消息,如果RabbitMQ因为自身内部错误导致消息丢失,就会发送一条nack消息,生产者应用程序同样可以在回调方法中处理该nack消息决定下一步的处理。
		Confirm的三种实现方式:
			方式一:channel.waitForConfirms()普通发送方确认模式;消息到达交换器,就会返回true。
			方式二:channel.waitForConfirmsOrDie()批量确认模式;使用同步方式等所有的消息发送之后才会执行后面代码,只要有一个消息未到达交换器就会抛出IOException异常。
			方式三:channel.addConfirmListener()异步监听发送方确认模式;
			
	备用交换器:
		在第一次声明交换器时被指定,用来提供一种预先存在的交换器,如果主交换器无法路由消息,那么消息将被路由到这个新的备用交换器。
		如果发布消息时同时设置了mandatory会发生什么?如果主交换器无法路由消息,RabbitMQ并不会通知发布者,因为,向备用交换器发送消息,表示消息已经被路由了。注意,新的备用交换器就是普通的交换器,没有任何特殊的地方。
		使用备用交换器,向往常一样,声明Queue和备用交换器,把Queue绑定到备用交换器上。然后在声明主交换器时,通过交换器的参数,alternate-exchange,,将备用交换器设置给主交换器。
		建议备用交换器设置为faout类型,Queue绑定时的路由键设置为“#”

消息的获得方式

	拉取Get:
		属于一种轮询模型,发送一次get请求,获得一个消息。如果此时RabbitMQ中没有消息,会获得一个表示空的回复。总的来说,这种方式性能比较差,很明显,每获得一条消息,都要和RabbitMQ进行网络通信发出请求。而且对RabbitMQ来说,RabbitMQ无法进行任何优化,因为它永远不知道应用程序何时会发出请求。具体使用,参见代码no-spring模块包cn.enjoyedu.GetMessage中。对我们实现者来说,要在一个循环里,不断去服务器get消息。

	推送Consume:
		属于一种推送模型。注册一个消费者后,RabbitMQ会在消息可用时,自动将消息进行推送给消费者。这种模式我们已经使用过很多次了,具体使用,参见代码no-spring模块包cn.enjoyedu.exchange.direct中。

	消息的应答:
		消费者收到的每一条消息都必须进行确认。消息确认后,RabbitMQ才会从队列删除这条消息,RabbitMQ不会为未确认的消息设置超时时间,它判断此消息是否需要重新投递给消费者的唯一依据是消费该消息的消费者连接是否已经断开。这么设计的原因是RabbitMQ允许消费者消费一条消息的时间可以很久很久。

	自动确认:
		消费者在声明队列时,可以指定autoAck参数,当autoAck=true时,一旦消费者接收到了消息,就视为自动确认了消息。如果消费者在处理消息的过程中,出了错,就没有什么办法重新处理这条消息,所以我们很多时候,需要在消息处理成功后,再确认消息,这就需要手动确认。

	自行手动确认:
		当autoAck=false时,RabbitMQ会等待消费者显式发回ack信号后才从内存(和磁盘,如果是持久化消息的话)中移去消息。否则,RabbitMQ会在队列中消息被消费后立即删除它。
		采用消息确认机制后,只要令autoAck=false,消费者就有足够的时间处理消息(任务),不用担心处理消息过程中消费者进程挂掉后消息丢失的问题,因为RabbitMQ会一直持有消息直到消费者显式调用basicAck为止。当autoAck=false时,对于RabbitMQ服务器端而言,队列中的消息分成了两部分:一部分是等待投递给消费者的消息;一部分是已经投递给消费者,但是还没有收到消费者ack信号的消息。如果服务器端一直没有收到消费者的ack信号,并且消费此消息的消费者已经断开连接,则服务器端会安排该消息重新进入队列,等待投递给下一个消费者(也可能还是原来的那个消费者)。
		例如,启动两个消费者A、B,都可以收到消息,但是其中有一个消费者A不会对消息进行确认,当把这个消费者A关闭后,消费者B又会收到本来发送给消费者A的消息。所以我们一般使用手动确认的方法是,将消息的处理放在try、catch语句块中,成功处理了,就给RabbitMQ一个确认应答,如果处理异常了,就在catch中,进行消息的拒绝,如何拒绝,参考《消息的拒绝》章节。

	QoS预取模式:
		在确认消息被接收之前,消费者可以预先要求接收一定数量的消息,在处理完一定数量的消息后,批量进行确认。如果消费者应用程序在确认消息之前崩溃,则所有未确认的消息将被重新发送给其他消费者。所以这里存在着一定程度上的可靠性风险。这种机制一方面可以实现限速(将消息暂存到RabbitMQ内存中)的作用,一方面可以保证消息确认质量(比如确认了但是处理有异常的情况)。
		注意:消费确认模式必须是非自动ACK机制(这个是使用baseQos的前提条件,否则会Qos不生效),然后设置basicQos的值;另外,还可以基于consume和channel的粒度进行设置(global)。我们可以进行批量确认,也可以进行单条确认。

		basicQos方法参数详细解释:
			prefetchSize:最多传输的内容的大小的限制,0为不限制,但据说prefetchSize参数,rabbitmq没有实现。
			prefetchCount:会告诉RabbitMQ不要同时给一个消费者推送多于N个消息,即一旦有N个消息还没有ack,则该consumer将block掉,直到有消息ack
			global:true\false 是否将上面设置应用于channel,简单点说,就是上面限制是channel级别的还是consumer级别。
			如果同时设置channel和消费者,会怎么样?AMQP规范没有解释如果使用不同的全局值多次调用basic.qos会发生什么。 RabbitMQ将此解释为意味着两个预取限制应该彼此独立地强制执行; 消费者只有在未达到未确认消息限制时才会收到新消息。
			channel.basicQos(10, false); // Per consumer limit
			channel.basicQos(15, true);  // Per channel limit
			channel.basicConsume("my-queue1", false, consumer1);
			channel.basicConsume("my-queue2", false, consumer2);
			也就是说,整个通道加起来最多允许15条未确认的消息,每个消费者则最多有10条消息。

消费者中的事务

		使用方法和生产者一致
		假设消费者模式中使用了事务,并且在消息确认之后进行了事务回滚,会是什么样的结果?
		结果分为两种情况:
		1.autoAck=false手动应对的时候是支持事务的,也就是说即使你已经手动确认了消息已经收到了,但RabbitMQ对消息的确认会等事务的返回结果,再做最终决定是确认消息还是重新放回队列,如果你手动确认之后,又回滚了事务,那么以事务回滚为准,此条消息会重新放回队列;
		2.autoAck=true如果自动确认为true的情况是不支持事务的,也就是说你即使在收到消息之后在回滚事务也是于事无补的,队列已经把消息移除了。

消息的拒绝

Reject和Nack
消息确认可以让RabbitMQ知道消费者已经接受并处理完消息。但是如果消息本身或者消息的处理过程出现问题怎么办?需要一种机制,通知RabbitMQ,这个消息,我无法处理,请让别的消费者处理。这里就有两种机制,Reject和Nack。
Reject在拒绝消息时,可以使用requeue标识,告诉RabbitMQ是否需要重新发送给别的消费者。不重新发送,一般这个消息就会被RabbitMQ丢弃。Reject一次只能拒绝一条消息。
Nack则可以一次性拒绝多个消息。这是RabbitMQ对AMQP规范的一个扩展。

死信交换器DLX

		RabbitMQ对AMQP规范的一个扩展。被投递消息被拒绝后的一个可选行为,往往用在对问题消息的诊断上。
		消息变成死信一般是以下几种情况:
			1,消息被拒绝,并且设置 requeue 参数为 false
			2,消息过期
			3,队列达到最大长度
		死信交换器仍然只是一个普通的交换器,创建时并没有特别要求和操作。在创建队列的时候,声明该交换器将用作保存被拒绝的消息即可,相关的参数是x-dead-letter-exchange。

死信交换器和备用交换器的区别

1、备用交换器是主交换器无法路由消息,那么消息将被路由到这个新的备用交换器,而死信交换器则是接收过期或者被拒绝的消息。
2、备用交换器是在声明主交换器时发生联系,而死信交换器则声明队列时发生联系。

队列

		1,临时队列
			分为:自动删除队列,单消费者队列,自动过期队列

		2,永久队列
			持久化队列和非持久化队列的区别是,持久化队列会被保存在磁盘中,固定并持久的存储,当Rabbit服务重启后,该队列会保持原来的状态在RabbitMQ中被管理,而非持久化队列不会被保存在磁盘中,Rabbit服务重启后队列就会消失。
			非持久化比持久化的优势就是,由于非持久化不需要保存在磁盘中,所以使用速度就比持久化队列快。即是非持久化的性能要高于持久化。而持久化的优点就是会一直存在,不会随服务的重启或服务器的宕机而消失。
			在声明队列时,将属性durable设置为“false”,则该队列为非持久化队列,设置成“true”时,该队列就为持久化队列

		3,队列级别消息过期
			就是为每个队列设置消息的超时时间。只要给队列设置x-message-ttl参数,就设定了该队列所有消息的存活时间,时间单位是毫秒。如果声明队列时指定了死信交换器,则过期消息会成为死信消息。

		4,消息存活时间
			当队列消息的TTL 和消息TTL都被设置,时间短的TTL设置生效。如果将一个过期消息发送给RabbitMQ,该消息不会路由到任何队列,而是直接丢弃。
			为消息设置TTL有一个问题:RabbitMQ只对处于队头的消息判断是否过期(即不会扫描队列),所以,很可能队列中已存在死消息,但是队列并不知情。这会影响队列统计数据的正确性,妨碍队列及时释放资源。

		5,消息的持久化
			默认情况下,队列和交换器在服务器重启后都会消失,消息当然也是。将队列和交换器的durable属性设为true,缺省为false,但是消息要持久化还不够,还需要将消息在发布前,将投递模式设置为2。消息要持久化,必须要有持久化的队列、交换器和投递模式都为2。

RabbitMQ內建集群

		內建集群的设计目标:
			1、允许消费者和生产者在节点崩溃的情况下继续运行;2、通过添加节点线性扩展消息通信的吞吐量。

		可以保证消息的万无一失吗?
			不行,当一个节点崩溃时,该节点上队列的消息也会消失,rabbitmq默认不会将队列的消息复制到整个集群上。

		集群中的队列和交换器:
			队列:
				集群中队列信息只在队列的所有者节点保存队列的所有信息,其他节点只知道队列的元数据和指向所有者节点的指针,节点崩溃时,该节点的队列和其上的绑定信息都消失了。
				为什么集群不复制队列内容和状态到所有节点:1)存储空间;2)性能,如果消息需要复制到集群中每个节点,网络开销不可避免,持久化消息还需要写磁盘。
				所以其他节点接收到不属于该节点的队列的消息时会将该消息传递给该队列的所有者节点上。
			
			交换器:
				本质上是个这个交换器的名称和队列的绑定列表,可以看成一个类似于hashmap的映射表,所以交换器会在整个集群上复制。
			
			元数据:
				队列元数据:队列名称和属性(是否可持久化,是否自动删除)
			
			交换器元数据:
				交换器名称、类型和属性
			
			绑定元数据:
				交换器和队列的绑定列表
			
			vhost元数据:
				vhost内的相关属性,如安全属性等等
			
			集群中的节点:要么是内存节点,要么是磁盘节点。怎么区分?就是节点将队列、交换器、用户等等信息保存在哪里?单节点肯定是磁盘类型。集群中可以有内存节点,为了性能的考虑,全部是磁盘节点,当声明队列、交换器等等时,rabbitmq必须将数据保存在所有节点后才能表示操作完成。Rabbitmq只要求集群中至少有一个磁盘节点,从高可用的角度讲每个集群应该至少配备两个磁盘节点。因为只有一个磁盘节点的情况下,当这个磁盘节点崩溃时,集群可以保持运行,但任何修改操作,比如创建队列、交换器、添加和删除集群节点都无法进行。

管理RabbitMQ

		管理虚拟主机:
			rabbitmqctl add_vhost [vhost_name] 
			rabbitmqctl list_vhosts 

		用户管理:
			rabbitmqctl add_user [username] [pwd]
			rabbitmqctl delete_user [username]
			rabbitmqctl  change_password  Username  Newpassword
			rabbitmqctl  list_users

		用户权限控制:
			guest是默认用户,具有默认virtual host "/"上的全部权限,仅能通过localhost访问RabbitMQ包括Plugin,建议删除或更改密码。可通过将配置文件中loopback_users来取消其本地访问的限制:[{rabbit, [{loopback_users, []}]}]
			如用户Mark在虚拟主机logHost上的所有权限: 
				rabbitmqctl set_permissions –p logHost Mark  '.*'  '.*'  '.*' 

		RabbitMQ的用户角色分类:
			none、management、policymaker、monitoring、administrator

		查看队列:
			rabbitmqctl list_queues

		查看交换器:
			rabbitmqctl list_exchanges

		查看绑定:
			rabbitmqctl list_bindings 

猜你喜欢

转载自blog.csdn.net/qq_28822933/article/details/85385947