RabbitMq的学习总结

  1. 问题需求:最近项目遇到了一个问题,调用第三方接口响应时间过长.主要问题出现在传给第三方pdf文件网络url地址的时候,那边会对pdf文件进行一个下载处理.但是由于文件过大的问题 可能8M左右吧,所以特别耗时,所以想着有没有可以用到一种 异步回调且任务队列处理的工具

  2. 解决方案:
    1.多线程文件下载:第三方单线程下载文件 存在效率低的情况,采取多线程的方式将pdf文件分成3段,交给3个线程下载,最后拼接起来即可.
    2.异步解决方案1:假设第三方处理结果不依赖与PDF文件的话,采取异步的方式,先将处理结果返回,同时开启一个线程进行文件下载.
    3.异步解决方案2:假设第三方返回结果不依赖PDF文件下载,第三方将PDF文件下载任务作为生产者发布的任务消息放到队列中,由消费者读取下载消息任务,完成下载.(对于整体协调性较小)
    4.异步解决方案方案3:假设情况如上相同,将第三方接口消息

  3. 搭建rabbitmq消息队列
    https://blog.csdn.net/acmman/article/details/79371312 参考地址,搭建完成后的页面,登录账号密码进入主页面
    RabbitMq的学习总结_第1张图片
    RabbitMq的学习总结_第2张图片
    这个页面可以看到 队列,交换机,队列使用的通道,账户管理等
    同时点击下面的Tutorials就可以进入rabbit官网教程了

  4. 6个案例学习
    官网的案例中包含了简单的HelloWorld,2个工作队列(消费者)平均处理消息,主体/订阅,路由,主体,RPC远程接口调用.具体案例及代码github上也有,但我在调试中消费者处理消息的地方有问题,所以重写了一份

  5. RabbitMq相关概念介绍
    rabbitmq是一款消息代理和转发工具,可以理解为邮局的作用.针对系统中出现的高并发,高负载的请求,将请求放到rabbit队列中可以有效的减少同一时间系统的负载,例如淘宝订单等 非同步返回的请求,可以放到队列中去,通过消费者 读取消息 处理完成后,再放回到队列中去.
    生产者:将消息放入队列中
    消费者:读取队列中的消息
    队列:消息存储的地方
    持久性:队列的持久性 通道的持久性是保证 消息永不丢失的关键之一,即使rabbitmq异常关闭,rabbitmq中的队列依然存在,不会丢失.
    消息确认机制:防止消息丢失的另一个机制,可以手动设置 是否自动确认.当消费者读取消息后,在没有确认之前,因为异常原因中断,消息不丢失,就是因为消息确认机制的存在.
    通道:与队列创建连接的通道
    交换机:生产者将消息放入队列的 交换,发布/订阅 模式就是通过交换机将所有消息发布给 所有消费者的.
    路由:消息选择性发给消费者的路径选择
    主题:消息选择性发给消费者.可以通过*,*,*类似正则表达式的方式发给消费者.类似听音乐软件中 古典音乐或者流行音乐的选择.
    RPC:远程接口调用(调用第三方接口 将处理结果放到队列中,发送方在收不到消息前一直堵塞,等待从队列从读取 相应结果)

  6. HelloWorld案例
    主要用来了解 消费者及生产者 发送消息及接受消息的原理.
    消费者类似Socket中的服务端,连接rabbit,设置通道及队列,监听该队列中是否存在消息,存在则读取出来完成,确认后,该笔消息清空.
    Receiver.java

package com.bxd.app.amqp.HelloWorld;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;


public class Receiver {
	private static final String QUEUE_NAME = "hello";// 声明要用的队列名称

	public static void main(String[] args) {
		receive();
	}

	public static void receive() {
		ConnectionFactory connectionFactory = new ConnectionFactory();// rabbitmq连接工具
		connectionFactory.setHost("localhost");// 设置连接主机地址
		connectionFactory.setPort(5672);// AMQP协议端口号
		connectionFactory.setUsername("guest");
		connectionFactory.setPassword("guest");
		try {
			Connection connection = connectionFactory.newConnection();// 创建一个新的连接
			Channel channel = connection.createChannel();// 创建一个新的通道
			channel.queueDeclare(QUEUE_NAME, false, false, false, null);// 通道队列声明
																		// 队列名称
																		// 是否持久化
																		// 是否独占
																		// 是否自动删除
																		// 额外参数
			System.out.println("[*]等待消息.退出按ctrl+c");
			/**
			 * DefaultConsumer实现了Consumer接口,通过传入一个通道
			 * 告诉服务器我们需要那个通道的消息,如果频道中有消息,就会执行回调函数handlerDelivery
			 */
			Consumer consumer = new DefaultConsumer(channel) {
				public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
						byte[] body) throws IOException {
					String message = new String(body, "utf-8");
					int i = 0;
					while (i < 3) {
						try {
							Thread.sleep(1000);
							System.out.println("模拟调用接口耗时时间:" + i + "s");
						} catch (InterruptedException ex) {
							ex.printStackTrace();
						}
						i++;
					}
					System.out.println("Customer Received " + message + " ");

				}
			};
			// 自动回复队列应答 -- RabbitMq中的消息应答机制
			channel.basicConsume(QUEUE_NAME, true, consumer);

		} catch (IOException | TimeoutException ex) {
			ex.printStackTrace();
		} // 新建一个连接

	}

}

