说明:想要理解RabbitMQ,需要先理解MQ是什么?能做什么?然后根据基础知识去理解RabbitMQ是什么、提供了什么功能。

一、MQ的简单理解

1. 什么是MQ?

  • 消息队列(Message Queue),是基础数据结构中 “先进先出” 的一种数据结构。
  • 一般用来解决应用解耦、异步消息、流量削峰等问题,实现高性能、高可用、可伸缩和最终一致性架构。

2.MQ是怎么实现消息传递的?

  1. 生产者产生消息并把传输的数据(消息)放在队列中,用队列机制来实现消息传递。
  2. 消费者可以到指定的队列拉取消息,或者订阅相应的队列,由MQ服务端给其推送消息。

3.MQ的几个主要特性

  • 解耦:一个业务需要多个模块共同实现,或一条消息有多个系统对应处理,只需要在主业务完成以后,发送一条MQ,其余模块消费MQ消息,即可实现业务,降低模块之间的耦合。
  • 异步:主业务执行结束后,从属业务通过MQ异步处理,减少业务的响应时间,提高用户体验。
  • 削峰:高并发情况下,业务异步处理,提供高峰期业务处理能力,避免系统瘫痪。

4.MQ的缺点

  • 系统可用性降低。依赖服务越多,服务越容易挂掉。需要考虑MQ瘫痪的情况。
  • 系统的复杂性提高。需要考虑消息丢失、消息重复消费、消息传递的顺序性。
  • 业务一致性。主业务和从属业务一致性的处理。

二、RabbitMQ的简单介绍

1. 什么是RabbitMQ?

  RabbitMQ是消息代理,它接受并转发消息。

  • RabbitMQ可以理解为一个邮箱,或者一个邮局,或者是一个邮递员,保证 “张三” 的信件最终传递给 “李四”。
  • RabbitMQ与上述所描述的邮局(邮箱、邮递员)的主要区别在于它不处理纸张,而是接受、存储和转发二进制数据块消息。

2.RabbitMQ和消息传递的三个术语

  • 生产:生产只意味着发送。发送消息的程序是生产者(production)。
  • 队列:队列是位于RabbitMQ中的“邮箱”的名称。尽管消息流经RabbitMQ和应用程序,但他们只能存在于队列中。队列只受主机的内存和磁盘限制,它的本质上是一个打的消息缓冲区。许多生产者可以向一个队列发送消息,许多消费者可以尝试从一个队列接收数据。
  • 消费(接收):消费与接收具有相似的含义。一个消费者(consumer)是一个程序,主要是等待接收信息。

注意:生产者、消费者、代理不必部署在同一主机上,应用程序既可以是生产者,又可以是消费者。

三、RabbitMQ安装

3.1环境说明(本文以RabbitMQ3.8.11为例)

RabbitMQ对Erlang版本要求(Rabbit是基于Erlang编写的)

     

RabbitMQ对JDK版本要求

3.2 安装Erlang步骤(本文以windows版安装为例)

3.2.1 下载Erlang,或访问如下链接进行下载:

  http://erlang.org/download/otp_win64_23.2.exe

3.2.2 双击运行 otp_win64_23.2.exe ,点击下一步完成安装。

3.2.3 安装完成后配置环境变量,如下图所示

3.2.4 运行窗口输入cmd,在dos窗口输入 erl ,返回如图中所示,则代表erlang安装完成。

3.2 安装RibbitMQ步骤(本文以windows版安装为例)

3.2.1 点击下载RibbitMQ,或访问如下链接进行下载:

  https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.8.11/rabbitmq-server-3.8.11.exe

3.2.2 双击运行 rabbitmq-server-3.8.11.exe,点击下一步完成安装。

3.2.3 双击RabbitMQ Service - start 运行RabbitMQ

  

出现如下提示,则代表服务启动成功:

  

3.2.4 访问RabbitMQ控制台

  控制台地址:    http://localhost:15672/

  控制台用户名/密码 :    guest/guest

