RabbitMQ 之消息确认机制(事务+Confirm)

https://blog.csdn.net/u013256816/article/details/55515234

概述:

在 Rabbitmq 中我们可以通过持久化来解决因为服务器异常而导致丢失的问题
 
除此之外我们还会遇到一个问题:生产者将消息发送出去之后,消息到底有没有正
确到达 Rabbit 服务器呢?如果不错得数处理,我们是不知道的,(即 Rabbit 服务器
不会反馈任何消息给生产者),也就是默认的情况下是不知道消息有没有正确到达;
导致的问题:消息到达服务器之前丢失,那么持久化也不能解决此问题,因为消息根本就没有到达 Rabbit 服务器!
RabbitMQ 为我们提供了两种方式:
1. 通过 AMQP 事务机制实现,这也是 AMQP 协议层面提供的解决方案;
2. 通过将 channel 设置成 confirm 模式来实现
事务机制                                                                                                                                                                          RabbitMQ 中与事务机制有关的方法有三个:txSelect(), txCommit()以及 txRollback(), 
txSelect 用于将当前 channel 设置成 transaction 模式,txCommit 用于提交事务,
txRollback 用于回滚事务,在通过 txSelect 开启事务之后,我们便可以发布消息
给 broker 代理服务器了,如果 txCommit 提交成功了,则消息一定到达了 broker 了,
如果在 txCommit执行之前 broker 异常崩溃或者由于其他原因抛出异常,这个时候
我们便可以捕获异常通过 txRollback 回滚事务了。                                                                                                                                 
txSelect   txCommit  txRollback                                                                                                                                txSelect:用户将当前channel设置成transation模式                                                                                                          txCommit :用于提交事务
txRollback :用户回滚事务

生产者:

  1. import java.io.IOException;
  2. import java.util.concurrent.TimeoutException;
  3. import com.rabbitmq.client.Channel;
  4. import com.rabbitmq.client.Connection;
  5. import com.rabbitmq.util.ConnectionUtils;
  6. public class TXsend {
  7. private static final String QUEUE_NAMW = "test_tx_queue";
  8.  
  9. public static void main(String[] args) throws IOException, TimeoutException {
  10. Connection conn = ConnectionUtils.getConnection();
  11. Channel channel = conn.createChannel();
  12.  
  13. channel.queueDeclare(QUEUE_NAMW, false, false, false, null);
  14.  
  15. String msg = "tx";
  16.  
  17. try {
  18. //开启事务模式、
  19. channel.txSelect();
  20. channel.basicPublish("", QUEUE_NAMW, null, msg.getBytes());
  21. //模拟事故
  22. int i = /;
  23. //提交
  24. channel.txCommit();
  25. } catch (Exception e) {
              //进行事务回滚
  26. channel.txRollback();
  27. System.out.println("TxRollback...");
  28. }
  29. channel.close();
  30. conn.close();
  31. }
  32. }

消费者:

  1. import java.io.IOException;
  2. import java.util.concurrent.TimeoutException;
  3. import com.rabbitmq.client.Channel;
  4. import com.rabbitmq.client.Connection;
  5. import com.rabbitmq.client.Consumer;
  6. import com.rabbitmq.client.DefaultConsumer;
  7. import com.rabbitmq.client.Envelope;
  8. import com.rabbitmq.client.AMQP.BasicProperties;
  9. import com.rabbitmq.util.ConnectionUtils;
  10. public class TxReceive {
  11.  
  12. private static final String QUEUE_NAMW = "test_tx_queue";
  13. public static void main(String[] args) throws IOException, TimeoutException {
  14.  
  15. Connection conn = ConnectionUtils.getConnection();
  16.  
  17. Channel channel = conn.createChannel();
  18.  
  19. //队列声明
  20. channel.queueDeclare(QUEUE_NAMW, false, false, false, null);
  21.  
  22. channel.basicQos();
  23.  
  24. //绑定队列到交换机转发器
  25.  
  26. //channel.queueBind(QUEUE_NAMW, "", "");
  27.  
  28. //定义一个消费者
  29. Consumer consumer = new DefaultConsumer(channel){
  30. //收到消息就会触发这个方法
  31. @Override
  32. public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
  33. throws IOException {
  34. String msg = new String(body,"utf-8");
  35. System.out.println("消费者1接收到的消息" + msg);
  36.  
  37. try {
  38. Thread.sleep();
  39. } catch (InterruptedException e) {
  40. e.printStackTrace();
  41. }finally{
  42. System.out.println("消费者1处理完成!");
  43. //手动回执
  44. channel.basicAck(envelope.getDeliveryTag(), false);
  45. }
  46. }
  47. };
  48. //监听队列
  49. //自动应答false
  50. boolean autoAck = false;
  51. channel.basicConsume(QUEUE_NAMW, autoAck, consumer);
  52. }
  53. }