Sender .java

package com.bxd.app.amqp.HelloWorld;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;


public class Sender {
	public static final String QUEUE_NAME = "hello";// 定义消息队列名称

	public static void main(String[] args) {
		connect_mqserver();
	}

	/**
	 * 连接mq消息代理服务器
	 */
	private static void connect_mqserver() {
		ConnectionFactory factory = new ConnectionFactory();// 创建mq连接池
		factory.setHost("localhost");// 设置连接主机地址
		factory.setPort(5672);// AMQP协议端口号
		factory.setUsername("guest");
		factory.setPassword("guest");
		try {
			Connection connection = factory.newConnection();// 创建新的连接
			Channel channel = connection.createChannel();// 创建新的通道
			channel.queueDeclare(QUEUE_NAME, false, false, false, null);// 队列名称,是否设置为持久化队列,是否设置为独占队列,自动删除设置,其他构造参数
			String message = "Hello World";//
			channel.basicPublish("", QUEUE_NAME, null, message.getBytes());// 交换机
																			// 路由地址
																			// 其他属性的参数
																			// 消息主体
			System.out.println("[X] Sent '" + message + "'");// 消息发送成功 记录日志
			channel.close();// 关闭通道
			connection.close();// 关闭连接
		} catch (IOException | TimeoutException ex) {
			ex.printStackTrace();
		}

		factory.clone();// 关闭工厂

	}

}

/
生产者创建:创建rabbit连接工厂–>设置ip 端口 账号密码等参数—>从连接池工厂创建一个新的连接–> 声明一个连接的通道----> 通道中使用的队列声明–> 创建消息 发送消息–>关闭相关连接

消费者创建:创建rabbit连接工厂–>设置ip 端口 账号密码等参数—>从连接池工厂创建一个新的连接–> 声明一个连接的通道----> 通道中使用的队列声明–> 收到消息 -->处理消息—>消息确认完成–>关闭相关连接

  1. 工作队列
    上面只是简答的介绍了生产者和消费者的概念及如何发送和接受消息.但是若大量的消息放入队列中,单单一个消费者肯定 不够用,所以工作队列应用而生,简单的将就是将消息平均的分配给2个消费者,但是必须保证2者同样繁忙,不会出现分配不公平的现象(比如同样是处理消息,A工作队列处理的任务需要耗时10S,而B工作队列中处理的任务需要耗时1S,在相同的时间里,如果总是按照决定数量平均的方式分配,显然很不合理,所以出现了队列中公平派遣的意义)

    NewTask.java

package com.bxd.app.amqp.WorkQueue;

import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.MessageProperties;


public class NewTask {
	public static final String QUEUE_NAME = "task_queue";// 定义消息队列名称

	public static void main(String[] args) {
		@SuppressWarnings("resource")
		Scanner scanner = new Scanner(System.in);
		String string = scanner.nextLine();
		while (string != null) {
			System.out.println("参数" + string + "发送消息到队列中");
			connect_mqserver(string);
			System.out.print("请输入参数:");
			string = scanner.nextLine();
		}
	}

	/**
	 * 连接mq消息代理服务器
	 */
	private static void connect_mqserver(String argv) {
		ConnectionFactory factory = new ConnectionFactory();// 创建mq连接池
		factory.setHost("localhost");// 设置连接主机地址
		factory.setPort(5672);// AMQP协议端口号
		factory.setUsername("guest");
		factory.setPassword("guest");
		try {
			Connection connection = factory.newConnection();// 创建新的连接
			Channel channel = connection.createChannel();// 创建新的通道
			boolean durable = true;// 设置队列是否持久化 true重启队列依然存在
			channel.queueDeclare(QUEUE_NAME, durable, false, false, null);// 队列名称,是否设置为持久化队列,是否设置为独占队列,自动删除设置,其他构造参数
			String message = String.join("", argv);// 随意拼接参数
			// 消息持久性设置
			channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());// 交换机
																												// 路由地址
																												// 其他属性的参数
																												// 消息主体
			System.out.println("[X] Sent '" + message + "'");// 消息发送成功 记录日志
			channel.close();// 关闭通道
			connection.close();// 关闭连接
		} catch (IOException | TimeoutException ex) {
			ex.printStackTrace();
		}

