RabbitMQ(一)基础和底层API使用

一、消息队列

1、什么是消息队列MQ

MQ 是 Message Queue 的缩写,消息队列是应用程序和应用程序之间的通信方法。

RabbitMQ是一个开源的,在AMQP基础上完成的,可复用的企业消息系统。

AMQP: 即 Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。Erlang中的实现有RabbitMQ等。

AMQP核心概念

  • Server:又称Broker,接收客户端的连接,实现AMQP实体服务
  • Connection:连接,应用程序与Broker的网络连接
  • Channel:网络信道,几乎所有的操作都在Channel中进行,包括定义Queue、定义Exchange、绑定Queue与Exchange、发布消息等。Channel是进行消息读写的通道。客户端可以建立多个Channel,每个Channel代表一个会话任务。
  • Message:消息,服务器和应用程序之间传送的数据,由Properties和Body组成。Properties可以对消息进行修饰,比如消息的优先级、延迟等高级特性;Body就是消息体内容。
  • Virtual host:虚拟地址,用于进行逻辑隔离,最上层的消息路由。一个Virtual host可以有若干个Exchange和Queue,同一个Virtual host里面不能有相同的Exchange和Queue
  • Exchange:交换机,接收消息,根据路由键转发消息到绑定的队列

RabbitMQ中有三种常用的交换机类型:
direct: 如果路由键匹配,消息就投递到对应的队列
fanout:投递消息给所有绑定在当前交换机上面的队列
topic:允许实现有趣的消息通信场景,使得5不同源头的消息能够达到同一个队列。topic队列名称有两个特殊的关键字。
* 可以替换一个单词
# 可以替换所有的单词

  • Binding:Exchange和Queue之间的虚拟连接,binding中可以包含routing key
  • Routing key:一个路由规则,虚拟机可用它来确定如何路由一个特定消息
  • Queue:也称为Message Queue,消息队列,保存消息并将它们转发给消费者,多个消费者可以订阅同一个Queue,这时Queue中的消息会被平均分摊给多个消费者进行处理,而不是每个消费者都收到所有的消息并处理。
  • Prefetch count:如果有多个消费者同时订阅同一个Queue中的消息,Queue中的消息会被平摊给多个消费者。这时如果每个消息的处理时间不同,就有可能会导致某些消费者一直在忙,而另外一些消费者很快就处理完手头工作并一直空闲的情况。我们可以通过设置prefetchCount来限制Queue每次发送给每个消费者的消息数,比如我们设置prefetchCount=1,则Queue每次给每个消费者发送一条消息;消费者处理完这条消息后Queue会再给该消费者发送一条消息。

2、为什么要使用消息队列?

(1)解耦

传统模式:

传统模式的缺点:

  • 系统间耦合性太强,如上图所示,系统A在代码中直接调用系统B和系统C的代码,如果将来D系统接入,系统A还需要修改代码,过于麻烦!

中间件模式:

中间件模式的的优点:

  • 将消息写入消息队列,需要消息的系统自己从消息队列中订阅,从而系统A不需要做任何修改。

(2)异步

传统模式:

传统模式的缺点:

  • 一些非必要的业务逻辑以同步的方式运行,太耗费时间。

中间件模式:

中间件模式的的优点:

  • 将消息写入消息队列,非必要的业务逻辑以异步的方式运行,加快响应速度

(3)削峰

传统模式

传统模式的缺点:

  • 并发量大的时候,所有的请求直接怼到数据库,造成数据库连接异常

中间件模式:

中间件模式的的优点:

  • 系统A慢慢的按照数据库能处理的并发量,从消息队列中慢慢拉取消息。在生产中,这个短暂的高峰期积压是允许的。

3、使用了消息队列会有什么缺点?

分析:一个使用了MQ的项目,如果连这个问题都没有考虑过,就把MQ引进去了,那就给自己的项目带来了风险。我们引入一个技术,要对这个技术的弊端有充分的认识,才能做好预防。要记住,不要给公司挖坑!
回答:回答也很容易,从以下两个个角度来答

  • 系统可用性降低:你想啊,本来其他系统只要运行好好的,那你的系统就是正常的。现在你非要加个消息队列进去,那消息队列挂了,你的系统不是呵呵了。因此,系统可用性降低
  • 系统复杂性增加:要多考虑很多方面的问题,比如一致性问题、如何保证消息不被重复消费,如何保证保证消息可靠传输。因此,需要考虑的东西更多,系统复杂性增大。
  •  

4、RabbitMQ的几种消息队列模型

最基本的模型

工作队列模型

发布订阅模型

路由模型

主题模型

RPC模型

二、Rabbit MQ常用类、方法

1、连接对象

Connection【com.rabbitmq.client.Connection】