此时消费者不会接收到消息

此种模式还是很耗时的,采用这种方式 降低了 Rabbitmq 的消息吞吐量

Confirm模式

概述

上面我们介绍了 RabbitMQ 可能会遇到的一个问题,即生成者不知道消息是否真正到达 broker,随
后通过 AMQP 协议层面为我们提供了事务机制解决了这个问题,但是采用事务机制实现会降低
RabbitMQ 的消息吞吐量,那么有没有更加高效的解决方式呢?答案是采用 Confirm 模式。 

producer 端 confirm 模式的实现原理

   该模式最大的好处就是异步的!!!     

开启 confirm 模式的方法                                                                                                                     已经在 transaction 事务模式的 channel 是不能再设置成 confirm 模式的,即这两种模式是不能共存的。                生产者通过调用 channel 的 confirmSelect 方法将 channel 设置为 confirm 模式                                                            核心代码:

//生产者通过调用channel的confirmSelect方法将channel设置为confirm模式
channel.confirmSelect();     

编程模式

1. 普通 confirm 模式:每发送一条消息后,调用 waitForConfirms()方法,等待服务器端
    confirm。实际上是一种串行 confirm 了。
2. 批量 confirm 模式:每发送一批消息后,调用 waitForConfirms()方法,等待服务器端
    confirm。
3. 异步 confirm 模式:提供一个回调方法,服务端 confirm 了一条或者多条消息后 Client 端会回
    调这个方法。

普通模式:

  1. import java.io.IOException;
  2. import java.util.concurrent.TimeoutException;
  3. import com.rabbitmq.client.Channel;
  4. import com.rabbitmq.client.Connection;
  5. import com.rabbitmq.util.ConnectionUtils;
  6. public class confirm{
  7. private static final String QUEUE_NAMW = "test_tx_confirm1";
  8.  
  9. public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
  10. Connection conn = ConnectionUtils.getConnection();
  11. Channel channel = conn.createChannel();
  12.  
  13. channel.queueDeclare(QUEUE_NAMW, false, false, false, null);
  14.  
  15. //生产者调用confirmSelect,将channel设置为confirm模式
  16. channel.confirmSelect();
  17. String msg = "confirm";
  18. channel.basicPublish("", QUEUE_NAMW, null, msg.getBytes());
  19. if(!channel.waitForConfirms()){
  20. System.out.println("send failed");
  21. }else{
  22. System.out.println("send ok");
  23. }
  24. channel.close();
  25. conn.close();
  26. }
  27. }

批量模式
批量 confirm 模式稍微复杂一点,客户端程序需要定期(每隔多少秒)
或者定量(达到多少条)或者两则结合起来publish 消息,然后等待
服务器端 confirm, 相比普通 confirm 模式,批量极大提升 confirm
 效率,但是问题在于一旦出现 confirm 返回 false 或者超时的情
况时,客户端需要将这一批次的消息全部重发,这会带来明显的重复消息数
量,并且,当消息经常丢失时,批量 confirm 性能应该是不升反降的。

import java.io.IOException;
import java.util.concurrent.TimeoutException;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.util.ConnectionUtils;
public class TXsend {
private static final String QUEUE_NAMW = "test_tx_confirm1";

public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
      Connection conn = ConnectionUtils.getConnection();
      Channel channel = conn.createChannel();

      channel.queueDeclare(QUEUE_NAMW, false, false, false, null);

      //1
      //生产者调用confirmSelect,将channel设置为confirm模式
      channel.confirmSelect();

      //2
      String msg = "confirm";
      //批量发送
      for(int i=1;i<=10;i++){
        channel.basicPublish("", QUEUE_NAMW, null, msg.getBytes());
      }