		factory.clone();// 关闭工厂

	}

}

Worker1.java

package com.bxd.app.amqp.WorkQueue;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;


public class Worker1 {
	private static final String QUEUE_NAME = "task_queue";// 声明要用的队列名称

	public static void main(String[] args) throws IOException, TimeoutException {
		receive();
	}

	public static void receive() throws IOException, TimeoutException {
		ConnectionFactory connectionFactory = new ConnectionFactory();// rabbitmq连接工具
		connectionFactory.setHost("localhost");// 设置连接主机地址
		connectionFactory.setPort(5672);// AMQP协议端口号
		connectionFactory.setUsername("guest");
		connectionFactory.setPassword("guest");
		final Connection connection = connectionFactory.newConnection();// 创建一个新的连接
		final Channel channel = connection.createChannel();// 创建一个新的通道
		channel.basicQos(1);// 一次只接受一条未包含的消息
		channel.queueDeclare(QUEUE_NAME, true, false, false, null);// 通道队列声明
																	// 队列名称
																	// 是否持久化
																	// 是否独占
																	// 是否自动删除
																	// 额外参数
		System.out.println("[*1]等待消息.退出按ctrl+c");
		channel.basicQos(1);// 服务器通道 最大接受数量
		/**
		 * DefaultConsumer实现了Consumer接口,通过传入一个通道
		 * 告诉服务器我们需要那个通道的消息,如果频道中有消息,就会执行回调函数handlerDelivery
		 */
		Consumer consumer = new DefaultConsumer(channel) {
			public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
					byte[] body) throws IOException {
				String message = new String(body, "utf-8");
				System.out.println("Customer1 Received " + message + " ");
				try {
					dowork(message);
				} finally {// 执行完毕 确认消息通知
					channel.basicAck(envelope.getDeliveryTag(), true);
				}

			}
		};
		// 自动回复队列应答 -- RabbitMq中的消息应答机制(消息确认机制,完成消息处理)
		channel.basicConsume(QUEUE_NAME, false, consumer);

	}

	/**
	 * @param str
	 *            根据传入参数的点数个数,决定休眠的时间
	 */
	public static void dowork(String str) {
		int i = 0;
		for (Character c : str.toCharArray()) {
			// 将字符串 转换为字符数组
			if (c == '.') {// 如果字符为. 那么休眠1秒
				try {
					i++;
					Thread.sleep(1000l);
					System.out.println("工作" + i + "秒");
				} catch (InterruptedException ex) {
					ex.printStackTrace();
				} // 休眠一秒

			}
		}
		System.out.println("工作完成,用时" + i + "秒");

	}

}

Worker2.java

package com.bxd.app.amqp.WorkQueue;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;


public class Worker2 {
	private static final String QUEUE_NAME = "task_queue";// 声明要用的队列名称

	public static void main(String[] args) {
		receive();
	}

	public static void receive() {
		ConnectionFactory connectionFactory = new ConnectionFactory();// rabbitmq连接工具
		connectionFactory.setHost("localhost");// 设置连接主机地址
		connectionFactory.setPort(5672);// AMQP协议端口号
		connectionFactory.setUsername("guest");
		connectionFactory.setPassword("guest");
		try {
			Connection connection = connectionFactory.newConnection();// 创建一个新的连接
			Channel channel = connection.createChannel();// 创建一个新的通道
			channel.basicQos(1);// 一次只接受一条未包含的消息
			channel.queueDeclare(QUEUE_NAME, true, false, false, null);// 通道队列声明
																		// 队列名称
																		// 是否持久化
																		// 是否独占
																		// 是否自动删除
																		// 额外参数
			System.out.println("[*2]等待消息.退出按ctrl+c");
			/**
			 * DefaultConsumer实现了Consumer接口,通过传入一个通道
			 * 告诉服务器我们需要那个通道的消息,如果频道中有消息,就会执行回调函数handlerDelivery
			 */
			Consumer consumer = new DefaultConsumer(channel) {
				public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
						byte[] body) throws IOException {
					String message = new String(body, "utf-8");
					System.out.println("Customer2 Received " + message + " ");
					try {
						dowork(message);
					} finally {// 执行完毕 确认消息通知
						channel.basicAck(envelope.getDeliveryTag(), true);
					}
				}
			};
			// 自动回复队列应答 -- RabbitMq中的消息应答机制 自动应答机制开启关闭,true 表示开启,false表示关闭 默认开启
			channel.basicConsume(QUEUE_NAME, false, consumer);

		} catch (IOException | TimeoutException ex) {
			ex.printStackTrace();
		}

	}

	/**
	 * @param str
	 *            根据传入参数的点数个数,决定休眠的时间
	 */
	public static void dowork(String str) {
		int i = 0;
		for (Character c : str.toCharArray()) {
			// 将字符串 转换为字符数组
			if (c == '.') {// 如果字符为. 那么休眠1秒
				try {
					i++;
					Thread.sleep(1000l);
					System.out.println("工作" + i + "秒");
				} catch (InterruptedException ex) {
					ex.printStackTrace();
				} // 休眠一秒

			}
		}
		System.out.println("工作完成,用时" + i + "秒");

	}

}

