RabbitMQ 的工作模式
简介
• MQ全称为Message Queue,消息队列是应用程序和应用程序之间的通信方法;
• RabbitMQ是开源的,实现了AMQP协议的,采用Erlang(面向并发编程语言)编写的,可复用的企业级消息系统;
• AMQP(高级消息队列协议)是一个异步消息传递所使用应用层协议规范,为面向消息中间件设计,基于此协议的客户端与消息中间件可以无视消息来源传递消息,不受客户端、消息中间件、不同的开发语言环境等条件的限制;
• 支持主流操作系统:Linux、Windows,MacOX等;
• 支持多种客户端开发语言:Java、Python、Ruby、.NET,PHP、C/C++、Node.js等
RabbitMQ实例
1.1. 简单模式
特点:一个生产者P发送消息到队列Q,一个消费者C接收
生产者实现步骤:
- 创建连接工厂
- 设置服务地址、端口、用户名、密码、vHost
- 从连接工厂中获取连接
- 从连接获取通道channel
- 使用channle 声明队列
- 使用channle 向队列发送消息
- 关闭channle
- 关闭连接
代码如下:
public class Send { private final static String QUEUE_NAME = "test_queue"; public static void main(String[] args) { //定义连接工厂 ConnectionFactory factory = new ConnectionFactory(); //设置服务地址 factory.setHost("127.0.0.1"); //端口 factory.setPort(5672); //设置 vhost ,用户名称,账号 factory.setVirtualHost("taotao"); factory.setUsername("taotao"); factory.setPassword("123456"); try { //通过工厂获取连接 Connection conn= factory.newConnection(); //连接中获取通道中 Channel channel = conn.createChannel(); //声明队列 channel.queueDeclare(QUEUE_NAME, false, false, false, null); //设置消息内容 String msg = "Hello World! - 02"; channel.basicPublish("", QUEUE_NAME, null, msg.getBytes()); System.out.println("Send :发送成功!"); channel.close(); conn.close(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
当生产者代码成功运行后,我们通过管理工具查看会发现一个队列,并且队列中有一条信息:
消费者实现步骤:
- 创建连接工厂
- 设置服务地址、端口、用户名、密码、vHost
- 从连接工厂中获取连接
- 从连接获取通道channel
- 使用channle 声明队列【可以不声明队列,但是要保证该队列存在,否则会报错】
- 定义队列消费者
- 监听队列
- 获取消息
代码如下:
public class Revc { private final static String QUEUE_NAME = "test_queue"; public static void main(String[] args) throws Exception { //定义连接工厂 ConnectionFactory factory = new ConnectionFactory(); //设置服务地址 factory.setHost("127.0.0.1"); //设置端口 factory.setPort(5672); //设置vhost 用户名称 密码 factory.setUsername("taotao"); factory.setPassword("123456"); factory.setVirtualHost("taotao"); //创建连接 Connection conn = factory.newConnection(); //从连接中获取通道 Channel channel = conn.createChannel(); //声明队列 channel.queueDeclare(QUEUE_NAME, false, false, false, null); //定义队列的消费者 QueueingConsumer queueingConsumer = new QueueingConsumer(channel); //监听队列 channel.basicConsume(QUEUE_NAME, true, queueingConsumer); //获取消息 while (true) { QueueingConsumer.Delivery delivery = queueingConsumer.nextDelivery(); String msg = new String(delivery.getBody()); System.out.println("Revc :"+msg); } } }
当消费者代码成功运行后,我们通过管理工具会发现队列中消息的数量从1变为0,这就说明消息被消费者获取以后,会被队列删除。
连接工具类:
/** * rabbitmq 工具类 * @author Administrator * */ public class ConnectionUtils { public static Connection getConnection() throws Exception { //创建连接工厂 ConnectionFactory factory = new ConnectionFactory(); //设置服务地址 factory.setHost("127.0.0.1"); //设置端口 factory.setPort(5672); //设置vhost factory.setVirtualHost("taotao"); //设置用户名称 factory.setUsername("taotao"); //设置密码 factory.setPassword("123456"); //从工厂中获取连接 Connection conn = factory.newConnection(); return conn; } }
1.2. 工作队列模式 Work Queue
特点:一个生产者,多个消费者,每个消费者获取到的消息唯一,多个消费者只有一个队列
生产者实现步骤【参考1.1】
public class Send { private final static String QUEUE_NAME = "test_queue_work"; public static void main(String[] args) { try { //获取连接 Connection conn = ConnectionUtils.getConnection(); //从连接中获取管道 Channel channel = conn.createChannel(); //声明队列 channel.queueDeclare(QUEUE_NAME, false, false, false, null); //设置消息 for(int i = 0; i < 50; i++) { String msg = "Holle Work ! -"+i; channel.basicPublish("", QUEUE_NAME, null,msg.getBytes()); Thread.sleep(10*i); } channel.close(); conn.close(); } catch(Exception e) { } } }
消费者实现步骤:
- 创建连接工厂
- 设置服务地址、端口、用户名、密码、vHost
- 从连接工厂中获取连接
- 从连接获取通道channel
- 使用channle 声明队列【可以不声明队列,但是要保证该队列存在,否则会报错】
- 定义队列消费者
- 监听队列并手动返回确认
- 获取消息【如果设置了手动返回确认则需要返回确消息】
public class Revc { private final static String QUEUE_NAME = "test_queue_work"; public static void main(String[] args) { try { //获取连接 Connection conn = ConnectionUtils.getConnection(); //从连接中获取管道 Channel channel = conn.createChannel(); //声明队列 channel.queueDeclare(QUEUE_NAME, false, false, false, null); //同一时刻服务器只会发一条消息费着 channel.basicQos(1); //定义队列消费者 QueueingConsumer consumer = new QueueingConsumer(channel); //监听队列,手动返回完成 channel.basicConsume(QUEUE_NAME, false,consumer); //获取消息 while(true) { QueueingConsumer.Delivery delivery = consumer.nextDelivery(); String msg = new String(delivery.getBody()); System.out.println("Revc-->"+msg); //返回确认状态 channel.basicAck(delivery.getEnvelope().getDeliveryTag(), true); Thread.sleep(10); } } catch(Exception e) { } } }
可以设置多个消费者。并设置其休眠时间不同。可以得出,休眠时间越长,获取消息越少。这也体现了work模式的【能者多劳】模式。
注意:设置能者多劳模式必须设置:同一时刻服务器只会发一条消息费者
channel.basicQos(1);
1.3. 发布/订阅模式Publish/Subscribe
特点:
1、 一个生产者发送的消息会被多个消费者获取。一个生产者、一个交换机、多个队列、多个消费者
2、 生产者:可以将消息发送到队列或者是交换机
3、 消费者:只能从队列中获取消息。
4、 如果消息发送到没有队列绑定的交换机上,那么消息将丢失。交换机不能存储消息,消息存储在队列中
生产者实现步骤:
- 创建连接工厂
- 设置服务地址、端口、用户名、密码、vHost
- 从连接工厂中获取连接
- 从连接获取通道channel
- 使用channle 声明交换机
- 发送消息到交换机
- 关闭channel
- 关闭连接
public class Send { private final static String EXCHANGE_NAME = "test_exchange_fanout"; public static void main(String[] args) throws Exception { // 获取到连接以及mq通道 Connection connection = ConnectionUtils.getConnection(); Channel channel = connection.createChannel(); // 声明exchange channel.exchangeDeclare(EXCHANGE_NAME, "fanout"); // 消息内容 String message = "Hello World!"; channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes()); System.out.println(" [x] Sent '" + message + "'"); channel.close(); connection.close(); } }
消费者实现步骤:
1. 创建连接工厂
2. 设置服务地址、端口、用户名、密码、vHost
3. 从连接工厂中获取连接
4. 从连接获取通道channel
5. 使用channle 声明队列【可以不声明队列,但是要保证该队列存在,否则会报错】
6. 将队列绑定到交换机
7. 定义队列消费者
8. 监听队列并手动返回确认
9. 获取消息【如果设置了手动返回确认则需要返回确消息】
public class Revc { private final static String QUEUE_NAME = "test_queue_work"; private final static String EXCHANGE_NAME = "test_exchange_fanout"; public static void main(String[] argv) throws Exception { //获取连接 Connection conn = ConnectionUtils.getConnection(); //从连接中获取管道 Channel ch = conn.createChannel(); //声明队列 ch.queueDeclare(QUEUE_NAME, false, false, false, null); //将队列绑定到交换机 ch.queueBind(QUEUE_NAME, EXCHANGE_NAME, ""); //定义队列消费者 QueueingConsumer consumer = new QueueingConsumer(ch); //监听队列,手动返回完成 ch.basicConsume(QUEUE_NAME, false,consumer); //获取消息 while(true) { QueueingConsumer.Delivery delivery = consumer.nextDelivery(); String msg = new String(delivery.getBody()); System.out.println(" [ Revc ]"+msg); Thread.sleep(100); ch.basicAck(delivery.getEnvelope().getDeliveryTag(), false); } } }
1.4.路由模式Routing
特点:生产者发送消息到交换机并且要指定路由key,消费者将队列绑定到交换机时需要指定路由key
生产者实现步骤:
- 创建连接工厂
- 设置服务地址、端口、用户名、密码、vHost
- 从连接工厂中获取连接
- 从连接获取通道channel
- 使用channle 声明交换机
- 发送消息到交换机并指定key
- 关闭channel
- 关闭连接
public class Send { private final static String EXCHANGE_NAME = "test_exchange_direct"; public static void main(String[] args) throws Exception { //获取连接 Connection conn = ConnectionUtils.getConnection(); //从连接中获取管道 Channel ch = conn.createChannel(); //声明exchange ch.exchangeDeclare(EXCHANGE_NAME, "direct"); //设置消息内容 String msg = "Hello Work !"; ch.basicPublish(EXCHANGE_NAME, "key", null,msg.getBytes()); System.out.println(" [x] Sent '" + msg + "'"); ch.close(); conn.close(); } }
消费者的实现步骤:
- 创建连接工厂
- 设置服务地址、端口、用户名、密码、vHost
- 从连接工厂中获取连接
- 从连接获取通道channel
- 使用channle 声明队列【可以不声明队列,但是要保证该队列存在,否则会报错】
- 将队列绑定到交换机并指定key
- 定义队列消费者
- 监听队列并手动返回确认
- 获取消息【如果设置了手动返回确认则需要返回确消息】
public class Revc { private final static String QUEUE_NAME = "test_queue_work"; private final static String EXCHANGE_NAME = "test_exchange_direct"; public static void main(String[] args) throws Exception{ //获取连接 Connection conn = ConnectionUtils.getConnection(); //获取chanel 通道 Channel ch = conn.createChannel(); //声明队列 ch.queueDeclare(QUEUE_NAME, false, false, false, null); //绑定队列到交换机 ch.queueBind(QUEUE_NAME, EXCHANGE_NAME, "key"); //同一时刻服务器只发送一条信息给消费者 ch.basicQos(1); //定义队列消费者 QueueingConsumer consumer = new QueueingConsumer(ch); //监听队列,并手动返回信息 ch.basicConsume(QUEUE_NAME, false,consumer); //获取信息 while(true) { QueueingConsumer.Delivery delivery = consumer.nextDelivery(); String msg = new String(delivery.getBody()); System.out.println(" [x] Received '" + msg + "'"); Thread.sleep(10); ch.basicAck(delivery.getEnvelope().getDeliveryTag(), false); } } }
1.5. 通配符模式Topics
特点:生产者P发送消息到交换机X,type=topic,交换机根据绑定队列的routing key的值进行通配符匹配;符号#:匹配一个或者多个词lazy.# 可以匹配lazy.irs或者lazy.irs.cor
“#”表示0个或若干个关键字,“*”表示一个关键字。如“log.*”能与“log.warn”匹配,无法与“log.warn.timeout”匹配;但是“log.#”能与上述两者匹配。
实现步骤参考路由模式