RabbitMQ 工作原理和常用工作模式 --贺兰的博客

    科技2022-07-11  89

    RabbitMQ介绍 :     MQ全称为Message Queue,即消息队列;RabbitMQ由erlang语言开发,基于AMQP协议实现的消息队列; :RabbitMQ的官网 常见的其它消息队列 : ActiveMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ、Redis(也可做消息队列) RabbitMQ优点介绍 : 1.使用简单,功能强大(SpringBoot默认集成) 2.基于AMQP协议 3.社区活跃,文档完善 4.基于erlang语言,高并发性能好 RabbitMQ工作原理 : 组成部分说明 : Producter : 消息生产者,负责将消息发送到MQ Consumer :   消息消费者,接收MQ转发的消息 Connection : 连接通道,包含信道channel Broker : 消息队列服务进程,包含Exchange和Queue Exchange : 消息队列交换机,按一定的规则将消息路由转发到某个队列,对消息进行过滤 Queue : 消息队列;存储消息的队列,消息到达队列并转发给指定的消费方 消息发布流程 : 发布消息 :     1. 生产者和Broker建立TCP连接     2. 生产者和Broker建立通道     3. 生产者通过通道将消息发送给Broker,由Exchange将消息进行转发     4. Exchange将消息转发到指定Queue 接收消息 :     1.消费者和Broker建立TCP连接     2.消费者和Broker建立通道     3.消费者监听指定的Queue     4.当有消息到达Queue时Broker默认将消息推送给消费者。     5.消费者接收到消息。 消息队列的应用场景 : 1.异步处理任务 2.应用程序解耦 3.流量削峰 RabbitMQ的工作模式 : 常用的工作模式 : Work Queue(工作队列模式) Publish/Subscribe(发布订阅模式) Routing(路由模式) Topics(通配符模式) Header(头交换机模式) RPC(远程调用模式)

    Work Queues(工作模式) : 模型图 : 工作队列模式特点 : 1.一条消息只会被一个消费者接收 2.采用轮询的方式将消息发送给消费者 3.消费者在处理完当前消息时,才会收到下一条消息 Publish/Subscribe(发布订阅模式) : 模型图 : 发布订阅模式 : 1.每个消费者监听自己的队列 2. 生产者将消息发送给Broker,由交换机将消息发送到每个绑定此交换机的队列 Work Queues与Publish/Subscribe的区别 :     不同点 :         1.Work Queues不需要定义交换机,而Publish/Subscribe需要定义         2.Publish/Subscribe生产方面向交换机发送消息,Work Queues面向队列(底层使用默认交换机)         3.Publish/Subscribe需要设置队列和交换机的绑定,Work Queues不需要(队列绑定默认交换机)     相同点 :         两者实现的发布/订阅的效果是一样的,多个消费端监听同一个队列不会重复消费消息 Routing(路由模式) : 模型图 : 多重绑定 (一个key绑定多个队列) direct: 特点 : 1.生产者将消息发送到broker,交换机根据routingKey将消息发送到指定队列 2.消费者根据binding关键字(routingkey)监控相应的队列 Routing模式和Publish/Subscribe模式区别 : Routing模式需要在生产者发送消息的时候设置routingKey,交换机会根据routingKey将消息发送到指定的队列 Topics(通配符模式): 模型图 : 特点 : 1.生产者将消息发送到broker,交换机根据routingKey将消息发送到指定队列 2.消费者监控带有通配符routingKey的相应队列(通配符指的是消费者 如 P :log.sms C :log.*) 通配符规则 : 符号“ * ” :只能匹配一个单词; 如 lazy. * 可以匹配lazy.info或者lazy.irs 符号“ # ”: 匹配一个或者多个词lazy.# 可以匹配lazy.irs或者lazy.irs.cor Topic模式的功能更加强大,可是实现Routing、publish/subscirbe模式的功能 Headers(头交换机模式) : 模型图 : 特点 : 生产者将消息发送到broker,交换机根据request message中的header进行匹配,将消息发送到指定队列 匹配规则(x-match) : all : 默认规则;一个传送消息header里的键值对和exchange里的键值对全部匹配才可以路由到对应交换机 any : 一个传送消息header里的键值对和exchange里的键值对有一对匹配,就可以路由到对应交换机 headers模式和topics模式的区别: 1.topic模式的路由值基于routingKey,headers模式的路由值基于消息的header数据 2.topic交换机路由键只有是字符串,headers可以是多种类型 RPC(远程调用过程):感兴趣可以搜其它文章,这里懒不想写了 关于全链路消息不丢失 : 从生产者和消费者两个方面来考虑 生产者 :队列,消息都进行持久化操作         队列持久化:在声明队列的时候,将第二个参数设置完true,则可以完成队列持久化的设置,队列的信息会保存到磁盘中。当rabbitMQ重启,则会恢复之前的存在的队列。

    /** * queue : 当前操作的队列. 设置队列名称即可 * durable: 当前队列是否开启持久化. 如果为true.当前mq服务重启之后,队列仍然存在 * exclusive: 当前队列是否独占此连接 * autoDelete: 当前队列是否自动删除 * arguments: 队列参数 */ channel.queueDeclare(QUEUE,true,false,false,null);

            消息持久化:在生产者发送消息时,可以通过第三个参数设置消息属性,将消息声明为持久化。则可以完成将消息写入磁盘。当rabbitmq重启,在队列恢复的同时也会一并恢复该队列中设置持久化属性且未被消费的消息。

    /** * exchange: 交换机. 对于当前操作使用默认交换机 "" * routingKey: 路由key. 如果当前使用默认交换机, routingKey的值就是当前队列的名称 * props: 参数 * body: 消息体 */ String message = "hello RabbitMQ"; channel.basicPublish("",QUEUE, MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes());

    RabbitMq的数据保护机制 :         针对生产者投递数据丢失,有两种解决机制,事务机制和confirm机制 事务机制 :

    String message = "Hello RabbitMQ"; try { //txSelect():开启事务 channel.txSelect(); for (int i = 0; i < 5; i++) { channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, MessageProperties.PERSISTENT_TEXT_PLAIN, (message + i).getBytes("UTF-8")); } //txCommit():提交事务 channel.txCommit(); } catch (Exception e) { //txRollback():事务回滚 channel.txRollback(); }

    事务会造成性能的急剧下降,不建议使用; confirm机制 : confirm模式需要基于channel进行设置, 一旦某条消息被投递到队列之后,消息队列就会发送一个确认信息给生产者,如果队列与消息是可持久化的, 那么确认消息会等到消息成功写入到磁盘之后发出. confirm的性能高,主要得益于它是异步的.生产者在将第一条消息发出之后等待确认消息的同时也可以继续发送后续的消息.当确认消息到达之后,就可以通过回调方法处理这条确认消息. 如果MQ服务宕机了,则会返回nack消息. 生产者同样在回调方法中进行后续处理.

    public static void main(String[] args)throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setUsername("guest"); factory.setPassword("guest"); factory.setVirtualHost("/"); factory.setHost("localhost"); factory.setPort(5672); Connection conn = factory.newConnection(); Channel channel = conn.createChannel(); String exchangeName = "exchangeName"; String routingKey = "routingKey"; String queueName = "queueName"; channel.exchangeDeclare(exchangeName,"direct",true); channel.queueDeclare(queueName,true,false,false,null); channel.queueBind(queueName,exchangeName,routingKey); byte [] messageBodyBytes = "hello confirm" .getBytes(); //发送之前 //将消息写入到某一个存储空间,用来防止发送消息失败 try{ channel.confirmSelect(); // 开启confirm模式 long start = System.currentTimeMillis(); //设置监听器 channel.addConfirmListener(new ConfirmListener() { public void handleAck(long deliveryTag, boolean multiple) throws IOException { //删除之前临时存储空间中的消息 System.out.println("ack:deliveryTag:"+deliveryTag+",multiple:"+multiple); } public void handleNack(long deliveryTag, boolean multiple) throws IOException { //从临时存储空间中拿出刚才的消息,并重新发送 System.out.println("nack:deliveryTag:"+deliveryTag+",multiple:"+multiple); } }); for(int i = 0;i<100;i++) { //循环发消息 channel.basicPublish(exchangeName, routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, messageBodyBytes); } }catch (Exception e){ e.printStackTrace(); }finally { channel.close(); conn.close(); } }

    消费者 : 1.自动ack 当消费者成功接收到消息之后,可以自动的向rabbitMQ返回一个应答信息,通知rabbitMQ此条消息已经被成功的接收,当rabbitMQ接收到这条消息之后,则会将此条消息删除。这叫做自动ACK(自动应答)

    /** * queue : 队列名称 * autoAck: 是否自动应答 *callback: 消费者 */ channel.basicConsume(QUEUE_INFORM_EMAIL,true,consumer);

    2.手动ack 当消费者接收到消息,不会马上自动向消息队列发送应答消息,而是需要开发人员手动编码发送应答消息, 从而保证消息队列不会自动删除这条消息,而是等到消费者返回ACK确认消息才会进行删除

    DefaultConsumer consumer = new DefaultConsumer(finalChannel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { String value = new String(body,"utf-8"); try{ //仓储服务业务 //调用物流系统 if(调用成功)//通知消息队列删除此消息 finalChannel.basicAck(envelope.getDeliveryTag(),false);else{ //重新调用 } }catch(Exception e){ //特定异常特定处理 } } }; //将第二个参数设置为false,则表明开启手动应答 channel.basicConsume(QUEUE,false,consumer);

    ACK工作原理 : 当定义好一个消费者实例,并且让它监听消息队列之后, 消费者实例就会将自身注册到rabbitMQ.这样可以保证消息队列自身是知道当前有多少消费者实例存在. rabbitMQ会通过自身内部的delivery方法将生产者发送的消息投递到相对应的消费者实例上. 在投递的时候,会给当前的消息加上一个消息的唯一标识(deliveryTag). 换句话说,就是可以通过这个唯一标识确定到具体的某一个消息. 消费者实例会通过connection对象中的channel进行与rabbitMQ的通信. 此时,当消费者实例成功使用完消息之后,就会通过手动ack方式先将这条消息的唯一标识返回到通道, 然后通道再把这条这个标识信息返回给rabbitMQ, 当rabbitMQ接到这个标识信息之后,则可以将相对应的消息进行删除. 这里需要注意一个问题: delivery tag仅仅在一个channel中是唯一的,换句话说,不同的channel中可以存在相同的delivery tag值. 所以在进行收到ack消息的时候, 务必保证接收消息的channel与返回ack消息的channel是同一个.

    Processed: 0.044, SQL: 8