相比HelloWorld,这里有NewTask发布任务,Worker2+Worker1处理消息,同时通过channel.basicQos(1);方法设置每个队列 一次只接受一条消息,这样保证了公平派遣的原则,而不会出现繁忙程度不一样的情况.同时工作队列中关于 持久性,消息确认机制,公平派遣都有详细的介绍.

  1. 发布/订阅
    假设出现 需要一条消息发布给所有人的情况时候怎么办?好办.使用发布/订阅模式,此处引入了 交换机Exchange的概念,这里的发布/订阅 及后面都有用到,通过设置交换机的不同参数 应对不同的场景.这里用到的fanout参数 ,前面 的交换机都是默认的"",这里需要手动设置下,通过消费者和生产者选择好 相同的交换机及模式 所有的消费者都可以收到生产者发布的消息,以实现订阅消息的模式.具体代码如下
    EmitLog.java 消息发布类
package com.bxd.app.amqp.PublishSubscribe;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;


public class EmitLog {
	// 1.声明交换机名称 不使用默认名称
	private static final String EXCHANGE_NAM = "logs";

	public static void main(String[] args) {
		// 2.创建mq连接工厂
		ConnectionFactory factory = new ConnectionFactory();
		// 3.声明连接所需基本参数 账号 密码 端口号 地址
		factory.setHost("localhost");
		try {
			// 4.获取mq连接
			Connection connection = factory.newConnection();
			// 5.声明一个通道
			Channel channel = connection.createChannel();
			// 6.声明交换类型
			channel.exchangeDeclare(EXCHANGE_NAM, "fanout");
			// 7.创建扇出消息信息
			String message = args.length < 1 ? "信息:Hello World" : String.join("", args);
			// 8.提交消息信息
			channel.basicPublish(EXCHANGE_NAM, "", null, message.getBytes("UTF-8"));
			System.out.println("[x] Sent" + message + " ");
		} catch (Exception ex) {
			// TODO: handle exception
		}

	}

}

ReceiveLogs.java

package com.bxd.app.amqp.PublishSubscribe;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;


public class ReceiveLogs {
	// 1.声明私有成员变量 交换机
	private static final String EXCHANGE_NAME = "logs";

	public static void main(String[] args) {
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("localhost");
		try {
			Connection connection = factory.newConnection();
			Channel channel = connection.createChannel();
			// 2.声明交换机
			channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
			// 3.获取所有的队列
			String queue = channel.queueDeclare().getQueue();
			System.out.println("获取声明的队列:"+queue);
			// 4.绑定队列与交换机之间的关系
			channel.queueBind(queue, EXCHANGE_NAME, "");
			System.out.println("[*]等待订阅消息发布,Ctrl+C退出消息");
			// 5.处理队列中的消息
			/**
			 * DefaultConsumer实现了Consumer接口,通过传入一个通道
			 * 告诉服务器我们需要那个通道的消息,如果频道中有消息,就会执行回调函数handlerDelivery
			 */
			Consumer consumer = new DefaultConsumer(channel) {
				public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
						byte[] body) throws IOException {
					String message = new String(body, "utf-8");
					System.out.println(" Received   Logs" + message + " ");
					try {
						dowork(message);
					} finally {// 执行完毕 确认消息通知
						channel.basicAck(envelope.getDeliveryTag(), true);
					}

				}
			};
			// 自动回复队列应答 -- RabbitMq中的消息应答机制(消息确认机制,完成消息处理)
			channel.basicConsume(queue, false, consumer);

		} catch (IOException ex) {
			ex.printStackTrace();
		} catch (TimeoutException ex) {
			ex.printStackTrace();
		}
	}

	/**
	 * @param str
	 *            根据传入参数的点数个数,决定休眠的时间
	 */
	public static void dowork(String str) {
		int i = 0;
		for (Character c : str.toCharArray()) {
			// 将字符串 转换为字符数组
			if (c == '.') {// 如果字符为. 那么休眠1秒
				try {
					i++;
					Thread.sleep(1000l);
					System.out.println("工作" + i + "秒");
				} catch (InterruptedException ex) {
					ex.printStackTrace();
				} // 休眠一秒

			}
		}
		System.out.println("工作完成,用时" + i + "秒");

	}
}