方法 返回值 描述
createChannel() Channel 创建一个通道对象

2、通道对象

Channel【com.rabbitmq.client.Channel】

方法 返回值 描述
queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete,Map<String, Object> arguments) Queue.DeclareOk 声明一个队列(创建一个队列)
basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body) void 向队列中声明一个消息(发送消息)发送给队列或交换机
basicConsume(String queue, boolean autoAck, Consumer callback) String 监听队列(消费端监听队列后才能时实获取队列的消息,立刻消费)
basicQos(int prefetchCount) void 限制发送消息个数
basicAck(long deliveryTag, boolean multiple) void 手动回执一个消息
exchangeDeclare(String exchange, String type) Exchange.DeclareOk 声明交换机,常用类型为  fanout(发布订阅模式)、direct(路由模式)、topic(主题模式)
queueBind(String queue, String exchange, String routingKey) Queue.BindOk 将队列绑定在交换机上
txSelect() Tx.SelectOk (消息确认机制)设置为transaction模式
txCommit() Tx.CommitOk (消息确认机制)提交事务
txRollback() Tx.RollbackOk (消息确认机制)回滚事务
confirmSelect() Confirm.SelectOk (消息确认机制)生产者调用confirmSelect 将channel设置为confirm模式
waitForConfirms() boolean (消息确认机制)消息确认

3、消息获取对象

Consumer【com.rabbitmq.client.Consumer】

DefaultConsumer【com.rabbitmq.client.DefaultConsumer】

new DefaultConsumer(Channel channel)   定义消费者,消费队列消息(接收消息)这是一个对象,并且重写里面的 handleDelivery方法
handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) void 重写方法。body是消息队列里面的消息

4、创建maven工程,引入依赖

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.my.RabbitMQ</groupId>
  <artifactId>myrabbitmq</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  
  <dependencies>
  	<dependency>
  		<groupId>com.rabbitmq</groupId>
  		<artifactId>amqp-client</artifactId>
  		<version>4.0.2</version>
  	</dependency>
  	<dependency>
  		<groupId>org.slf4j</groupId>
  		<artifactId>slf4j-log4j12</artifactId>
  		<version>1.7.5</version>
  	</dependency>
  	<dependency>
  		<groupId>log4j</groupId>
  		<artifactId>log4j</artifactId>
  		<version>1.2.17</version>
  	</dependency>
  	<dependency>
  		<groupId>junit</groupId>
  		<artifactId>junit</artifactId>
  		<version>4.11</version>
  	</dependency>
  </dependencies>
</project>

5、创建连接工厂类

package com.mmr.rabbitmq.util;

import java.io.IOException;
import java.util.concurrent.TimeoutException;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class ConnectionUtils {
	/**
	 * 获取MQ的连接
	 * @return
	 * @throws TimeoutException 
	 * @throws IOException 
	 */
	public static Connection getConnection() throws IOException, TimeoutException{
		//定义一个连接工厂
		ConnectionFactory factory=new ConnectionFactory();
		//设置服务地址
		factory.setHost("127.0.0.1");
		//AMQP 5672
		factory.setPort(5672);
		//vhost
		factory.setVirtualHost("/virtual_host");
		//用户名
		factory.setUsername("marvin");
		//密码
		factory.setPassword("123");
		
		return factory.newConnection();
	}
}

三、简单消息队列模型

1、创建消息生产者

package com.mmr.rabbitmq01.simple;

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

import com.mmr.rabbitmq.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

public class Send {
	//队列名
	private static final String QUEUE_NAME="test_simple_queue";

	public static void main(String[] args) throws IOException, TimeoutException {
		//获取连接
		Connection connection=ConnectionUtils.getConnection();
		//从连接中创建通道
		Channel channel=connection.createChannel();
		//声明(创建)队列
		channel.queueDeclare(QUEUE_NAME, false, false, false, null);
		//发送的消息
		String msg="hello simple";
		channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
		
		System.out.println("--send queue:"+msg);
		//关闭通道和连接
		channel.close();
		connection.close();
		
	}
}

2、消息消费者

消费者有两种方式消费消息队列

  • DefaultConsumer(推荐使用)
  • QueueingConsumer(旧的,不推荐使用)
package com.mmr.rabbitmq01.simple;

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

import com.mmr.rabbitmq.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.ConsumerCancelledException;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.QueueingConsumer;
import com.rabbitmq.client.QueueingConsumer.Delivery;
import com.rabbitmq.client.ShutdownSignalException;
import com.rabbitmq.client.AMQP.BasicProperties;

/**
 * 消费者获取消息
 * @author Marvin
 *
 */
public class Recv {
	private static final String QUEUE_NAME="test_simple_queue";
	