      //3
      //确认
      if(!channel.waitForConfirms()){
        System.out.println("send failed");
      }else{
        System.out.println("send ok");
      }

      channel.close();
      conn.close();
}
}

异步模式
Channel 对象提供的 ConfirmListener()回调方法只包含 deliveryTag
(当前 Chanel 发出的消息序号),我们需要自己为每一个 Channel 
维护一个 unconfirm 的消息序号集合,每 publish 一条数据,集合中
元素加 1,每回调一次 handleAck方法,unconfirm 集合删掉相应的
一条(multiple=false)或多条(multiple=true)记录。从程序运行
效率上看,这个unconfirm 集合最好采用有序集合 SortedSet 存储结构。
实际上,SDK 中的 waitForConfirms()方法也是通过 SortedSet维护消息序号的。
  1. import java.io.IOException;
  2. import java.util.Collection;
  3. import java.util.Collections;
  4. import java.util.SortedSet;
  5. import java.util.TreeSet;
  6. import java.util.concurrent.TimeoutException;
  7. import com.rabbitmq.client.Channel;
  8. import com.rabbitmq.client.ConfirmListener;
  9. import com.rabbitmq.client.Connection;
  10. import com.rabbitmq.util.ConnectionUtils;
  11. public class TXsend {
  12. private static final String QUEUE_NAMW = "test_tx_confirm3";
  13.  
  14. public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
  15. Connection conn = ConnectionUtils.getConnection();
  16. Channel channel = conn.createChannel();
  17.  
  18. channel.queueDeclare(QUEUE_NAMW, false, false, false, null);
  19.  
  20. //生产者调用confirmSelect,将channel设置为confirm模式
  21. channel.confirmSelect();
  22. //未确认的消息标识
  23. final SortedSet<Long> confirmSet = Collections.synchronizedSortedSet(new TreeSet<Long>());
  24. //频道加一个监听
  25. channel.addConfirmListener(new ConfirmListener() {
  26. //回调/重发重试 可以1s之后再发 10s之后再发
  27. @Override
  28. public void handleNack(long deliveryTag, boolean multiple) throws IOException {
  29. if(multiple){
  30. System.out.println("handleNack-----multiple =1");
  31. confirmSet.headSet(deliveryTag+1).clear();;
  32. }else{
  33. System.out.println("handleNack-----multiple =0");
  34. confirmSet.remove(deliveryTag);
  35. }
  36. }
  37. //没问题的handleAck
  38. @Override
  39. public void handleAck(long deliveryTag, boolean multiple) throws IOException {
  40. if(multiple){
  41. System.out.println("handleAck-----multiple =1");
  42. confirmSet.headSet(deliveryTag+1).clear();;
  43. }else{
  44. System.out.println("handleAck-----multiple =0");
  45. confirmSet.remove(deliveryTag);
  46. }
  47. }
  48. });
  49.  
  50. String msg = "confirm";
  51. //模拟插入数据
  52. while(true){
  53. long seqNo = channel.getNextPublishSeqNo();
  54. channel.basicPublish("", QUEUE_NAMW, null, msg.getBytes());
  55. confirmSet.add(seqNo);
  56. }
  57. }
  58. }