使用方法时:运行多个ReceiveLogs类,然后运行EmitLog消息发布,可以看到所有的ReceiveLogs都收到了消息

  1. 路由
    在上面一个案例 我们讲到了关于如何使用发布订阅模式,但是如果 个性化选择一个东西,比如关注了我的公众号发布了一些消息,只有订阅的人可以看到 如何实现?应运而生的就是 路由这个知识点,主要是在 交换机的基础上 一种有效补充,可以选择性发布消息 ,而不是所有的人都能收到.同时 消费者的队列也是临时产生的,随着消费者的产生而灭亡的,非持久性的.上面的发布订阅 中消费者使用队列也是这样的. 交换机使用的模式是direct
    EmitLogRouting.java
package com.bxd.app.amqp.Routing;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;


public class EmitLogRouting {
	// 1.声明交换机名称 不使用默认名称
	private static final String EXCHANGE_NAM = "routing_logs";
	// 声明路由名称
	private static final String ROUTING_NAME = "NBA";

	public static void main(String[] args) {
		// 2.创建mq连接工厂
		ConnectionFactory factory = new ConnectionFactory();
		// 3.声明连接所需基本参数 账号 密码 端口号 地址
		factory.setHost("localhost");
		try {
			// 4.获取mq连接
			Connection connection = factory.newConnection();
			// 5.声明一个通道
			Channel channel = connection.createChannel();
			// 6.声明交换类型
			channel.exchangeDeclare(EXCHANGE_NAM, "direct");
			// 7.创建扇出消息信息
			String message = args.length < 1 ? "信息:Hello World" : String.join("", args);
			// 8.提交消息信息(添加路由key信息)
			channel.basicPublish(EXCHANGE_NAM, ROUTING_NAME, null, message.getBytes("UTF-8"));
			System.out.println("[x] Sent" + message + " ");
		} catch (Exception ex) {
			// TODO: handle exception
		}

	}

}

ReceiveCBALogs.java

package com.bxd.app.amqp.Routing;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;


public class ReceiveCBALogs {
	// 1.声明私有成员变量 交换机
	private static final String EXCHANGE_NAME = "routing_logs";
	// 声明路由信息
	private static final String ROUTING_NAME = "CBA";

	public static void main(String[] args) {
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("localhost");
		try {
			Connection connection = factory.newConnection();
			Channel channel = connection.createChannel();
			// 2.声明交换机
			channel.exchangeDeclare(EXCHANGE_NAME, "direct");
			// 3.获取所有的队列
			String queue = channel.queueDeclare().getQueue();
			System.out.println("获取声明的队列:" + queue);
			// 4.绑定队列与交换机之间的关系(队列与交换机之间的关系 可以绑定多个)
			channel.queueBind(queue, EXCHANGE_NAME, "NBA");
			channel.queueBind(queue, EXCHANGE_NAME, ROUTING_NAME);
			System.out.println("[*]等待订阅NBA消息发布,Ctrl+C退出消息");
			// 5.处理队列中的消息
			/**
			 * DefaultConsumer实现了Consumer接口,通过传入一个通道
			 * 告诉服务器我们需要那个通道的消息,如果频道中有消息,就会执行回调函数handlerDelivery
			 */
			Consumer consumer = new DefaultConsumer(channel) {
				public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
						byte[] body) throws IOException {
					String message = new String(body, "utf-8");
					System.out.println(" Received   Logs" + message + " ");
					try {
						dowork(message);
					} finally {// 执行完毕 确认消息通知
						channel.basicAck(envelope.getDeliveryTag(), true);
					}

				}
			};
			// 自动回复队列应答 -- RabbitMq中的消息应答机制(消息确认机制,完成消息处理)
			channel.basicConsume(queue, false, consumer);

		} catch (IOException ex) {
			ex.printStackTrace();
		} catch (TimeoutException ex) {
			ex.printStackTrace();
		}
	}

	/**
	 * @param str
	 *            根据传入参数的点数个数,决定休眠的时间
	 */
	public static void dowork(String str) {
		int i = 0;
		for (Character c : str.toCharArray()) {
			// 将字符串 转换为字符数组
			if (c == '.') {// 如果字符为. 那么休眠1秒
				try {
					i++;
					Thread.sleep(1000l);
					System.out.println("工作" + i + "秒");
				} catch (InterruptedException ex) {
					ex.printStackTrace();
				} // 休眠一秒

			}
		}
		System.out.println("工作完成,用时" + i + "秒");

	}
}

package com.bxd.app.amqp.Routing;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;


public class ReceiveNBALogs {
	// 1.声明私有成员变量 交换机
	private static final String EXCHANGE_NAME = "routing_logs";
	// 声明路由信息
	private static final String ROUTING_NAME = "NBA";