	public static void main(String[] args) throws IOException, TimeoutException, ShutdownSignalException, ConsumerCancelledException, InterruptedException {
//		oldAPI();
		newAPI();
	}
	/**
	 * 新的方式(DefaultConsumer),推荐使用
	 */
	private static void newAPI() throws IOException, TimeoutException {
		//获取连接
		Connection connection=ConnectionUtils.getConnection();
		//创建频道
		Channel channel=connection.createChannel();
		//队列声明
		channel.queueDeclare(QUEUE_NAME, false, false, false, null);
		//定义消费者
		DefaultConsumer consumer=new DefaultConsumer(channel){
			//获取到达消息
			@Override
			public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
					throws IOException {
				String msg=new String(body,"utf-8");
				System.out.println("new api recv:"+msg);
			}
		};
		//监听队列
		channel.basicConsume(QUEUE_NAME, true, consumer);
	}
	/**
	 * 旧的方式(QueueingConsumer),不推荐使用
	 */
	private static void oldAPI() throws IOException, TimeoutException, InterruptedException {
		//获取连接
		Connection connection=ConnectionUtils.getConnection();
		//创建频道
		Channel channel=connection.createChannel();
		//定义队列的消费者
		QueueingConsumer consumer=new QueueingConsumer(channel);
		//监听队列
		channel.basicConsume(QUEUE_NAME, true, consumer);
		while(true){
			Delivery delivery=consumer.nextDelivery();
			String msgString=new String(delivery.getBody());
			System.out.println("[recv] msg:"+msgString);
		}
	}
}

3、测试结果总结

运行java类:

可以看到 send 类一发出消息,recv 就立即消费了队列消息(因为消费者监听了这个队列)

我们的队列信息可以通过http://localhost:15672可视化平台看到我们定义的队列信息(队列名:test_simple_queue;虚拟主机:/virtual_host

四、工作队列模型

工作队列分为两种:

  1. 轮询分发:queue 向两个消费者交替分发消息,不考虑消费者处理消息的效率高低,是否完成。
  2. 公平分发:queue 向两个消费者每次发送一个消息,等到消费者处理完成后手动向队列回执一个确认信息,队列收到确认信息后再向消费者发送下一个消息。(消费者完成后才获取下一个消息,处理效率高的消费者相对来说会从队列中获取更多的消息,能者多劳)

1、轮询分发

(1)消息生产者

package com.mmr.rabbitmq02.work;

import java.io.IOException;
import java.util.concurrent.TimeoutException;
import com.mmr.rabbitmq.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

/**
 * 轮询分发
 * 				|----clinet1
 * P----Queue---|
 * 				|----clinet2
 */
public class Send {
	private static final String QUEUE_NAME="test_work_queue";

	public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
		// TODO Auto-generated method stub
		//获取连接
		Connection connection=ConnectionUtils.getConnection();
		//获取channel
		Channel channel=connection.createChannel();
		//声明队列
		channel.queueDeclare(QUEUE_NAME, false, false, false, null);
		
		for(int i=0;i<10;i++){
			String s="hello:"+i;
			System.out.println("[work send] i="+i);
			channel.basicPublish("", QUEUE_NAME, null, s.getBytes());
			Thread.sleep(2*10);
		}
		channel.close();
		connection.close();
	}
}

(2)消费者1

3秒处理一个,处理效率低

package com.mmr.rabbitmq02.work;

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

import com.mmr.rabbitmq.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;

public class Recv1 {
	private static final String QUEUE_NAME="test_work_queue";
	
	public static void main(String[] args) throws IOException, TimeoutException {
		Connection connection=ConnectionUtils.getConnection();
		//获取channel
		Channel channel=connection.createChannel();
		//声明队列
		channel.queueDeclare(QUEUE_NAME, false, false, false, null);
		//定义一个消费者
		Consumer consumer=new DefaultConsumer(channel){
			//消息到达触发这个方法
			@Override
			public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
					throws IOException {
				String msg=new String(body,"utf-8");
				System.out.println("[1] Recv msg:"+msg);
				try {
					Thread.sleep(3*1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}finally {
					System.out.println("[1] done");
				}
			}
		};
		//监听
		boolean autoAck =true;
		channel.basicConsume(QUEUE_NAME, autoAck, consumer);
	}
}

(3)消费者2

1秒处理一个,处理效率高

package com.mmr.rabbitmq02.work;

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

import com.mmr.rabbitmq.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;

public class Recv2 {
	private static final String QUEUE_NAME="test_work_queue";
	public static void main(String[] args) throws IOException, TimeoutException {
		Connection connection=ConnectionUtils.getConnection();
		//获取channel
		Channel channel=connection.createChannel();
		//声明队列
		channel.queueDeclare(QUEUE_NAME, false, false, false, null);
		//定义一个消费者
		Consumer consumer=new DefaultConsumer(channel){
			//消息到达触发这个方法
			@Override
			public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
					throws IOException {
				String msg=new String(body,"utf-8");
				System.out.println("[2] Recv msg:"+msg);
				try {
					Thread.sleep(1*1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}finally {
					System.out.println("[2] done");
				}
			}
		};
		//监听
		boolean autoAck =true;
		channel.basicConsume(QUEUE_NAME, autoAck, consumer);
	}
}

(3)测试结果总结

总结:两个消费者处理效率不同,但是队列仍会交替将消息发送给两个消费者,不管你是否处理完成。

2、公平分发

每个消费者发送确认消息之前,消息队列不发送下一个消息给消费者,一次只处理一个消息

