rabbitmq快速入门学习

rabbitmq是什么?

它是一个高级的消息队列, 下层基于AMQP协议实现的类似这种结构
简单的消息发布过程
publisher发布消息给交换器(exchange)然后交换机把消息发送给它以某种方式绑定的队列, 我们的消费者监控着这个队列, 如果有消息过来则使用消息

这种方式我们会发现, 如果发布者发布消息, 只要丢给交换机他就能给你发送到消费者借助队列, 所以我们可以这样

一个队列对多个消费者
上面这种方式所实现的方式, 一个消息发送给exchange时, 他会借助queue队列发送给两个消费者中的一个, 而且好像默认时轮训, 一个消息只能有一个消费者接收并使用

现在还存在这种形式

交换机和队列一对多
这种方式的实现, 如果发送一个消息, 那么这个消息会发送给两个队列中, 此时两个队列都存在交换机中的一个消息, 这种就类似广播方式, 一个消息, 多个消费者使用

还有这种情况
在这里插入图片描述
交换机和队列中间使用三个路由键, 其中两个路由键绑定了同一个队列, 另外一个绑定了另一个队列, 这样, 如果exhange路由2绑定的消息给消费者2, 同时发送路由2路由3消息会同时发送给队列2

所以这里出现了多个路由键绑定一个队列的情况

有什么用?

主要用处有:

  • 跨语言跨应用通信(解耦)
  • 异步提高效率
  • 流量削峰
  • 工作窃取
  • 广播
  • 最终一致性

大多数时候, 我们可以将rabbitmq看作一个可持久化的, 一(交换机)对多(队列), 可集群的消息队列, 跨语言的高级消息队列

根据这几个特点, 可以完成很多的生产场景

实际生产环境中的使用场景

比如:

  1. 应用A需要将一个消息事件发送给另一个应用, 但是应用A是java编写, 应用B是PHP编写, 此时便需要使用到应用解耦方式, 达到跨语言实现 ---- 跨语言跨应用通信(解耦)
  2. 应用A现在产生了一个不需要实时执行的事件, 但此时应用A很繁忙, 没有多余的时间去执行它, 或者这个事件很耗时, 需要将它延后到空闲时执行, 此时可以考虑使用消息队列 ---- 异步提高效率
  3. 引用A平时只能处理1000个/秒的请求, 有一天需要做秒杀活动, 此时最高峰值达到1w个/秒的请求, 此时便可以将系统无法处理的事件暂时发布到队列中, 而秒杀活动通常也就那几秒, 等到几秒时间过后, 系统还是以每秒1000个请求的方式继续将队列中的请求处理掉 ---- 流量削峰
  4. 单个应用突然发现繁忙导致效率变慢了(也许它受到了一个需要cpu高度密集的请求, 导致cpu处理缓慢), 此时还存在多个请求还没处理完毕, 但是其他应用却是空闲的, 那么现在需要怎么做? 可以借助mq将需要处理的事件转发给另外几个应用中 ---- 工作窃取
  5. 假设应用A发送一个集群开始执行某种事件的操作, 也就是一个消息需要同时转发给多个集群, 此时遍需要使用上消息队列 ---- 广播
  6. 现在有这么一个场景, 应用A更新完毕某个数据, 但此时其他集群并没有这种这段数据, 此时可以使用消息队列, 但这样存在一个缺点是非实时性的, 现在应用B和应用C之间的数据可能存在几毫秒的时间间隔, 但最终结果是一定的, 即使发送消息给应用C失效, 但是rabbitmq可以选择重新将这个事件放入队列中再次重复或者爆出异常, 让使用者进行判断, 到底如何解决这个问题 — 最终一致性

大概这几种方式了, 主要还是利用了消息队列的特性
延迟, 另外的空间, 持久化(防止消息失效), 跨语言, 跨应用, 多种交换机等方式

怎么用?

安装

首先我使用的是虚拟机在虚拟机上安装了docker, 并且在docker上安装并运行了rabbitmq

  1. docker pull rabbitmq:management
  2. docker run -d --name=rabbitmq -p 15672:15672 -p 5672:5672 rabbitmq:management
  3. docker ps看下是否运行
    docker ps
    回到主机上访问下rabbitmq 的web管理端(我主机的ip是192.168.0.153)
    http://192.168.0.153:15672/
    在这里插入图片描述