	public static void main(String[] args) {
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("localhost");
		try {
			Connection connection = factory.newConnection();
			Channel channel = connection.createChannel();
			// 2.声明交换机
			channel.exchangeDeclare(EXCHANGE_NAME, "direct");
			// 3.获取所有的队列
			String queue = channel.queueDeclare().getQueue();
			System.out.println("获取声明的队列:" + queue);
			// 4.绑定队列与交换机之间的关系
			channel.queueBind(queue, EXCHANGE_NAME, ROUTING_NAME);
			System.out.println("[*]等待订阅CBA消息发布,Ctrl+C退出消息");
			// 5.处理队列中的消息
			/**
			 * DefaultConsumer实现了Consumer接口,通过传入一个通道
			 * 告诉服务器我们需要那个通道的消息,如果频道中有消息,就会执行回调函数handlerDelivery
			 */
			Consumer consumer = new DefaultConsumer(channel) {
				public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
						byte[] body) throws IOException {
					String message = new String(body, "utf-8");
					System.out.println(" Received   Logs" + message + " ");
					try {
						dowork(message);
					} finally {// 执行完毕 确认消息通知
						channel.basicAck(envelope.getDeliveryTag(), true);
					}

				}
			};
			// 自动回复队列应答 -- RabbitMq中的消息应答机制(消息确认机制,完成消息处理)
			channel.basicConsume(queue, false, consumer);

		} catch (IOException ex) {
			ex.printStackTrace();
		} catch (TimeoutException ex) {
			ex.printStackTrace();
		}
	}

	/**
	 * @param str
	 *            根据传入参数的点数个数,决定休眠的时间
	 */
	public static void dowork(String str) {
		int i = 0;
		for (Character c : str.toCharArray()) {
			// 将字符串 转换为字符数组
			if (c == '.') {// 如果字符为. 那么休眠1秒
				try {
					i++;
					Thread.sleep(1000l);
					System.out.println("工作" + i + "秒");
				} catch (InterruptedException ex) {
					ex.printStackTrace();
				} // 休眠一秒

			}
		}
		System.out.println("工作完成,用时" + i + "秒");

	}
}

路由EmitLogRouting发布的是NBA内容,只有ReceiveNBALog消费者可以收到

  1. 主题模式
    针对上面这种情况,如果一个人关注的篮球板块 怎么办?这里就需要主体模式:通过 xxx.xxx.xxx的形式 来匹配相关的主体,可以使用* 或者#代替. * 代替一个单词,#代替0个或者多个单词.具体代码如下,同时交换机模式使用topic
    EmitLogTopics.java
package com.bxd.app.amqp.Topics;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;


public class EmitLogTopics {
	// 1.声明交换机名称 不使用默认名称
	private static final String EXCHANGE_NAM = "topic_logs";
	// 主体路由由3个单词组成
	private static final String ROUTING_NAME = "lazy.orange.rabbit";

	public static void main(String[] args) {
		// 2.创建mq连接工厂
		ConnectionFactory factory = new ConnectionFactory();
		// 3.声明连接所需基本参数 账号 密码 端口号 地址
		factory.setHost("localhost");
		try {
			// 4.获取mq连接
			Connection connection = factory.newConnection();
			// 5.声明一个通道
			Channel channel = connection.createChannel();
			// 6.声明交换类型
			channel.exchangeDeclare(EXCHANGE_NAM, "topic");
			// 7.创建扇出消息信息
			String message = args.length < 1 ? "信息:Hello World" : String.join("", args);
			// 8.提交消息信息(添加路由key信息)
			channel.basicPublish(EXCHANGE_NAM, ROUTING_NAME, null, message.getBytes("UTF-8"));
			System.out.println("[x] Sent" + message + " ");
		} catch (Exception ex) {
			// TODO: handle exception
		}

	}

}

ReceiveLazyLogs.java

package com.bxd.app.amqp.Topics;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;


public class ReceiveLazyLogs {
	// 1.声明私有成员变量 交换机
	private static final String EXCHANGE_NAME = "topic_logs";
	// 声明路由信息
	private static final String ROUTING_NAME = "lazy.*.*";