8、RabbitMQ-消息的确认机制(生产者)的更多相关文章

  1. (六)RabbitMQ消息队列-消息任务分发与消息ACK确认机制(PHP版)

    原文:(六)RabbitMQ消息队列-消息任务分发与消息ACK确认机制(PHP版) 在前面一章介绍了在PHP中如何使用RabbitMQ,至此入门的的部分就完成了,我们内心中一定还有很多疑问:如果多个消 ...

  2. RabbitMQ消息队列(六)-消息任务分发与消息ACK确认机制(.Net Core版)

    在前面一章介绍了在.Net Core中如何使用RabbitMQ,至此入门的的部分就完成了,我们内心中一定还有很多疑问:如果多个消费者消费同一个队列怎么办?如果这几个消费者分任务的权重不同怎么办?怎么把 ...

  3. JStorm源代码阅读——消息的确认机制

    Acker //Acker相当于一个bolt,用于处理事件 public class Acker implements IBolt { private RotatingMap<Object, A ...

  4. RabbitMQ消息发布和消费的确认机制

    前言 新公司项目使用的消息队列是RabbitMQ,之前其实没有在实际项目上用过RabbitMQ,所以对它的了解都谈不上入门.趁着周末休息的时间也猛补习了一波,写了两个窗体应用,一个消息发布端和消息消费 ...

  5. 十五、.net core(.NET 6)搭建RabbitMQ消息队列生产者和消费者的简单方法

    搭建RabbitMQ简单通用的直连方法 如果还没有MQ环境,可以参考上一篇的博客,在windows系统上的rabbitmq环境搭建.如果使用docker环境,可以直接百度一下,应该就一个语句就可以搞定 ...

  6. 学习ActiveMQ(六):JMS消息的确认与重发机制

    当我们发送消息的时候,会出现发送失败的情况,此时我们需要用到activemq为我们提供了消息重发机制,进行消息的重新发送.那么我们怎么知道消息有没有发送失败呢?activemq还有消息确认机制,消费者 ...

  7. 【转】RabbitMQ基础——和——持久化机制

    这里原来有一句话,触犯啦天条,被阉割!!!! 首先不去讨论我的日志组件怎么样.因为有些日志需要走网络,有的又不需要走网路,也是有性能与业务场景的多般变化在其中,就把他抛开,我们只谈消息RabbitMQ ...

  8. RabbitMQ消息可靠性分析和应用

    RabbitMQ流程简介(带Exchange) RabbitMQ使用一些机制来保证可靠性,如持久化.消费确认及发布确认等. 先看以下这个图: P为生产者,X为中转站(Exchange),红色部分为消息 ...

  9. JMS确认机制

    JMS中为数不多的重点就是消息的确认机制,下面分别介绍J2EE和Spring的MessageListenerContainer的确认机制 J2EE中JMS确认机制 在JMS规范中一共4种确认方式 AU ...

  10. (转)RabbitMQ消息队列(九):Publisher的消息确认机制

    在前面的文章中提到了queue和consumer之间的消息确认机制:通过设置ack.那么Publisher能不到知道他post的Message有没有到达queue,甚至更近一步,是否被某个Consum ...

随机推荐

  1. Web前端图形滑块检验组件实现

    组件渲染图形: 初始化:                                                                                        ...

  2. java 自定义异常处理

    package com.direct.work; import java.io.FileNotFoundException; import java.io.IOException; import ja ...

  3. cookie、session、分页

    一.cookie HTTP协议是无状态的. 无状态的意思是每次请求都是独立的,它的执行情况和结果与前面的请求和之后的请求都无直接关系,它不会受前面的请求响应情况直接影响,也不会直接影响后面的请求响应情 ...

  4. jq中的isArray方法分析,如何判断对象是否是数组

    <!DOCTYPE html> <html> <head> <title>jq中的isArray方法分析</title> <meta ...

  5. sql-(Cross||Outer)Apply

    Apply - 涉及以下两个步骤中的一步或两步(取决于Apply的类型): 1.A1:把右表表达式应用于左表的行 2.A2:添加外部行 Apply运算符把右表表达式应用于左输入的每一行.右表达式可以引 ...

  6. OpenCV实现基于傅里叶变换的旋转文本校正

    代码 先给出代码,再详细解释一下过程: #include <opencv2/core/core.hpp> #include <opencv2/imgproc/imgproc.hpp& ...

  7. 强化学习系列之:Deep Q Network (DQN)

    文章目录 [隐藏] 1. 强化学习和深度学习结合 2. Deep Q Network (DQN) 算法 3. 后续发展 3.1 Double DQN 3.2 Prioritized Replay 3. ...

  8. unistd.h文件

    转载地址:http://baike.baidu.com/link?url=nEyMMFYevs4yoHgQUs2bcfd5WApHUKx0b1ervi7ulR09YhtqC4txmvL1Ce3FS8x ...

  9. 131.007 Unsupervised Learning - Feature Selection | 非监督学习 - 特征选择

    1 Why? Reason1 Knowledge Discovery (about human beings limitaitons) Reason2 Cause of Dimensionality ...

  10. 解决linux下fflush(stdin)无效

    void clean_stdin(void) { int c; do { c = getchar(); } while (c != '\n' && c != EOF); }