  • basicQos():限制发送消息个数,限制发送给同一个消费者不得超过一条消息
  • basicAck():手动回执一个消息

注意:使用公平分发消费者一定要关闭自动应答,手动回执才有效

boolean autoAck =false;//关闭自动应答
channel.basicConsume(QUEUE_NAME, autoAck, consumer);

(1)消息生产者

package com.mmr.rabbitmq03.work.fair;

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

import com.mmr.rabbitmq.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

/**
 * 公平分发
 * 使用公平分发必须关闭自动应答
 * 				|----clinet1
 * P----Queue---|
 * 				|----clinet2
 */
public class Send {
	private static final String QUEUE_NAME="test_work_queue";
	public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
		// TODO Auto-generated method stub
		//获取连接
		Connection connection=ConnectionUtils.getConnection();
		//获取channel
		Channel channel=connection.createChannel();
		//声明队列
		channel.queueDeclare(QUEUE_NAME, false, false, false, null);
		/**
		 * 每个消费者发送确认消息之前,消息队列不发送下一个消息给消费者,一次只处理一个消息
		 * 
		 * 限制发送给同一个消费者不得超过一条消息
		 * basicQos()限制发送消息个数
		 */
		int prefetchCount=1;
		channel.basicQos(prefetchCount);
		
		for(int i=0;i<10;i++){
			String s="hello:"+i;
			System.out.println("[work send] i="+i);
			channel.basicPublish("", QUEUE_NAME, null, s.getBytes());
			Thread.sleep(2*10);
		}
		channel.close();
		connection.close();
	}
}

(2)消费者1(关闭自动应答)

3秒处理一个,处理效率

package com.mmr.rabbitmq03.work.fair;

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

import com.mmr.rabbitmq.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;

public class Recv1 {
	private static final String QUEUE_NAME="test_work_queue";
	public static void main(String[] args) throws IOException, TimeoutException {
		Connection connection=ConnectionUtils.getConnection();
		//获取channel
		final Channel channel=connection.createChannel();
		//声明队列
		channel.queueDeclare(QUEUE_NAME, false, false, false, null);
		//限制发送消息个数,保证一次 只发一个
		channel.basicQos(1);
		//定义一个消费者
		Consumer consumer=new DefaultConsumer(channel){
			//消息到达触发这个方法
			@Override
			public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
					throws IOException {
				String msg=new String(body,"utf-8");
				System.out.println("[1] Recv msg:"+msg);
				try {
					Thread.sleep(3*1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}finally {
					System.out.println("[1] done");
					//手动回执一个消息
					channel.basicAck(envelope.getDeliveryTag(), false);
				}
			}
		};
		//监听,一定要关闭自动应答
		boolean autoAck =false;
		channel.basicConsume(QUEUE_NAME, autoAck, consumer);
	}
}

(3)消费者2(关闭自动应答)

1秒处理一个,处理效率高

package com.mmr.rabbitmq03.work.fair;

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

import com.mmr.rabbitmq.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;

public class Recv2 {
	private static final String QUEUE_NAME="test_work_queue";
	public static void main(String[] args) throws IOException, TimeoutException {
		Connection connection=ConnectionUtils.getConnection();
		//获取channel
		final Channel channel=connection.createChannel();
		//声明队列
		channel.queueDeclare(QUEUE_NAME, false, false, false, null);
		channel.basicQos(1);
		//定义一个消费者
		Consumer consumer=new DefaultConsumer(channel){
			//消息到达触发这个方法
			@Override
			public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
					throws IOException {
				String msg=new String(body,"utf-8");
				System.out.println("[2] Recv msg:"+msg);
				try {
					Thread.sleep(1*1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}finally {
					System.out.println("[2] done");
					channel.basicAck(envelope.getDeliveryTag(), false);
				}
			}
		};
		//监听,一定要关闭自动应答
		boolean autoAck =false;
		channel.basicConsume(QUEUE_NAME, autoAck, consumer);
	}
}

(3)测试结果总结

总结:两个消费者处理效率不同,队列一次只向消费者发送一个消息,直到的收到消息回执后才发送下一个消息。

所以可以看到执行效率高的Recv2处理的消息更多。

五、发布订阅模型(fanout)

解读:

  • 一个生产者,多个消费者。
  • 每一个消费者都有自己的队列
  • 生产者没有直接把消息发送到队列,而是发到了交换机(转发器)exchange
  • 每个队列都要绑定到交换机上
  • 生产者发送的消息,经过交换机,到达队列,就能实现一个消息被多个消费者消费

1、消息生产者(声明交换机 fanout 模式

消息发送至 exchange(交换机) 而非先前的 queue(队列)

//声明交换机 fanout 模式
channel.exchangeDeclare(EXCHANGE_NANE, "fanout");//分发
//发送消息,发送到交换机(basicPublish第二个参数""是空字符串,表示发送给所有绑定的队列)
String msg="hello ps";
channel.basicPublish(EXCHANGE_NANE, "", null, msg.getBytes());

package com.mmr.rabbitmq04.ps;

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

import com.mmr.rabbitmq.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

/**
 * 发布订阅模式
 * channel.exchangeDeclare()交换机
 * 一个生产者消息发送到交换机,交换机分两个消息队列分别发送给两个消费者
 * 实现了一个任务后同时发送邮件和短信的功能
 */
public class Send {
	//交换机名
	private static final String EXCHANGE_NANE="test_exchange_fanout";
	public static void main(String[] args) throws IOException, TimeoutException {
		Connection connection=ConnectionUtils.getConnection();
		Channel channel=connection.createChannel();
		//声明交换机
		channel.exchangeDeclare(EXCHANGE_NANE, "fanout");//分发
		
		//发送消息,发送到交换机
		String msg="hello ps";
		channel.basicPublish(EXCHANGE_NANE, "", null, msg.getBytes());
		System.out.println("send "+msg);
		
		channel.close();
		connection.close();
	}
}

2、消费者SMS(绑定队列和交换机)

//消费者绑定交换机(转发器),将队列绑定到交换机
channel.queueBind(QUEUE_NAME, EXCHANGE_NANE, "");

package com.mmr.rabbitmq04.ps;

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

import com.mmr.rabbitmq.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;

public class Recv1SMS {
	//交换机名
	private static final String EXCHANGE_NANE="test_exchange_fanout";
	//队列名
	private static final String QUEUE_NAME="test_fanout_sms";
	public static void main(String[] args) throws IOException, TimeoutException {
		Connection connection=ConnectionUtils.getConnection();
		final Channel channel=connection.createChannel();
		//先声明交换机
		channel.exchangeDeclare(EXCHANGE_NANE, "fanout");//分发
		//声明队列
		channel.queueDeclare(QUEUE_NAME, false, false, false, null);
		//绑定交换机(转发器)将队列绑定到交换机
		channel.queueBind(QUEUE_NAME, EXCHANGE_NANE, "");
		Consumer consumer=new DefaultConsumer(channel){
			public void handleDelivery(String consumerTag, com.rabbitmq.client.Envelope envelope, com.rabbitmq.client.AMQP.BasicProperties properties, byte[] body) throws IOException {
				String s=new String(body,"utf-8");
				System.out.println("[2] Recv SMS msg:"+s);
				//手动回执一个消息
				channel.basicAck(envelope.getDeliveryTag(), false);
			};
		};
		channel.basicConsume(QUEUE_NAME, false, consumer);
	}
}

3、消费者 Email(绑定队列和交换机)

package com.mmr.rabbitmq04.ps;

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

import com.mmr.rabbitmq.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;

public class Recv1Email {

	private static final String EXCHANGE_NANE="test_exchange_fanout";
	private static final String QUEUE_NAME="test_fanout_email";
	public static void main(String[] args) throws IOException, TimeoutException {
		Connection connection=ConnectionUtils.getConnection();
		final Channel channel=connection.createChannel();
		channel.exchangeDeclare(EXCHANGE_NANE, "fanout");
		//声明队列
		channel.queueDeclare(QUEUE_NAME, false, false, false, null);
		//绑定交换机(转发器)
		channel.queueBind(QUEUE_NAME, EXCHANGE_NANE, "");
		Consumer consumer=new DefaultConsumer(channel){
			public void handleDelivery(String consumerTag, com.rabbitmq.client.Envelope envelope, com.rabbitmq.client.AMQP.BasicProperties properties, byte[] body) throws IOException {
				String s=new String(body,"utf-8");
				System.out.println("[1] Recv Email msg:"+s);
				//手动回执一个消息
				channel.basicAck(envelope.getDeliveryTag(), false);
			};
		};
		channel.basicConsume(QUEUE_NAME, false, consumer);
	}
}

4、测试结果总结

总结:生产者将消息发送到交换机,而非先前的队列。

每个消费者都有一个自己的队列,并将队列和交换机进行绑定。

这样发送到交换机的消息就会同时发送给msm 和 email的队列,两个队列都拿到了这个消息,并通过消费者消费消息。

六、路由模式(direct)

1、消息生产者(声明交换机 direct 模式)

//声明路由 direct 模式
channel.exchangeDeclare(EXCHANGE_NANE, "direct");

//error路由(通过routingKey定义,消费者通过key接收特定的消息)
String msgerr="hello direct err";
String
routingKeyErr="error";
channel.basicPublish(EXCHANGE_NANE, routingKeyErr, null, msgerr.getBytes());

package com.mmr.rabbitmq05.routing;

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

import com.mmr.rabbitmq.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

/**
 * direct
 * 路由模式,通过routingKey定义,消费者通过key接收特定的消息
 */
public class Send {
	private static final String EXCHANGE_NANE="test_exchange_direct";
	public static void main(String[] args) throws IOException, TimeoutException {
		Connection connection=ConnectionUtils.getConnection();
		Channel channel=connection.createChannel();
		//声明路由
		channel.exchangeDeclare(EXCHANGE_NANE, "direct");
		//error路由(通过routingKey定义,消费者通过key接收特定的消息)
		String msgerr="hello direct err";
		String routingKeyErr="error";
		channel.basicPublish(EXCHANGE_NANE, routingKeyErr, null, msgerr.getBytes());
		//info路由
		String msginfo="hello direct info";
		String routingKeyInfo="info";
		channel.basicPublish(EXCHANGE_NANE, routingKeyInfo, null, msginfo.getBytes());
		//warning路由
		String msgwarning="hello direct warning";
		String routingKeyWarning="warning";
		channel.basicPublish(EXCHANGE_NANE, routingKeyWarning, null, msgwarning.getBytes());
		
		channel.close();
		connection.close();
	}
}

2、消费者1(只接收error消息)

//绑定
channel.queueBind(QUEUE_NANE, EXCHANGE_NANE, "error");

package com.mmr.rabbitmq05.routing;

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

import com.mmr.rabbitmq.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;

public class Recv1 {
	private static final String EXCHANGE_NANE="test_exchange_direct";
	private static final String QUEUE_NANE="test_direct_queue_1";
	public static void main(String[] args) throws IOException, TimeoutException {
		Connection connection=ConnectionUtils.getConnection();
		final Channel channel=connection.createChannel();
		channel.basicQos(1);
		//声明路由
		channel.exchangeDeclare(EXCHANGE_NANE, "direct");
		//声明队列
		channel.queueDeclare(QUEUE_NANE, false, false, false, null);
		//绑定
		channel.queueBind(QUEUE_NANE, EXCHANGE_NANE, "error");
		Consumer consumer=new DefaultConsumer(channel){
			public void handleDelivery(String consumerTag, com.rabbitmq.client.Envelope envelope, com.rabbitmq.client.AMQP.BasicProperties properties, byte[] body) throws IOException {
				String s=new String(body,"utf-8");
				System.out.println("[1 err] Recv msg:"+s);
				channel.basicAck(envelope.getDeliveryTag(), false);
			};
		};
		channel.basicConsume(QUEUE_NANE, false, consumer);
	}
}

3、消费者2(接收info_err_warning消息)

package com.mmr.rabbitmq05.routing;

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

import com.mmr.rabbitmq.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;

public class Recv2 {
	private static final String EXCHANGE_NANE="test_exchange_direct";
	private static final String QUEUE_NANE="test_direct_queue_2";
	public static void main(String[] args) throws IOException, TimeoutException {
		Connection connection=ConnectionUtils.getConnection();
		final Channel channel=connection.createChannel();
		channel.basicQos(1);
		//声明路由
		channel.exchangeDeclare(EXCHANGE_NANE, "direct");
		//声明队列
		channel.queueDeclare(QUEUE_NANE, false, false, false, null);
		channel.queueBind(QUEUE_NANE, EXCHANGE_NANE, "error");
		channel.queueBind(QUEUE_NANE, EXCHANGE_NANE, "info");
		channel.queueBind(QUEUE_NANE, EXCHANGE_NANE, "warning");
		Consumer consumer=new DefaultConsumer(channel){
			public void handleDelivery(String consumerTag, com.rabbitmq.client.Envelope envelope, com.rabbitmq.client.AMQP.BasicProperties properties, byte[] body) throws IOException {
				String s=new String(body,"utf-8");
				System.out.println("[2 info_err_warning] Recv msg:"+s);
				channel.basicAck(envelope.getDeliveryTag(), false);
			};
		};
		channel.basicConsume(QUEUE_NANE, false, consumer);
	}
}

4、测试结果总结

总结:通过队列绑定key来确定该队列具体接收的消息, Recv1里面只绑定了err信息,而Recv2里面绑定了info err warning三个信息

七、主题模式(topic)

1、消息生产者(声明交换机 topic 模式)

//声明交换机 topic 模式
channel.exchangeDeclare(EXCHANGE_NANE, "topic");

package com.mmr.rabbitmq06.topic;

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

import com.mmr.rabbitmq.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

/**
 * topic
 * 路由匹配
 */
public class Send {
	private static final String EXCHANGE_NANE="test_exchange_topic";
	public static void main(String[] args) throws IOException, TimeoutException {
		Connection connection=ConnectionUtils.getConnection();
		Channel channel=connection.createChannel();
		//声明交换机 topic 模式
		channel.exchangeDeclare(EXCHANGE_NANE, "topic");
		
		String msg="商品....添加";
		String routingKey="goods.add";
		channel.basicPublish(EXCHANGE_NANE, routingKey, null, msg.getBytes("utf-8"));
		
		String msgd="商品....删除";
		String routingKeyd="goods.delete";
		channel.basicPublish(EXCHANGE_NANE, routingKeyd, null, msgd.getBytes("utf-8"));
		System.out.println("---send:"+msg);
		
		channel.close();
		connection.close();
	}
}

2、消费者1(只接收goods.add消息,和原先一样)

//绑定
channel.queueBind(QUEUE_NANE, EXCHANGE_NANE, "goods.add");

package com.mmr.rabbitmq06.topic;

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

import com.mmr.rabbitmq.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;

public class Recv1 {
	private static final String EXCHANGE_NANE="test_exchange_topic";
	private static final String QUEUE_NANE="test_topic_queue_1";
	public static void main(String[] args) throws IOException, TimeoutException {
		Connection connection=ConnectionUtils.getConnection();
		final Channel channel=connection.createChannel();
		channel.basicQos(1);
		//声明路由
		channel.exchangeDeclare(EXCHANGE_NANE, "topic");
		//声明队列
		channel.queueDeclare(QUEUE_NANE, false, false, false, null);
		//绑定
		channel.queueBind(QUEUE_NANE, EXCHANGE_NANE, "goods.add");
		Consumer consumer=new DefaultConsumer(channel){
			public void handleDelivery(String consumerTag, com.rabbitmq.client.Envelope envelope, com.rabbitmq.client.AMQP.BasicProperties properties, byte[] body) throws IOException {
				String s=new String(body,"utf-8");
				System.out.println("[1 goods.add] Recv msg:"+s);
				channel.basicAck(envelope.getDeliveryTag(), false);
			};
		};
		channel.basicConsume(QUEUE_NANE, false, consumer);
	}
}

3、消费者2(接收匹配符绑定)

package com.mmr.rabbitmq06.topic;

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

import com.mmr.rabbitmq.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;

public class Recv2 {
	private static final String EXCHANGE_NANE="test_exchange_topic";
	private static final String QUEUE_NANE="test_topic_queue_2";
	public static void main(String[] args) throws IOException, TimeoutException {
		Connection connection=ConnectionUtils.getConnection();
		final Channel channel=connection.createChannel();
		channel.basicQos(1);
		//声明路由(修改类型为topic)
		channel.exchangeDeclare(EXCHANGE_NANE, "topic");
		//声明队列
		channel.queueDeclare(QUEUE_NANE, false, false, false, null);
		//绑定方式(变为匹配符绑定)
		channel.queueBind(QUEUE_NANE, EXCHANGE_NANE, "goods.#");
		Consumer consumer=new DefaultConsumer(channel){
			public void handleDelivery(String consumerTag, com.rabbitmq.client.Envelope envelope, com.rabbitmq.client.AMQP.BasicProperties properties, byte[] body) throws IOException {
				String s=new String(body,"utf-8");
				System.out.println("[1 goods.#] Recv msg:"+s);
				channel.basicAck(envelope.getDeliveryTag(), false);
			};
		};
		channel.basicConsume(QUEUE_NANE, false, consumer);
	}
}

4、测试结果总结

总结:交换机设置为 topic 模式后 Recv1 还可以使用老方法 key 全名,Recv2 可以使用通配符匹配,绑定消息

八、rabbitMQ 消息确认机制(事务)

在rabbitmq中,我们可以通过持久化数据 解决rabbitmq 服务器异常 的数据丢失问题

问题: 生产者将消息发送出去之后,消息到底有没有到达rabbitmq 服务器,默认的情况是不知道的

两种方式:

  • AMQP实现了事务机制
  • Confirm模式

事务机制(txSelect、txCommit、txRollback)

  • txSelect :用户将当前 channel 设置成 transaction 模式
  • txCommit:用于提交事务
  • txRollback:回滚事务

1、消息生产者

package com.mmr.rabbitmq07.transaction;

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

import com.mmr.rabbitmq.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

public class TxSend {
	private static final String QUEUE_NANE="test_queue_Transaction";
	public static void main(String[] args) throws IOException, TimeoutException {
		Connection connection=ConnectionUtils.getConnection();
		Channel channel=connection.createChannel();
		channel.queueDeclare(QUEUE_NANE, false, false, false, null);
		String msg="hello transaction message";
		try {
			//设置为transaction模式
			channel.txSelect();
			channel.basicPublish("", QUEUE_NANE, null, msg.getBytes());
			//提交事务
			channel.txCommit();
			System.out.println("commit");
		} catch (Exception e) {
			//回滚事务
			channel.txRollback();
			System.out.println("rollback");
		}
		channel.close();
		connection.close();
	}
}

2、消息消费者

package com.mmr.rabbitmq07.transaction;

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

import com.mmr.rabbitmq.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;

public class TxRecv1 {
	private static final String QUEUE_NANE="test_queue_Transaction";
	public static void main(String[] args) throws IOException, TimeoutException {
		Connection connection=ConnectionUtils.getConnection();
		Channel channel=connection.createChannel();
		channel.queueDeclare(QUEUE_NANE, false, false, false, null);
		Consumer consumer=new DefaultConsumer(channel){
			public void handleDelivery(String consumerTag, com.rabbitmq.client.Envelope envelope, com.rabbitmq.client.AMQP.BasicProperties properties, byte[] body) throws IOException {
				String s=new String(body,"utf-8");
				System.out.println("[TX] Recv msg:"+s);
			};
		};
		channel.basicConsume(QUEUE_NANE, true, consumer);
	}
}

九、rabbitMQ 消息确认机制(confirm)

confirmSelect():开启confirm模式

编程模式

  1. 普通 发一条 waitForConfirms()
  2. 批量的 发一批 waitForConfirms()
  3. 异步 confirm模式:提供一个回调方法

1、消息生产者(单条发送)

package com.mmr.rabbitmq08.confirm;

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

import com.mmr.rabbitmq.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

/**
 * 单条发送,确认
 */
public class Send1 {
	private static final String QUEUE_NANE="test_queue_confirm";
	public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
		Connection connection=ConnectionUtils.getConnection();
		Channel channel=connection.createChannel();
		channel.queueDeclare(QUEUE_NANE, false, false, false, null);
		//生产者调用confirmSelect 将channel设置为confirm模式
		channel.confirmSelect();
		String msg="hello confrom message";
		channel.basicPublish("", QUEUE_NANE, null, msg.getBytes());
		//确认
		if(channel.waitForConfirms()){
			System.out.println("message seng success");
		}else{
			System.out.println("message seng failed");
		}
		channel.close();
		connection.close();
	}
}

2、消息生产者(批量发送)

package com.mmr.rabbitmq08.confirm;

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

import com.mmr.rabbitmq.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

/**
 * 批量发送,确认
 */
public class Send2 {
	private static final String QUEUE_NANE="test_queue_confirm";
	public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
		Connection connection=ConnectionUtils.getConnection();
		Channel channel=connection.createChannel();
		channel.queueDeclare(QUEUE_NANE, false, false, false, null);
		//生产者调用confirmSelect 将channel设置为confirm模式
		channel.confirmSelect();
		//批量发送
		for(int i=0;i<10;i++){
			String msg="hello confrom message=="+i;
			channel.basicPublish("", QUEUE_NANE, null, msg.getBytes());
		}
		//确认
		if(channel.waitForConfirms()){
			System.out.println("message seng success");
		}else{
			System.out.println("message seng failed");
		}
		channel.close();
		connection.close();
	}
}

3、消息消费者

package com.mmr.rabbitmq08.confirm;

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

import com.mmr.rabbitmq.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;

public class Recv {
	private static final String QUEUE_NANE="test_queue_confirm";
	public static void main(String[] args) throws IOException, TimeoutException {
		Connection connection=ConnectionUtils.getConnection();
		Channel channel=connection.createChannel();
		channel.queueDeclare(QUEUE_NANE, false, false, false, null);
		
		Consumer consumer=new DefaultConsumer(channel){
			@Override
			public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
					throws IOException {
				String msg=new String(body,"utf-8");
				System.out.println("[confirm] Recv msg:"+msg);
			}
		};
		channel.basicConsume(QUEUE_NANE, true, consumer);
	}
}

4、异步confirm

发布了69 篇原创文章 · 获赞 43 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/fox_bert/article/details/100110421
今日推荐