	public static void main(String[] args) {
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("localhost");
		try {
			Connection connection = factory.newConnection();
			Channel channel = connection.createChannel();
			// 2.声明交换机
			channel.exchangeDeclare(EXCHANGE_NAME, "topic");
			// 3.获取所有的队列
			String queue = channel.queueDeclare().getQueue();
			System.out.println("获取声明的队列:" + queue);
			// 4.绑定队列与交换机之间的关系
			channel.queueBind(queue, EXCHANGE_NAME, ROUTING_NAME);
			System.out.println("[*]等待主体lazy消息发布,Ctrl+C退出消息");
			// 5.处理队列中的消息
			/**
			 * DefaultConsumer实现了Consumer接口,通过传入一个通道
			 * 告诉服务器我们需要那个通道的消息,如果频道中有消息,就会执行回调函数handlerDelivery
			 */
			Consumer consumer = new DefaultConsumer(channel) {
				public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
						byte[] body) throws IOException {
					String message = new String(body, "utf-8");
					System.out.println(" Received   Logs" + message + " ");
					try {
						dowork(message);
					} finally {// 执行完毕 确认消息通知
						channel.basicAck(envelope.getDeliveryTag(), true);
					}

				}
			};
			// 自动回复队列应答 -- RabbitMq中的消息应答机制(消息确认机制,完成消息处理)
			channel.basicConsume(queue, false, consumer);

		} catch (IOException ex) {
			ex.printStackTrace();
		} catch (TimeoutException ex) {
			ex.printStackTrace();
		}
	}

	/**
	 * @param str
	 *            根据传入参数的点数个数,决定休眠的时间
	 */
	public static void dowork(String str) {
		int i = 0;
		for (Character c : str.toCharArray()) {
			// 将字符串 转换为字符数组
			if (c == '.') {// 如果字符为. 那么休眠1秒
				try {
					i++;
					Thread.sleep(1000l);
					System.out.println("工作" + i + "秒");
				} catch (InterruptedException ex) {
					ex.printStackTrace();
				} // 休眠一秒

			}
		}
		System.out.println("工作完成,用时" + i + "秒");

	}
}

ReceiveOrangeLogs.java

package com.bxd.app.amqp.Topics;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;


public class ReceiveOrangeLogs {
	// 1.声明私有成员变量 交换机
	private static final String EXCHANGE_NAME = "topic_logs";
	// 声明路由信息
	private static final String ROUTING_NAME = "*.orange.*";

	public static void main(String[] args) {
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("localhost");
		try {
			Connection connection = factory.newConnection();
			Channel channel = connection.createChannel();
			// 2.声明交换机
			channel.exchangeDeclare(EXCHANGE_NAME, "topic");
			// 3.获取所有的队列
			String queue = channel.queueDeclare().getQueue();
			System.out.println("获取声明的队列:" + queue);
			// 4.绑定队列与交换机之间的关系(队列与交换机之间的关系 可以绑定多个)
			channel.queueBind(queue, EXCHANGE_NAME, ROUTING_NAME);
			System.out.println("[*]等待消orange主题消息,Ctrl+C退出消息");
			// 5.处理队列中的消息
			/**
			 * DefaultConsumer实现了Consumer接口,通过传入一个通道
			 * 告诉服务器我们需要那个通道的消息,如果频道中有消息,就会执行回调函数handlerDelivery
			 */
			Consumer consumer = new DefaultConsumer(channel) {
				public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
						byte[] body) throws IOException {
					String message = new String(body, "utf-8");
					System.out.println(" Received   Logs" + message + " ");
					try {
						dowork(message);
					} finally {// 执行完毕 确认消息通知
						channel.basicAck(envelope.getDeliveryTag(), true);
					}

				}
			};
			// 自动回复队列应答 -- RabbitMq中的消息应答机制(消息确认机制,完成消息处理)
			channel.basicConsume(queue, false, consumer);

		} catch (IOException ex) {
			ex.printStackTrace();
		} catch (TimeoutException ex) {
			ex.printStackTrace();
		}
	}

	/**
	 * @param str
	 *            根据传入参数的点数个数,决定休眠的时间
	 */
	public static void dowork(String str) {
		int i = 0;
		for (Character c : str.toCharArray()) {
			// 将字符串 转换为字符数组
			if (c == '.') {// 如果字符为. 那么休眠1秒
				try {
					i++;
					Thread.sleep(1000l);
					System.out.println("工作" + i + "秒");
				} catch (InterruptedException ex) {
					ex.printStackTrace();
				} // 休眠一秒

			}
		}
		System.out.println("工作完成,用时" + i + "秒");

	}
}

  1. RPC
    远程接口调用,针对第三方接口调用 且耗时很长的消息,将返回结果放到消息队列中,发送一致堵塞,直到队列中收到消息.

    RPCServer.java

package com.bxd.app.amqp.RPC;