入门

hello world

常量:

public class Constant {
	public static final String HOSTNAME = "192.168.0.155";
	public static final int PORT = 5672;
	public static final String QUEUE_NAME = "queue_name";
	public static final String EXCHANGE_NAME = "exchange_name";
	public static final String USERNAME = "guest";
	public static final String PASSWORD = "guest";
}

消息发布者源码:

public class HelloWorldPublishDemo {
	public static void main(String[] args) throws Exception {
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost(Constant.HOSTNAME);
		try (Connection connection = factory.newConnection();
		     Channel channel = connection.createChannel()) {
			channel.queueDeclare(Constant.QUEUE_NAME, false, false, false, null);
			String message = "zhazha";
			channel.basicPublish("", Constant.QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
			System.out.println(" [x] Sent '" + message + "'");
		}
	}
}

消息消费者源码:

public class HelloWorldRecvDemo01 {
	public static void main(String[] args) throws Exception {
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost(Constant.HOSTNAME);
		Connection connection = factory.newConnection();
		Channel channel = connection.createChannel();
		
		channel.queueDeclare(Constant.QUEUE_NAME, false, false, false, null);
		
		channel.basicConsume(Constant.QUEUE_NAME, true, (String consumerTag, Delivery message) -> {
			System.out.println(new String(message.getBody(), StandardCharsets.UTF_8));
			System.out.println(consumerTag);
		}, System.out::println);
	}
}

这样便达成了这种
简单的消息发布过程
情况

现在另外加入另一个消息消费者

消费者2源码:

public class HelloWorldRecvDemo02 {
	
	public static void main(String[] args) throws Exception {
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost(Constant.HOSTNAME);
		Connection connection = factory.newConnection();
		Channel channel = connection.createChannel();
		
		channel.queueDeclare(Constant.QUEUE_NAME, false, false, false, null);
		
		channel.basicConsume(Constant.QUEUE_NAME, true, (String consumerTag, Delivery message) -> {
			System.out.println(new String(message.getBody(), StandardCharsets.UTF_8));
			System.out.println(consumerTag);
		}, System.out::println);
	}
	
}

那么此时运行起源码就会发现, 发布者发布的消息会以轮训的方式, 发送给他们中的一个, 下次另一个

广播方式

发送一个消息给多个消费者

类似于这种

在这里插入图片描述
直接贴下源码(常量源码我就不贴了, 在前面)

消息的发布者:

public class PublishDemo {
	
	public static void main(String[] args) throws Exception {
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost(Constant.HOSTNAME);
		Connection connection = connectionFactory.newConnection();
		Channel channel = connection.createChannel();
		try (connection; channel) {
			channel.exchangeDeclare(Constant.EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
			channel.basicPublish(Constant.EXCHANGE_NAME, "", null, "zhazha message ".getBytes());
			System.err.println("publish send message ... ");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
}

消息的消费者01:

public class ConsumerDemo01 {
	
	public static void main(String[] args) throws Exception {
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost(Constant.HOSTNAME);
		Connection connection = connectionFactory.newConnection();
		Channel channel = connection.createChannel();
		channel.exchangeDeclare(Constant.EXCHANGE_NAME, "fanout");
		String queueName = channel.queueDeclare().getQueue();
		channel.queueBind(queueName, Constant.EXCHANGE_NAME, "");
		System.out.println("waiting for messages, To exit press ctrl + c ");
		DefaultConsumer consumer = new DefaultConsumer(channel) {
			@Override
			public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
				String message = new String(body);
				System.err.println("recv message: " + message);
			}
		};
		channel.basicConsume(queueName, true, consumer);
	}
	
}

消息的消费者02 :

public class ConsumerDemo02 {
	
	public static void main(String[] args) throws Exception {
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost(Constant.HOSTNAME);
		Connection connection = connectionFactory.newConnection();
		Channel channel = connection.createChannel();
		channel.exchangeDeclare(Constant.EXCHANGE_NAME, "fanout");
		String queueName = channel.queueDeclare().getQueue();
		channel.queueBind(queueName, Constant.EXCHANGE_NAME, "");
		System.out.println("waiting for messages, To exit press ctrl + c ");
		DefaultConsumer consumer = new DefaultConsumer(channel) {
			@Override
			public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
				String message = new String(body);
				System.err.println("recv message: " + message);
			}
		};
		channel.basicConsume(queueName, true, consumer);
	}
	
}

入门代码至此完毕

多种交换机(Exchange)