四、RabbitMQ传递消息的方式(Java客户端)

  • Work queues(工作队列)
  • Publish/Subscribe(发布/订阅)
  • Routing(路由)
  • Topics(主题)
  • RPC(远程过程调用)
  • Publisher Confirms(发布者确认)

环境要求:

  • JDK版本为15(1.8+即可)
  • amqp-client 5.10.0

添加依赖:

<!--ribbitMq-->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.10.0</version>
</dependency>

4.1 Work queues(工作队列)

官方描述:

  工作队列(又名:任务队列)背后的主要思想是避免立即执行资源密集型任务,并且必须等待它完成。相反,我们把任务安排在以后完成。我们将任务封装为消息并将其发送到队列。后台运行的工作进程将弹出任务并最终执行作业。当您运行多个worker时,任务将在它们之间共享。

代码示例:

生产者:

 1 public class NewTask {
2
3 private static final String TASK_QUEUE_NAME = "task_queue";
4
5 public static void main(String[] args) throws Exception{
6
7 ConnectionFactory factory = new ConnectionFactory();
8
9 // 设置IP
10 factory.setHost("127.0.0.1");
11
12 // 设置端口号
13 factory.setPort(5672);
14
15 try (Connection connection = factory.newConnection();
16 Channel channel = connection.createChannel()){
17 channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null);
18
19 String message = String.join(" ", "four");
20
21 channel.basicPublish("", TASK_QUEUE_NAME,
22 MessageProperties.PERSISTENT_TEXT_PLAIN,
23 message.getBytes(StandardCharsets.UTF_8));
24
25 System.out.println(" [x] Sent '" + message + "'");
26 }
27 }
28 }

消费者:

 1 public class Worker {
2
3 private static final String TASK_QUEUE_NAME = "task_queue";
4
5 public static void main(String[] args )throws Exception {
6
7 ConnectionFactory factory = new ConnectionFactory();
8
9 // 设置IP
10 factory.setHost("127.0.0.1");
11
12 // 设置端口号
13 factory.setPort(5672);
14
15 final Connection connection = factory.newConnection();
16 final Channel channel = connection.createChannel();
17
18 channel.queueDeclare(TASK_QUEUE_NAME, true, false,false,null);
19 System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
20
21 channel.basicQos(1);
22
23 DeliverCallback deliverCallback = (comsumerTag, delivery) ->{
24 String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
25
26 System.out.println(" [x] Received '" + message + "'");
27
28 try {
29 doWork(message);
30 } finally {
31 System.out.println("[x] Done");
32 channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
33 }
34 };
35 channel.basicConsume(TASK_QUEUE_NAME, false, deliverCallback, comsumerTag -> {});
36 }
37
38 private static void doWork(String task){
39 for (char ch : task.toCharArray()){
40 if(ch == '.'){
41 try {
42 Thread.sleep(1000);
43 } catch (InterruptedException e) {
44 Thread.currentThread().interrupt();
45 }
46 }
47 }
48 }
49 }

4.3 Publish/Subscribe(发布/订阅)

官方描述:

  RabbitMQ消息传递模型中的核心思想是生产者从不将任何消息直接发送到队列。实际上,生产者经常甚至根本不知道是否将消息传递到任何队列。相反,生产者只能将消息发送到交换机。交流是一件非常简单的事情。一方面,它接收来自生产者的消息,另一方面,将它们推入队列。交易所必须确切知道如何处理收到的消息。是否应将其附加到特定队列?是否应该将其附加到许多队列中?还是应该丢弃它。规则由交换类型定义 。

简而言之:

  相当于我们关注了一个微信公众号,公众号每次推文我们都能及时的收到。我们就相当于消费者,公众号相当于消息中转站,文章作者相当于生产者。

代码示例:

生产者:

 1 public class EmitLog {
2
3 private static final String ExCHANGE_NAME = "logs";
4
5 public static void main(String[] args) throws Exception{
6
7 ConnectionFactory factory = new ConnectionFactory();
8
9 // 设置IP
10 factory.setHost("127.0.0.1");
11
12 // 设置端口号
13 factory.setPort(5672);
14
15 try (Connection connection = factory.newConnection();
16 Channel channel = connection.createChannel()){
17 channel.exchangeDeclare(ExCHANGE_NAME, "fanout");
18
19 String message = args.length < 1 ? "info: Hello World!" : String.join(" ", args);
20
21 channel.basicPublish(ExCHANGE_NAME, "", null, message.getBytes(StandardCharsets.UTF_8));
22
23 System.out.println(" [x] Sent '" + message + "'");
24
25 }
26
27 }
28
29 }

消费者:

 1 public class ReceiveLogs {
2
3 private static final String ExCHANGE_NAME = "logs";
4
5 public static void main(String[] args) throws Exception{
6 ConnectionFactory factory = new ConnectionFactory();
7
8 // 设置IP
9 factory.setHost("127.0.0.1");
10
11 // 设置端口号
12 factory.setPort(5672);
13
14 Connection connection = factory.newConnection();
15 Channel channel = connection.createChannel();
16
17 channel.exchangeDeclare(ExCHANGE_NAME, "fanout");
18 String queueName = channel.queueDeclare().getQueue();
19 channel.queueBind(queueName, ExCHANGE_NAME, "");
20
21 System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
22
23 DeliverCallback deliverCallback = (sonsumerTag, delivery) -> {
24 String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
25 System.out.println(" [x] Received '" + message + "'");
26 };
27 channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {});
28 }
29 } 

4.4 Routing(路由)

官方描述:

  接上例,我们可能希望将日志消息写入磁盘的程序仅接收严重错误,而不会在警告或信息日志消息上浪费磁盘空间。

简而言之:

  如果我们只想接收某些信息,比如日志级别有INFO、ERROR、DEBUG等,我们只愿接收INFO日志。可以使用Routing进行过滤。

代码示例:

生产者:

 1 public class EmitLogDirect {
2
3 private static final String EXCHANGE_NAME = "direct_logs";
4
5 public static void main(String[] args) throws Exception{
6
7 ConnectionFactory factory = new ConnectionFactory();
8
9 // 设置IP
10 factory.setHost("127.0.0.1");
11
12 // 设置端口号
13 factory.setPort(5672);
14
15 try (Connection connection = factory.newConnection();
16 Channel channel = connection.createChannel()){
17 channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
18
19 String severity = getServerity(args);
20 String message = getMessage(args);
21
22 channel.basicPublish(EXCHANGE_NAME, severity, null, message.getBytes(StandardCharsets.UTF_8));
23 System.out.println(" [x] Sent '" + severity + "':'" + message + "'");
24
25 }
26
27 }
28
29 private static String getServerity(String[] strings){
30 if (strings.length < 1){
31 return "info";
32 }
33 return strings[0];
34
35 }
36
37 private static String getMessage(String[] strings){
38 if (strings.length < 2) {
39 return "Hello World!";
40 }
41 return joinStrings(strings, " ", 1);
42 }
43
44 private static String joinStrings(String[] strings, String delimiter, int startIndex){
45 int length = strings.length;
46 if(length == 0){
47 return "";
48 }
49 if(length <= startIndex){
50 return "";
51 }
52 StringBuilder words = new StringBuilder(strings[startIndex]);
53 for (int i = startIndex + 1; i < length; i++){
54 words.append(delimiter).append(strings[i]);
55 }
56 return words.toString();
57
58 }
59 }

消费者:

 1 public class ReceiveLogsDirect {
2
3 private static final String EXCHANGE_NAME = "direct_logs";
4
5 public static void main(String[] args) throws Exception{
6
7 ConnectionFactory factory = new ConnectionFactory();
8
9 // 设置IP
10 factory.setHost("127.0.0.1");
11
12 // 设置端口号
13 factory.setPort(5672);
14
15 Connection connection = factory.newConnection();
16
17 Channel channel = connection.createChannel();
18
19 channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
20
21 String queueName = channel.queueDeclare().getQueue();
22
23 if(args.length < 1){
24 System.err.println("Usage: ReceiveLogsDirect [info] [warning] [error]");
25 System.exit(1);
26 }
27
28 for (String severity : args){
29 channel.queueBind(queueName, EXCHANGE_NAME, severity);
30 }
31 System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
32
33 DeliverCallback deliverCallback = (consumerTag, delivery)->{
34 String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
35 System.out.println(" [x] Received '" + delivery.getEnvelope().getRoutingKey() + "':'" + message + "'");
36 };
37
38 channel.basicConsume(queueName, true, deliverCallback, comsumerTag ->{});
39 }
40 }