import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Envelope;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class RPCServer {

	private static final String RPC_QUEUE_NAME = "rpc_queue";

	// 具体处理方法
	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[] argv) {
		// 建立连接、通道,并声明队列
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("localhost");

		Connection connection = null;
		try {
			connection = factory.newConnection();
			final Channel channel = connection.createChannel();

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

			channel.basicQos(1);

			System.out.println(" [x] Awaiting RPC requests");

			Consumer consumer = new DefaultConsumer(channel) {
				@Override
				public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
						byte[] body) throws IOException {
					AMQP.BasicProperties replyProps = new AMQP.BasicProperties.Builder()
							.correlationId(properties.getCorrelationId()).build();

					String response = "";

					try {
						String message = new String(body, "UTF-8");
						int n = Integer.parseInt(message);

						System.out.println(" [.] fib(" + message + ")");
						response += fib(n);
					} catch (RuntimeException e) {
						System.out.println(" [.] " + e.toString());
					} finally {
						// 返回处理结果队列
						channel.basicPublish("", properties.getReplyTo(), replyProps, response.getBytes("UTF-8"));
						// 确认消息,已经收到后面参数
						// multiple:是否批量.true:将一次性确认所有小于envelope.getDeliveryTag()的消息。
						channel.basicAck(envelope.getDeliveryTag(), false);
						// RabbitMq consumer worker thread notifies the RPC
						// server owner thread
						synchronized (this) {
							this.notify();
						}
					}
				}
			};
			// 取消自动确认
			boolean autoAck = false;
			channel.basicConsume(RPC_QUEUE_NAME, autoAck, consumer);
			// Wait and be prepared to consume the message from RPC client.
			while (true) {
				synchronized (consumer) {
					try {
						consumer.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		} catch (IOException | TimeoutException e) {
			e.printStackTrace();
		} finally {
			if (connection != null)
				try {
					connection.close();
				} catch (IOException _ignore) {
				}
		}
	}
}

RPCClient.java

package com.bxd.app.amqp.RPC;

import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Envelope;

import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeoutException;

public class RPCClient {

    private Connection connection;
    private Channel channel;
    private String requestQueueName = "rpc_queue";
    private String replyQueueName;

    public RPCClient() throws IOException, TimeoutException {
        //建立一个连接和一个通道,并为回调声明一个唯一的'回调'队列
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");

        connection = factory.newConnection();
        channel = connection.createChannel();
        //定义一个临时变量的接受队列名    
        replyQueueName = channel.queueDeclare().getQueue();
    }
    //发送RPC请求  
    public String call(String message) throws IOException, InterruptedException {
         //生成一个唯一的字符串作为回调队列的编号
        String corrId = UUID.randomUUID().toString();
        //发送请求消息,消息使用了两个属性:replyto和correlationId
        //服务端根据replyto返回结果,客户端根据correlationId判断响应是不是给自己的
        AMQP.BasicProperties props = new AMQP.BasicProperties.Builder().correlationId(corrId).replyTo(replyQueueName)
                .build();

        //发布一个消息,requestQueueName路由规则
        channel.basicPublish("", requestQueueName, props, message.getBytes("UTF-8"));

        //由于我们的消费者交易处理是在单独的线程中进行的,因此我们需要在响应到达之前暂停主线程。
        //这里我们创建的 容量为1的阻塞队列ArrayBlockingQueue,因为我们只需要等待一个响应。
        final BlockingQueue response = new ArrayBlockingQueue(1);

        // String basicConsume(String queue, boolean autoAck, Consumer callback)
        channel.basicConsume(replyQueueName, true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
                    byte[] body) throws IOException {
                //检查它的correlationId是否是我们所要找的那个
                if (properties.getCorrelationId().equals(corrId)) {
                    //如果是,则响应BlockingQueue
                    response.offer(new String(body, "UTF-8"));
                }
            }
        });

        return response.take();
    }

    public void close() throws IOException {
        connection.close();
    }

    public static void main(String[] argv) {
        RPCClient fibonacciRpc = null;
        String response = null;
        try {
            fibonacciRpc = new RPCClient();

            System.out.println(" [x] Requesting fib(30)");
            response = fibonacciRpc.call("30");
            System.out.println(" [.] Got '" + response + "'");
        } catch (IOException | TimeoutException | InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (fibonacciRpc != null) {
                try {
                    fibonacciRpc.close();
                } catch (IOException _ignore) {
                }
            }
        }
    }
}

RPCMain.java

package com.bxd.app.amqp.RPC;
 
public class RPCMain {
 
	public static void main(String[] args) throws Exception {
		RPCClient rpcClient = new RPCClient();
		System.out.println(" [x] Requesting getMd5String(abc)");   
		String response = rpcClient.call("abc");
		System.out.println(" [.] Got '" + response + "'");
		rpcClient.close();
	}
}

启动server端,使用Main,   调用client中的call方法,   server端的队列及client的队列  使用的同一个.RPC远程接口调用 主要适用于大型企业 内部个子系统之间的相互调用,防止行程信息孤岛,同时子系统之间调用注重效率,RPC基于TCP通讯协议,而传统企业使用的是HTTP通讯协议.所以RPC优势比较大

你可能感兴趣的:(Java)