  • Direct:完全根据key进行投递的,例如,绑定时设置了routing key”abc”,那么客户端提交的消息,只有设置了key”abc”的才会投递到队列。
  • Topic:对key进行模式匹配后进行投递,符号”#”匹配一个或多个词,符号”*”匹配正好一个词。例如”abc.#”匹配”abc.def.ghi””abc.*”只匹配”abc.def”
  • Fanout:不需要key,它采取广播模式,一个消息进来时,投递到与该交换机绑定的所有队列。
  • Headers:我们可以不考虑它。

默认exchange, 不支持绑定队列, 可直接通过 routing key直接和同名queue名字进行隐式绑定

给个简单的默认exchange的例子

public class Consumer {
	
	public static void main(String[] args) throws Exception {
		final ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost(Constant.HOSTNAME);
		final Connection connection = connectionFactory.newConnection();
		final Channel channel = connection.createChannel();
		channel.queueDeclare("queueName", false, false, false, null);
		final DefaultConsumer consumer = new DefaultConsumer(channel) {
			@Override
			public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
				final String message = new String(body, StandardCharsets.UTF_8);
				System.out.println(message);
			}
		};
		channel.basicConsume("queueName", true, consumer);
	}
}
public class Publisher {
	
	public static void main(String[] args) throws Exception {
		final ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost(Constant.HOSTNAME);
		final Connection connection = connectionFactory.newConnection();
		final Channel channel = connection.createChannel();
		
		channel.basicPublish("", "queueName", null, "haha".getBytes(StandardCharsets.UTF_8));
	}
	
}

直接便可通讯

我们使用下直接交换机模式试试

direct

先发出源码在根据源码分析
在这里插入图片描述
根据上面的代码可以编写出如下代码:

服务器源码:

public class Server01 {
	
	public static void main(String[] args) throws Exception {
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost(Constant.HOSTNAME);
		Connection connection = connectionFactory.newConnection();
		Channel channel = connection.createChannel();
		try (connection; channel) {
			channel.exchangeDeclare(Constant.EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
			channel.basicPublish(Constant.EXCHANGE_NAME, "orange", null, "orange".getBytes(StandardCharsets.UTF_8));
			channel.basicPublish(Constant.EXCHANGE_NAME, "black", null, "black".getBytes(StandardCharsets.UTF_8));
			channel.basicPublish(Constant.EXCHANGE_NAME, "green", null, "green".getBytes(StandardCharsets.UTF_8));
		} catch (Exception e) {
		    e.printStackTrace();
		}
	}
}

客户端1, 2的源码:

public class Client01 {
	
	public static void main(String[] args) throws Exception {
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost(Constant.HOSTNAME);
		Connection connection = connectionFactory.newConnection();
		Channel channel = connection.createChannel();
		channel.exchangeDeclare(Constant.EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
		String queue = channel.queueDeclare().getQueue();
		channel.queueBind(queue, Constant.EXCHANGE_NAME, "orange");
		channel.basicConsume(queue, true, (consumerTag, message) -> System.out.println("message = " + new String(message.getBody(), StandardCharsets.UTF_8)), System.out::println);
	}
}
public class Client02 {
	