4.5 Topics(主题)

官方描述:

  发送到主题交换机的消息不能具有任意的 routing_key-它必须是单词列表,以点分隔。这些词可以是任何东西,但通常它们指定与消息相关的某些功能。一些有效的路由关键示例:“ stock.usd.nyse ”,“ nyse.vmw ”,“ quick.orange.rabbit ”。路由关键字中可以包含任意多个单词,最多255个字节。

  绑定密钥也必须采用相同的形式。主题交换背后的逻辑 类似于直接交换-用特定路由键发送的消息将传递到所有用匹配绑定键绑定的队列。但是,绑定键有两个重要的特殊情况:

    • *(星)只能代替一个单词。
    • #(散列)可以代替零个或多个单词。

简而言之:

   Topic会根据消息自身所携带的路由键(Routing Key)在所有的绑定关系中寻找,与消息相匹配的队列推送该消息。

注意:

  当在绑定中不使用特殊字符“ * ”(星号)和“ # ”(哈希)时,主题交换的行为就像直接的一样。

代码示例:

生产者:

 1 public class EmitLogTopic {
2
3 private static final String EXCHANGE_NAME = "topic_logs";
4
5 public static void main(String[] args) throws Exception{
6
7 ConnectionFactory factory = new ConnectionFactory();
8 // 设置IP
9 factory.setHost("127.0.0.1");
10
11 // 设置端口号
12 factory.setPort(5672);
13
14 try(Connection connection = factory.newConnection();
15 Channel channel = connection.createChannel()){
16
17 channel.exchangeDeclare(EXCHANGE_NAME, "topic");
18
19 String routingKey = getRouting(args);
20 String message = getMessage(args);
21
22 channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes(StandardCharsets.UTF_8));
23 System.out.println(" [x] Sent '" + routingKey + "':'" + message + "'");
24 }
25 }
26
27 private static String getRouting(String[] strings){
28 if (strings.length < 1){
29 return "anonymous.info";
30 }
31 return strings[0];
32 }
33
34 private static String getMessage(String[] strings){
35 if (strings.length < 2){
36 return "hello world";
37 }
38 return joinStrings(strings, " ", 1);
39 }
40
41 private static String joinStrings(String[] strings, String delimiter, int startIndex){
42 int length = strings.length;
43 if(length == 0){
44 return "";
45 }
46 if(length < startIndex){
47 return "";
48 }
49 StringBuilder words = new StringBuilder(strings[startIndex]);
50 for (int i = startIndex + 1; i < length; i++){
51 words.append(delimiter).append(strings[i]);
52 }
53 return words.toString();
54 }
55 }

消费者:

 1 public class ReceiveLogTopic {
2
3 private static final String EXCHANGE_NAME = "topic_logs";
4
5 public static void main(String[] args) throws Exception{
6
7 ConnectionFactory factory = new ConnectionFactory();
8 // 设置IP
9 factory.setHost("127.0.0.1");
10
11 // 设置端口号
12 factory.setPort(5672);
13
14 Connection connection = factory.newConnection();
15 Channel channel = connection.createChannel();
16
17 channel.exchangeDeclare(EXCHANGE_NAME, "topic");
18
19 String queueName = channel.queueDeclare().getQueue();
20
21 if(args.length < 1){
22 System.err.println("Usage: ReceiveLogsTopic [binding_key]...");
23 System.exit(1);
24 }
25
26 for (String bindingKey : args){
27 channel.queueBind(queueName, EXCHANGE_NAME, bindingKey);
28 }
29
30 System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
31
32 DeliverCallback deliverCallback = (consumerTag, delivery) -> {
33 String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
34 System.out.println(" [x] Received '" + delivery.getEnvelope().getRoutingKey() + "':'" + message + "'");
35 };
36 channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {});
37 }
38 }

4.6 RPC(远程过程调用)

官方描述:

  尽管RPC是计算中非常普遍的模式,但它经常受到批评。当程序员不知道函数调用是本地的还是缓慢的RPC时,就会出现问题。这样的混乱会导致系统变幻莫测,并给调试增加了不必要的复杂性。滥用RPC可能会导致无法维护的意大利面条代码,而不是简化软件。

代码示例:

生产者

 1 public class RPCServer {
2
3 private static final String RPC_QUEUE_NAME = "rpc_queue";
4
5 private static int fib(int n){
6 if(n == 0){
7 return 0;
8 }
9 if(n == 1){
10 return 1;
11 }
12 return fib(n - 1) + fib(n - 2);
13 }
14
15 public static void main(String[] args) throws Exception{
16
17 // 创建服务器的连接
18 ConnectionFactory factory = new ConnectionFactory();
19
20 // 设置IP
21 factory.setHost("127.0.0.1");
22
23 // 设置端口号
24 factory.setPort(5672);
25
26 try (Connection connection = factory.newConnection();
27 Channel channel = connection.createChannel()) {
28 channel.queueDeclare(RPC_QUEUE_NAME, false, false, false, null);
29 channel.queuePurge(RPC_QUEUE_NAME);
30
31 channel.basicQos(1);
32
33 System.out.println(" [x] Awaiting RPC requests");
34
35 Object monitor = new Object();
36 DeliverCallback deliverCallback = (consumerTag, delivery) ->{
37 AMQP.BasicProperties replyProps = new AMQP.BasicProperties
38 .Builder()
39 .correlationId(delivery.getProperties().getCorrelationId())
40 .build();
41
42 String response = "";
43
44 try{
45 String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
46 int n = Integer.parseInt(message);
47
48 System.out.println(" [.] fib(" + message + ")");
49 response += fib(n);
50 }catch (RuntimeException e){
51 System.out.println(" [.] " + e.toString());
52 }finally {
53 channel.basicPublish("", delivery.getProperties().getReplyTo(), replyProps, response.getBytes(StandardCharsets.UTF_8));
54 channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
55
56 // RabbitMq consumer worker thread notifies the RPC server owner thread
57 // RabbitMq使用者工作线程通知RPC服务器所有者线程
58 synchronized (monitor){
59 monitor.notify();
60 }
61 }
62 };
63 channel.basicConsume(RPC_QUEUE_NAME, false, deliverCallback, (consumerTag -> {}));
64 // Wait and be prepared to consume the message from RPC client.
65 // 等待并准备使用来自RPC客户端的消息。
66 while(true){
67 synchronized (monitor){
68 try {
69 monitor.wait();
70 }catch (InterruptedException e){
71 e.printStackTrace();
72 }
73 }
74 }
75 }
76 }
77 }