	public static void main(String[] args) throws Exception {
		ConnectionFactory connectionFactory = new ConnectionFactory();
		
		connectionFactory.setHost(Constant.HOSTNAME);
		Connection connection = connectionFactory.newConnection();
		Channel channel = connection.createChannel();
		String queue = channel.queueDeclare().getQueue();
		channel.exchangeDeclare(Constant.EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
		channel.queueBind(queue, Constant.EXCHANGE_NAME, "black");
		channel.queueBind(queue, Constant.EXCHANGE_NAME, "green");
		channel.basicConsume(queue, true, (consumerTag, message) -> System.out.println("message = " + new String(message.getBody(), StandardCharsets.UTF_8)), System.out::println);
	}
}

首先定义出一个交换机

channel.exchangeDeclare(Constant.EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

然后发出三个消息, 分别给orange, black, green 路由键发送消息:

channel.basicPublish(Constant.EXCHANGE_NAME, "orange", null, "orange".getBytes(StandardCharsets.UTF_8));
channel.basicPublish(Constant.EXCHANGE_NAME, "black", null, "black".getBytes(StandardCharsets.UTF_8));
channel.basicPublish(Constant.EXCHANGE_NAME, "green", null, "green".getBytes(StandardCharsets.UTF_8));

现在我们回到客户端:

首先通过定义出同一个交换机(消费者在实际生产中处于不同的主机上)
channel.exchangeDeclare(Constant.EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
可以看出我们定义了个DIRECT类型的交换机

然后定义出一个队列
String queue = channel.queueDeclare().getQueue();, 这个队列名随机产生

现在做下绑定

channel.queueBind(queue, Constant.EXCHANGE_NAME, "orange");
同时绑定了同一个路由键orange

然后我们就可以接收消息并消费它了
channel.basicConsume(queue, true, (consumerTag, message) -> System.out.println("message = " + new String(message.getBody(), StandardCharsets.UTF_8)), System.out::println);

我们只要那direct下的这张图片, 就可以看着这样图片写出代码了

同时我们还可以这样

在这里插入图片描述
同样的角色, 让同一个路由键绑定到不同的队列中, 这种貌似我们在前面已经讲过了, 但是缺少代码, 现在添加下, 看下结果, 看官网的意思类似于fanout交换机

方式也很简单, 在前面的基础上再添加一个客户端(消费者):

public class Cilent03 {
	public static void main(String[] args) throws Exception {
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost(Constant.HOSTNAME);
		Connection connection = connectionFactory.newConnection();
		Channel channel = connection.createChannel();
		String queue = channel.queueDeclare().getQueue();
		channel.exchangeDeclare(Constant.EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
		channel.queueBind(queue, Constant.EXCHANGE_NAME, "black");
		channel.basicConsume(queue, true, (consumerTag, message) -> System.out.println("message = " + new String(message.getBody(), StandardCharsets.UTF_8)), System.out::println);
	}
}

发现上面做了个black路由键, 现在可以发现客户端(消费者)client02的代码也绑定了black所以现在我们启动client02client03, 就能够收到数据了(注意其中有一个会收到greenblack那是因为主机默认想三个路由键发送了消息)

topic

在这里插入图片描述
了解下这个方案
这种方案下, 出现了*#这两个字符串
*代表了单个单词(不是字符哦)
#代表了多个单词或者没有单词

那么看起来就相当于 *.orange.**.*.rabbitlazy.#求匹配的意思

比如1.orange.211.orange.22就能够匹配, 但如果是1.1.orange.2.2这样的铁定不行

看官网他的意思是这个路由键不能够太长, 对多不超过255个, 当然这些都是符合规则的: “stock.usd.nyse”, “nyse.vmw”, “quick.orange.rabbit”

我们代码上试试看下

消息发送端源码:

public class WordPublisherDemo01 {
	
	public static void main(String[] args) throws Exception {
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost(Constant.HOSTNAME);
		Connection connection = connectionFactory.newConnection();
		Channel channel = connection.createChannel();
		try (connection; channel) {
			channel.exchangeDeclare(Constant.EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
			channel.basicPublish(Constant.EXCHANGE_NAME, "1.orange.2", null, "1.orange.2".getBytes(StandardCharsets.UTF_8));
			channel.basicPublish(Constant.EXCHANGE_NAME, "11.orange.22", null, "11.orange.22".getBytes(StandardCharsets.UTF_8));
			channel.basicPublish(Constant.EXCHANGE_NAME, "1.1.orange.2.2", null, "1.1.orange.2.2".getBytes(StandardCharsets.UTF_8));
		} catch (Exception e) {
		    e.printStackTrace();
		}
	}
}

消息消费者源码:

public class WordConsumerDemo01 {
	
	public static void main(String[] args) throws Exception {
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost(Constant.HOSTNAME);
		Connection connection = connectionFactory.newConnection();
		Channel channel = connection.createChannel();
		channel.exchangeDeclare(Constant.EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
		String queue = channel.queueDeclare().getQueue();
		channel.queueBind(queue, Constant.EXCHANGE_NAME, "*.orange.*");
		channel.basicConsume(queue, true, (consumerTag, message) -> System.out.println(new String(message.getBody(), StandardCharsets.UTF_8)), System.out::println);
	}
}
1.orange.2
11.orange.22

把代码运行起来后果然如我们所看到的情况

那么现在试试#代表多个单词的匹配方式

public class WordsConsumerDemo02 {
	
	public static void main(String[] args) throws Exception {
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost(Constant.HOSTNAME);
		Connection connection = connectionFactory.newConnection();
		Channel channel = connection.createChannel();
		channel.exchangeDeclare(Constant.EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
		String queue = channel.queueDeclare().getQueue();
		channel.queueBind(queue, Constant.EXCHANGE_NAME, "#.orange.#");
		channel.basicConsume(queue, true, (consumerTag, message) -> System.out.println(new String(message.getBody(), StandardCharsets.UTF_8)), System.out::println);
	}
	
}

消息生产者还是WordPublisherDemo01的源码

1.orange.2
11.orange.22
1.1.orange.2.2

Fanout

那扇形怎么回事呢?

Fanout:不需要key,它采取广播模式,一个消息进来时,投递到与该交换机绑定的所有队列。

贴下代码看现象

生产者:

public class PublisherDemo01 {
	
	public static void main(String[] args) throws Exception {
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost(Constant.HOSTNAME);
		Connection connection = connectionFactory.newConnection();
		Channel channel = connection.createChannel();
		try (connection; channel) {
			channel.exchangeDeclare(Constant.EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
			// FANOUT 这种模式不管routingKey
			channel.basicPublish(Constant.EXCHANGE_NAME, "1", null, "1".getBytes(StandardCharsets.UTF_8));
			channel.basicPublish(Constant.EXCHANGE_NAME, "2", null, "2".getBytes(StandardCharsets.UTF_8));
			channel.basicPublish(Constant.EXCHANGE_NAME, "3", null, "3".getBytes(StandardCharsets.UTF_8));
		} catch (Exception e) {
		    e.printStackTrace();
		}
	}
	
}

消费者:

public class ConsumerDemo01 {
	
	public static void main(String[] args) throws Exception {
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost(Constant.HOSTNAME);
		Connection connection = connectionFactory.newConnection();
		Channel channel = connection.createChannel();
		channel.exchangeDeclare(Constant.EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
		String queue = channel.queueDeclare().getQueue();
		channel.queueBind(queue, Constant.EXCHANGE_NAME, "");
		channel.basicConsume(queue, true, (consumerTag, message) -> System.out.println(new String(message.getBody(), StandardCharsets.UTF_8)), System.out::println);
	}
	
}

RPC

这里, 我们举一个官网的例子, 将发送32个数字给远程的主机计算斐波那契数列的结果, 后传递回来

  • 首先我们考虑使用什么样的交换机

这里我们选择默认的direct exchange, 明显这里不需要topic 也不需要广播, 只需要在两个主机之间进行数据交换而已, 所以使用direct exchange直接交换机

direct exchange : 完全根据key进行投递的,例如,绑定时设置了routing key”abc”,那么客户端提交的消息,只有设置了key”abc”的才会投递到队列。

在这里插入图片描述
客户端通过rpc_queue队列发送给服务端, 携带了两个参数 一个是另一个队列名字和每次请求的id(用于区分一次请求用的)

服务端计算完斐波那契数列的总数后将结果借助另一个队列发送回来, 携带着结果

客户端验证下是否为是否为前一次发送的请求, 是的话直接把结果打印出来

根据上面这张图片我们能写入如下代码

客户端源码:

@Slf4j(topic = "c.RpcPublisherDemo01")
public class RpcPublisherDemo01 implements AutoCloseable {
	
	private final Connection connection;
	private final Channel channel;
	
	public RpcPublisherDemo01() throws IOException, TimeoutException {
		final ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost(Constant.HOSTNAME);
		connection = connectionFactory.newConnection();
		channel = connection.createChannel();
	}
	
	public static void main(String[] args) throws InterruptedException {
		log.info("publisher start ...");
		try (final RpcPublisherDemo01 publisher = new RpcPublisherDemo01()) {
			for (int i = 0; i < 32; i++) {
				final String result = publisher.call(Integer.toString(i));
				System.out.println("fib(" + i + ") Got '" + result + "'");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		TimeUnit.SECONDS.sleep(100);
	}
	
	private String call(String message) throws IOException, InterruptedException {
		// 创建通道
		final String queueName = channel.queueDeclare().getQueue();
		
		// 创建props, 发送两个参数
		final String correlationId = UUID.randomUUID().toString();
		final AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().correlationId(correlationId).replyTo(queueName).build();
		
		// 发送数字到消费者, 发送到直接交换机的 routingKey 路由键上
		String rpcQueueName = "my_rpc_queue_name";
		channel.basicPublish("", rpcQueueName, properties, message.getBytes(StandardCharsets.UTF_8));
		
		// 收到消费者消息, 并打印出来
		// 定义一个阻塞队列, 如果队列中为空, 则阻塞等待
		final ArrayBlockingQueue<String> response = new ArrayBlockingQueue<>(1);
		final String cTag = channel.basicConsume(queueName, false, (consumerTag, deliver) -> {
			if (correlationId.equals(deliver.getProperties().getCorrelationId())) {
				response.offer(new String(deliver.getBody(), StandardCharsets.UTF_8));
			}
		}, consumerTag -> {
		});
		
		final String result = response.take();
		channel.basicCancel(cTag);
		return result;
	}
	
	@Override
	public void close() throws Exception {
		channel.close();
		connection.close();
	}
}

服务端源码:

@Slf4j(topic = "c.RpcConsumerDemo01")
public class RpcConsumerDemo01 {
	
	private static final String RPC_QUEUE_NAME = "my_rpc_queue_name";
	
	private static int fib(int n) {
		if (n == 0) {
			return 0;
		}
		if (n == 1) {
			return 1;
		}
		return fib(n - 1) + fib(n - 2);
	}
	
	public static void main(String[] args) throws Exception {
		log.info("publisher start ...");
		final ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost(Constant.HOSTNAME);
		
		final Connection connection = connectionFactory.newConnection();
		final Channel channel = connection.createChannel();
		channel.queueDeclare(RPC_QUEUE_NAME, false, false, false, null);
		final Object monitor = new Object();
		channel.basicConsume(RPC_QUEUE_NAME, false, (consumerTag, message) -> {
			AMQP.BasicProperties properties = null;
			int result = 0;
			try {
				properties = new AMQP.BasicProperties().builder().correlationId(message.getProperties().getCorrelationId()).build();
				final int number = Integer.parseInt(new String(message.getBody(), StandardCharsets.UTF_8));
				result = fib(number);
				log.info("current ThreadName is {}, recv number is {}, result is {}", Thread.currentThread().getName(), number, result);
			} catch (NumberFormatException e) {
				e.printStackTrace();
			} finally {
				channel.basicPublish("", message.getProperties().getReplyTo(), properties, Integer.toString(result).getBytes(StandardCharsets.UTF_8));
				channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
				// 这里面的代码不一定是立即执行的, 它是异步代码块, 可能延后执行, 所以要有个信号通知主线程, 告诉它我执行完了, 你别阻塞了
				synchronized (monitor) {
					monitor.notify();
				}
			}
		}, consumerTag -> {
		});
		// 主线程阻塞
		synchronized (monitor) {
			monitor.wait();
		}
	}
	
}

官方提供的源码有使用到同步代码块的功能其目的在服务端我们消费消息的时候可能不会是阻塞状态, 是异步执行的, 既然它是异步的, 那么如果不使用this.wait() 方法阻塞住线程, 那么线程会直接执行完毕销毁掉, 所以需要在线程执行的结尾加上阻塞等待功能, 在异步方法的结尾加上this.notify()方法, 通知异步功能执行完毕可以结束线程了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值