消费者:

 1 public class RPCClient {
2
3 private Connection connection;
4 private Channel channel;
5 private String requestQueueName = "rpc_queue";
6
7 public RPCClient() throws IOException, TimeoutException {
8 // 创建服务器的连接
9 ConnectionFactory factory = new ConnectionFactory();
10
11 // 设置IP
12 factory.setHost("127.0.0.1");
13
14 // 设置端口号
15 factory.setPort(5672);
16
17 connection = factory.newConnection();
18 channel = connection.createChannel();
19 }
20
21 public static void main(String[] args) throws Exception{
22 RPCClient fibonacciRpc = new RPCClient();
23 for (int i = 0; i < 32; i++) {
24 String i_str = Integer.toString(i);
25 System.out.println(" [x] Requesting fib(" + i_str + ")");
26 String response = fibonacciRpc.call(i_str);
27 System.out.println(" [.] Got '" + response + "'");
28 }
29
30 }
31
32 public String call(String message) throws IOException, InterruptedException {
33 final String corrId = UUID.randomUUID().toString();
34
35 String replyQueueName = channel.queueDeclare().getQueue();
36 AMQP.BasicProperties props = new AMQP.BasicProperties
37 .Builder()
38 .correlationId(corrId)
39 .replyTo(replyQueueName)
40 .build();
41
42 channel.basicPublish("", requestQueueName, props, message.getBytes("UTF-8"));
43
44 final BlockingQueue<String> response = new ArrayBlockingQueue<>(1);
45
46 String ctag = channel.basicConsume(replyQueueName, true, (consumerTag, delivery) -> {
47 if (delivery.getProperties().getCorrelationId().equals(corrId)) {
48 response.offer(new String(delivery.getBody(), "UTF-8"));
49 }
50 }, consumerTag -> {
51 });
52
53 String result = response.take();
54 channel.basicCancel(ctag);
55 return result;
56 }
57
58 public void close() throws IOException {
59 connection.close();
60 }
61 }

4.7 Publisher Confirms(发布者确认)

官方描述:

  在某些应用程序中,确保将发布的消息发送到代理非常重要。发布者确认是RabbitMQ功能,可以帮助满足此要求。发布者确认本质上是异步的,但也可以同步处理它们。没有确定的方法可以实现发布者确认,这通常归结为应用程序和整个系统中的约束。典型的技术有:

  • 单独发布消息,同步等待确认:简单,但吞吐量非常有限。
  • 批量发布消息,同步等待批量确认:简单,合理的吞吐量,但是很难推断出什么时候出了问题。
  • 异步处理:最佳性能和资源使用,在出现错误的情况下可以很好地控制,但是可以正确实施。

 代码示例:

  1 public class PublisherConfirms {
2
3 static final int MESSAGE_COUNT = 50_000;
4
5 static Connection createConnection() throws Exception{
6
7 ConnectionFactory cf = new ConnectionFactory();
8
9 // 设置IP
10 cf.setHost("127.0.0.1");
11
12 // 设置端口号
13 cf.setPort(5672);
14
15 // 设置用户名
16 cf.setUsername("guest");
17
18 // 设置密码
19 cf.setPassword("guest");
20
21 return cf.newConnection();
22 }
23
24 public static void main(String[] args) throws Exception{
25 publishMessagesIndividually();
26 publishMessagesInBatch();
27 handlePublishConfirmsAsynchronously();
28 }
29
30 static void publishMessagesIndividually() throws Exception{
31 try(Connection connection = createConnection()){
32 Channel ch = connection.createChannel();
33
34 String queue = UUID.randomUUID().toString();
35 ch.queueDeclare(queue, false, false, true, null);
36
37 ch.confirmSelect();
38
39 long start = System.nanoTime();
40 for (int i = 0; i < MESSAGE_COUNT; i++){
41 String body = String.valueOf(i);
42 ch.basicPublish("", queue, null, body.getBytes(StandardCharsets.UTF_8));
43 ch.waitForConfirmsOrDie(5_000);
44 }
45 long end = System.nanoTime();
46 System.out.format("Published %,d messages individually in %,d ms%n", MESSAGE_COUNT, Duration.ofNanos(end - start).toMillis());
47 }
48 }
49
50 static void publishMessagesInBatch() throws Exception {
51 try (Connection connection = createConnection()) {
52 Channel ch = connection.createChannel();
53
54 String queue = UUID.randomUUID().toString();
55 ch.queueDeclare(queue, false, false, true, null);
56
57 ch.confirmSelect();
58
59 int batchSize = 100;
60 int outstandingMessageCount = 0;
61 long start = System.nanoTime();
62 for (int i = 0; i < MESSAGE_COUNT; i++) {
63 String body = String.valueOf(i);
64 ch.basicPublish("", queue, null, body.getBytes());
65 outstandingMessageCount++;
66
67 if (outstandingMessageCount == batchSize) {
68 ch.waitForConfirmsOrDie(5_000);
69 outstandingMessageCount = 0;
70 }
71 }
72
73 if (outstandingMessageCount > 0) {
74 ch.waitForConfirmsOrDie(5_000);
75 }
76 long end = System.nanoTime();
77 System.out.format("Published %,d messages in batch in %,d ms%n", MESSAGE_COUNT, Duration.ofNanos(end - start).toMillis());
78
79 }
80
81 }
82
83 static void handlePublishConfirmsAsynchronously() throws Exception {
84 try (Connection connection = createConnection()) {
85 Channel ch = connection.createChannel();
86
87 String queue = UUID.randomUUID().toString();
88 ch.queueDeclare(queue, false, false, true, null);
89
90 ch.confirmSelect();
91
92 ConcurrentNavigableMap<Long, String> outstandingConfirms = new ConcurrentSkipListMap<>();
93
94 ConfirmCallback cleanOutstandingConfirms = (sequenceNumber, multiple) -> {
95 if (multiple) {
96 ConcurrentNavigableMap<Long, String> confirmed = outstandingConfirms.headMap(
97 sequenceNumber, true
98 );
99 confirmed.clear();
100 } else {
101 outstandingConfirms.remove(sequenceNumber);
102 }
103 };
104
105 ch.addConfirmListener(cleanOutstandingConfirms, (sequenceNumber, multiple) -> {
106 String body = outstandingConfirms.get(sequenceNumber);
107 System.err.format(
108 "Message with body %s has been nack-ed. Sequence number: %d, multiple: %b%n",
109 body, sequenceNumber, multiple
110 );
111 cleanOutstandingConfirms.handle(sequenceNumber, multiple);
112 });
113
114 long start = System.nanoTime();
115 for (int i = 0; i < MESSAGE_COUNT; i++) {
116 String body = String.valueOf(i);
117 outstandingConfirms.put(ch.getNextPublishSeqNo(), body);
118 ch.basicPublish("", queue, null, body.getBytes());
119 }
120
121 if (!waitUntil(Duration.ofSeconds(60), () -> outstandingConfirms.isEmpty())) {
122 throw new IllegalStateException("All messages could not be confirmed in 60 seconds");
123 }
124
125 long end = System.nanoTime();
126 System.out.format("Published %,d messages and handled confirms asynchronously in %,d ms%n", MESSAGE_COUNT, Duration.ofNanos(end - start).toMillis());
127 }
128 }
129
130 static boolean waitUntil(Duration timeout, BooleanSupplier condition) throws InterruptedException {
131 int waited = 0;
132 while (!condition.getAsBoolean() && waited < timeout.toMillis()) {
133 Thread.sleep(100L);
134 waited = +100;
135 }
136 return condition.getAsBoolean();
137 }
138 }

五、总结

  总的来说,RabbitMQ还是比较简单的。目前文章只是简单记录一下,后期会更深入学习。

关于RabbitMQ的简单理解的更多相关文章

  1. RabbitMQ实战:理解消息通信

    RabbitMQ是一个开源的消息代理和队列服务器,可以通过基本协议在完全不同的应用之间共享数据,可以将作业排队以便让分布式服务进行处理. 本篇介绍下消息通信,首先介绍基础概念,将这些概念映射到AMQP ...

  2. RabbitMQ的简单封装

    一般在工作中,都是直接使用已经封装好的mq的程序集进行功能开发.所以很多时候都没有去了解rabbitmq到底是如何封装(实现使用的).所以心血来潮,简单记录下自己对rabbitmq的简单封装 整体的思 ...

  3. git的简单理解及基础操作命令

    前端小白一枚,最近开始使用git,于是花了2天看了廖雪峰的git教程(偏实践,对于学习git的基础操作很有帮助哦),也在看<git版本控制管理>这本书(偏理论,内容完善,很不错),针对所学 ...

  4. 简单理解Struts2中拦截器与过滤器的区别及执行顺序

    简单理解Struts2中拦截器与过滤器的区别及执行顺序 当接收到一个httprequest , a) 当外部的httpservletrequest到来时 b) 初始到了servlet容器 传递给一个标 ...

  5. [转]简单理解Socket

    简单理解Socket 转自 http://www.cnblogs.com/dolphinX/p/3460545.html  题外话 前几天和朋友聊天,朋友问我怎么最近不写博客了,一个是因为最近在忙着公 ...

  6. Js 职责链模式 简单理解

    js 职责链模式 的简单理解.大叔的代码太高深了,不好理解. function Handler(s) { this.successor = s || null; this.handle = funct ...

  7. Deep learning:四十六(DropConnect简单理解)

    和maxout(maxout简单理解)一样,DropConnect也是在ICML2013上发表的,同样也是为了提高Deep Network的泛化能力的,两者都号称是对Dropout(Dropout简单 ...

  8. Deep learning:四十二(Denoise Autoencoder简单理解)

    前言: 当采用无监督的方法分层预训练深度网络的权值时,为了学习到较鲁棒的特征,可以在网络的可视层(即数据的输入层)引入随机噪声,这种方法称为Denoise Autoencoder(简称dAE),由Be ...

  9. 简单理解dropout

    dropout是CNN(卷积神经网络)中的一个trick,能防止过拟合. 关于dropout的详细内容,还是看论文原文好了: Hinton, G. E., et al. (2012). "I ...

随机推荐

  1. 洛谷P4848 崂山白花蛇草水 权值线段树+KDtree

    题目描述 神犇 \(Aleph\) 在 \(SDOI\ Round2\) 前立了一个 \(flag\):如果进了省队,就现场直播喝崂山白花蛇草水.凭借着神犇 \(Aleph\) 的实力,他轻松地进了山 ...

  2. 风炫安全WEB安全学习第二十四节课 利用XSS钓鱼攻击

    风炫安全WEB安全学习第二十四节课 利用XSS钓鱼攻击 XSS钓鱼攻击 HTTP Basic Authentication认证 大家在登录网站的时候,大部分时候是通过一个表单提交登录信息. 但是有时候 ...

  3. Docker-ce Centos8 笔记一:安装Docker-ce

    Docker是一个建设企业及数据中心服务仓库的进程,通过裸金属机和虚拟机承载的MAC.windows和linux系统提供本地和远程软件服务,涉及应用软件镜像.系统镜像.虚拟化仓库(虚拟机).它承载着灵 ...

  4. 初识sa-token,一行代码搞定登录授权!

    前言 在java的世界里,有很多优秀的权限认证框架,如Apache Shiro.Spring Security 等等.这些框架背景强大,历史悠久,其生态也比较齐全. 但同时这些框架也并非十分完美,在前 ...

  5. LeetCode237 删除链表中的节点

    请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点,你将只被给定要求被删除的节点. 现有一个链表 -- head = [4,5,1,9],它可以表示为: 4 -> 5 -> 1 - ...

  6. LeetCode448-数组中消失的数字

    题目 给定一个范围在 1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次. 找到所有在 [1, n] 范围之间没有出现在数组中的数字. 您能 ...

  7. 有关Servlet的面试题

    CGI(Common Gateway Interface),通用网管接口 通用网管接口,简称CGI,是一种根据请求信息动态产生回应内容的技术.通过CGI,web服务器可以根据请求的不同,启动不同的外部 ...

  8. DHCP最佳实践(一)

    这是Windows DHCP最佳实践和技巧的最终指南. 如果您有任何最佳做法或技巧,请在下面的评论中发布它们. 在本指南(一)中,我将分享以下DHCP最佳实践和技巧. 不要将DHCP放在您的域控制器上 ...

  9. 微信小程序API交互的自定义封装

    目录 1,起因 2,优化成果 3,实现思路 4,完整代码 1,起因 哪天,正在蚂蚁森林疯狂偷能量的我被boss叫过去,告知我司要做一个线上直播公开课功能的微信小程序,博主第一次写小程序,复习了下文档, ...

  10. 阿里云VOD(三)

    一.视频播放器 参考文档:https://help.aliyun.com/document_detail/125570.html?spm=a2c4g.11186623.6.1083.1c53448bl ...