三、RabbitMq学习笔记

RabbitMQ原生API三种交换模式

1. Hello World

在这里没有声明交换机(exchange),也没有声明绑定(bind),RabbitMQ会使用默认的交换机(AMQP default)路由键就是队列名称

【生产者】

/**
 * 消费者
 * 
 * @author ITCloud
 */
public class Consumer {
	public static void main(String[] args) throws Exception {
		// 1.创建连接
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost("192.168.186.130");
		connectionFactory.setPort(5672);
		connectionFactory.setUsername("admin");
		connectionFactory.setPassword("admin");
		connectionFactory.setVirtualHost("/");
		// 2.获取连接,创建channel
		Connection connection = connectionFactory.newConnection();
		Channel channel = connection.createChannel();
		
		// 3. 队列声明
		String queueName = "hello.world";
		/**
		 * (String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
		 * 参数说明:
		 * 	queue:队列名称
		 * 	duable:队列是否是持久化的,rabbitmq重启之后,队列依然存在
		 * 	exclusive:独占队列,只对当前连接有效,一般都会设置成非独占队列false
		 * 	autoDelete:队列是否自动删除
		 * 	arguments:一些参数,后续介绍
		 */
		channel.queueDeclare(queueName, true, false, false, null);
		
		// 4.创建一个简单的消费者
		com.rabbitmq.client.Consumer consumer = new DefaultConsumer(channel) {
			//处理接收的消息
			/**
			 * body:接收消息体
			 */
			@Override
			public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
					throws IOException {
				System.out.println("消费端:" + new String(body));
			}
		};
		
		//设置异步接收消息,当消费端启动后,将一直会监听消费端
		channel.basicConsume(queueName, true, consumer);
	}
}

【消费者】

/**
 * 生产者
 * @author ITCloud
 */
public class Producer {
	public static void main(String[] args) throws Exception {
		//1.创建连接工厂类
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost("192.168.186.130");
		connectionFactory.setPort(5672);
		connectionFactory.setUsername("admin");
		connectionFactory.setPassword("admin");
		connectionFactory.setVirtualHost("/");
		//2.获取连接,创建channel
		Connection connection = connectionFactory.newConnection();
		Channel channel = connection.createChannel();
		
		//3. 发送消息
		String msg = "Hello world RabbitMQ!!";
		/**
		 * 参数详解
		 *String exchange, String routingKey, BasicProperties props, byte[] body
		 * 交换机名称:exchange 必须,如果不指定,则使用rabbitmq提供默认的exchange:AMQP default
		 * 路由键:routingKey 当交换机是Fanout时候routingKey可以不需要,如果没有明确指定,则路由到队列
		 * 发送消息带一些参数:props 非必需
		 * 要发送的消息:body
		 */
		channel.basicPublish("", "hello.world", null, msg.getBytes());
		
		//4.关闭相关连接
		channel.close();
		connection.close();
	
	}
}

2. direct交换模式

direct交换模式的特点:生产者和消费者通过routingKey来连接,消费端只有拥有相应的routingKey才能进行消费

【消费者】

/**
 * direct类型的交换机,特点:
 * 	生产者和消费者通过routingKey来连接,消费端只有拥有相应的routingKey才能进行消费
 * @author ITCloud
 *
 */
public class DirectConsumer {
	public static void main(String[] args) throws Exception{
		// 1.创建连接
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost("192.168.186.130");
		connectionFactory.setPort(5672);
		connectionFactory.setUsername("admin");
		connectionFactory.setPassword("admin");
		connectionFactory.setVirtualHost("/");
		// 2.获取连接,创建channel
		Connection connection = connectionFactory.newConnection();
		Channel channel = connection.createChannel();
		
		//3.交换机和队列声明
		/**
		 * 参数说明:(String exchange, String type, boolean durable, boolean autoDelete,Map<String, Object> arguments)
		 * exchange: 交换机名称
		 * type:交换机类型:direct topic fanout hearders(几乎不用)
		 * durable: 是否是持久化的队列,
		 * autoDelete:是否自动删除
		 * arguments:一些参数,几乎不用
		 */
		channel.exchangeDeclare("direct.exchange", "direct", true, false, null);
		channel.queueDeclare("direct.queue", true, false, false, null);
		//4.队列绑定
		channel.queueBind("direct.queue", "direct.exchange", "direct.queue");
		
		Consumer consumer = new DefaultConsumer(channel) {
			@Override
			public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
					throws IOException {
				System.out.println("消费端:" + new String(body));
			}
		};
		//true表示自动接收
		channel.basicConsume("direct.queue", true, consumer);
	}
}

【生产者】

public class DirectProducer {
	public static void main(String[] args) throws Exception{
		// 1.创建连接
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost("192.168.186.130");
		connectionFactory.setPort(5672);
		connectionFactory.setUsername("admin");
		connectionFactory.setPassword("admin");
		connectionFactory.setVirtualHost("/");
		// 2.获取连接,创建channel
		Connection connection = connectionFactory.newConnection();
		Channel channel = connection.createChannel();
		
		//3. 发送消息
		String msg = "Send msg by RabbitMQ!! direct";
		channel.basicPublish("direct.exchange", "direct.queue", null, msg.getBytes());
		
		channel.close();
		connection.close();
	}
}

3. fanout交换模式

fanout交换模式的特点:不需要routingKey,只要绑定了交换机即可

这种模式可以用于死信队列中

【消费者】

/**
 * fanout类型的交换机,特点:
 * 	不需要routingKey,只要绑定了交换机即可
 * @author ITCloud
 *
 */
public class FanoutConsumer {
	public static void main(String[] args) throws Exception{
		// 1.创建连接
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost("192.168.186.130");
		connectionFactory.setPort(5672);
		connectionFactory.setUsername("admin");
		connectionFactory.setPassword("admin");
		connectionFactory.setVirtualHost("/");
		// 2.获取连接,创建channel
		Connection connection = connectionFactory.newConnection();
		Channel channel = connection.createChannel();
		
		//3.交换机和队列声明
		channel.exchangeDeclare("fanout.exchange", "fanout", true, false, null);
		channel.queueDeclare("fanout.queue", true, false, false, null);
		//4.队列绑定,这里rountingKey=""; 但是不可以设置为null
		channel.queueBind("fanout.queue", "fanout.exchange", "");
		
		Consumer consumer = new DefaultConsumer(channel) {
			@Override
			public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
					throws IOException {
				System.out.println("消费端:" + new String(body));
			}
		};
		//true表示自动接收
		channel.basicConsume("fanout.queue", true, consumer);
	}
}

【生产者】

public class FanoutProducer {
	public static void main(String[] args) throws Exception{
		// 1.创建连接
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost("192.168.186.130");
		connectionFactory.setPort(5672);
		connectionFactory.setUsername("admin");
		connectionFactory.setPassword("admin");
		connectionFactory.setVirtualHost("/");
		// 2.获取连接,创建channel
		Connection connection = connectionFactory.newConnection();
		Channel channel = connection.createChannel();
		
		//3. 发送消息
		String msg = "Send msg by RabbitMQ!! fanout";
		channel.basicPublish("fanout.exchange", "", null, msg.getBytes());
		
		channel.close();
		connection.close();
	}
}

4. topic交换模式

【消费者】

/**
 * topic类型的交换机,特点:
 * 	通过rountingKey进行模糊匹配:
 * 		1. * 匹配一个单词
 * 		2. # 匹配多个单词
 * 例如:A.* 只可以匹配A.aab;但是不可以匹配A.aa.bb
 * @author ITCloud
 *
 */
public class TopicConsumer {
	public static void main(String[] args) throws Exception{
		// 1.创建连接
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost("192.168.186.130");
		connectionFactory.setPort(5672);
		connectionFactory.setUsername("admin");
		connectionFactory.setPassword("admin");
		connectionFactory.setVirtualHost("/");
		// 2.获取连接,创建channel
		Connection connection = connectionFactory.newConnection();
		Channel channel = connection.createChannel();
		
		//3.交换机和队列声明
		channel.exchangeDeclare("topic.exchange", "topic", true, false, null);
		channel.queueDeclare("topic.queue", true, false, false, null);
		//4.队列绑定,这里可以设置rountingKey=""; 但是不可以设置为null
		channel.queueBind("topic.queue", "topic.exchange", "topic.#");
		
		Consumer consumer = new DefaultConsumer(channel) {
			@Override
			public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
					throws IOException {
				System.out.println("消费端:" + new String(body));
			}
		};
		//true表示自动接收
		channel.basicConsume("topic.queue", true, consumer);
	}
}

【生产者】

public class TopicProducer {
	public static void main(String[] args) throws Exception{
		// 1.创建连接
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost("192.168.186.130");
		connectionFactory.setPort(5672);
		connectionFactory.setUsername("admin");
		connectionFactory.setPassword("admin");
		connectionFactory.setVirtualHost("/");
		// 2.获取连接,创建channel
		Connection connection = connectionFactory.newConnection();
		Channel channel = connection.createChannel();
		
		//3. 发送消息
		String msg = "Send msg by RabbitMQ!! topic";
        //声明rountingKey = "topic.hello.world" 匹配topic.#
		channel.basicPublish("topic.exchange", "topic.hello.world", null, msg.getBytes());
		
		channel.close();
		connection.close();
	}
}

5. Ack之重回队列

【消费者】

/**
 *重回队列:就是将消息进行重新扔到队列中,给消费者重新消费
 *	简单的说:就是把没有消费成功的队列,重新返回给Broker
 * @author ITCloud
 */
public class AckConsumer {
	public static void main(String[] args) throws Exception {
		// 1.创建连接
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost("192.168.186.130");
		connectionFactory.setPort(5672);
		connectionFactory.setUsername("admin");
		connectionFactory.setPassword("admin");
		connectionFactory.setVirtualHost("/");
		// 2.获取连接,创建channel
		Connection connection = connectionFactory.newConnection();
		Channel channel = connection.createChannel();
		
		// 3.交换机和 队列声明
		String queueName = "ack.queue";
		String exchanegName = "ack.exchange";
		channel.queueDeclare(queueName, true, false, false, null);
		channel.exchangeDeclare(exchanegName, "direct", true, false, null);
		
		channel.queueBind(queueName, exchanegName, "ack.queue");
		
		// 4.创建一个简单的消费者
		com.rabbitmq.client.Consumer consumer = new DefaultConsumer(channel) {
			@Override
			public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
					throws IOException {
				System.out.println("消费端:" + new String(body));
				Integer num = (Integer)properties.getHeaders().get("num");
				try {
					TimeUnit.SECONDS.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				if (num == 3) {
					//表示将该消息重新扔到消息的尾端,进行重新消费
					//此时会阻塞在这个地方 TODO
					channel.basicNack(envelope.getDeliveryTag(), false, true);
				} else {
					//消息手动接收
					channel.basicAck(envelope.getDeliveryTag(), false);
				}
			}
		};
		
		channel.basicQos(0, 1, false);
		//这里设置成非自动确认接收消息
		channel.basicConsume(queueName, false, consumer);
	}
}

【生产者】

/**
 * 
 * @author ITCloud
 */
public class AckProducer {
	public static void main(String[] args) throws Exception {
		//1.创建连接工厂类
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost("192.168.186.130");
		connectionFactory.setPort(5672);
		connectionFactory.setUsername("admin");
		connectionFactory.setPassword("admin");
		connectionFactory.setVirtualHost("/");
		//2.获取连接,创建channel
		Connection connection = connectionFactory.newConnection();
		Channel channel = connection.createChannel();
		
		//3. 发送消息
		for (int x = 0; x<5; x++) {
			Map<String, Object> headers = new HashMap<>();
			headers.put("num", x);
			BasicProperties properties = new BasicProperties.Builder()
					.deliveryMode(2) //持久化投递模式
					.contentEncoding("utf-8")
					.headers(headers)
					.build();
			String msg = "Hello world RabbitMQ!!" + x;
			channel.basicPublish("ack.exchange", "ack.queue", true, properties, msg.getBytes());
		}
		
		//4.关闭相关连接
		channel.close();
		connection.close();
	}
}

6. 消息确认机制

6.1 生产者消息确认

这里主要研究异步comfirm,因为异步comfirm性能比较高

【消费者】

/**
 * 消费者
 * @author ITCloud
 */
public class ConfirmConsumer {
	public static void main(String[] args) throws Exception {
		// 1.创建连接
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost("192.168.186.130");
		connectionFactory.setPort(5672);
		connectionFactory.setUsername("admin");
		connectionFactory.setPassword("admin");
		connectionFactory.setVirtualHost("/");
		// 2.获取连接,创建channel
		Connection connection = connectionFactory.newConnection();
		Channel channel = connection.createChannel();
		
		// 3.交换机和 队列声明
		String queueName = "confirm.queue";
		String exchanegName = "confirm.exchange";
		channel.queueDeclare(queueName, true, false, false, null);
		channel.exchangeDeclare(exchanegName, "direct", true, false, null);
		
		channel.queueBind(queueName, exchanegName, "confirm.queue");
		
		// 4.创建一个简单的消费者
		com.rabbitmq.client.Consumer consumer = new DefaultConsumer(channel) {
			//处理接收的消息
			/**
			 * body:接收消息体
			 */
			@Override
			public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
					throws IOException {
				System.out.println("消费端:" + new String(body));
			}
		};
		
		//设置异步接收消息,当消费端启动后,将一直会监听消费端
		channel.basicConsume(queueName, true, consumer);
	}
}

【生产者】

/**
 * 消息确认机制:确认消息是否成功投递到rabbitmq之中
 * @author ITCloud
 */
public class ConfirmProducer {
	public static void main(String[] args) throws Exception {
		//1.创建连接工厂类
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost("192.168.186.130");
		connectionFactory.setPort(5672);
		connectionFactory.setUsername("admin");
		connectionFactory.setPassword("admin");
		connectionFactory.setVirtualHost("/");
		//2.获取连接,创建channel
		Connection connection = connectionFactory.newConnection();
		Channel channel = connection.createChannel();
		
		
		
		//3.制定消息投递方式:消息确认模式
		channel.confirmSelect();
		
		//4. 发送消息
		for (int x = 0; x < 3; x ++) {
			String msg = "Hello world RabbitMQ!!" + x;
			channel.basicPublish("confirm.exchange", "confirm.queue", null, msg.getBytes());
		}
		
		final SortedSet<Long> confirmSet = Collections.synchronizedNavigableSet(new TreeSet<Long>());
		//消息确认机制
		channel.addConfirmListener(new ConfirmListener() {
			//消息投递失败,是投递失败,才会触发此方法
			@Override
			public void handleNack(long deliveryTag, boolean multiple) throws IOException {
				System.out.println("-----------no ack-------");
			}
			
			//消息投递成功
			//deliveryTag消息的唯一标识,每发送一消息+1;也可以看成消息的数量
			//multiple表示是否是批量发送消息
			@Override
			public void handleAck(long deliveryTag, boolean multiple) throws IOException {
				System.out.println(deliveryTag + "mutiple:" + multiple);
				System.out.println("------------ack-----------");	
			}
		});
		//4.关闭相关连接
//		channel.close();
//		connection.close();
	
	}
}

6.1 消息限流

限流需要关闭消费者的自动接收,然后在DefaultConsumer类中进行消息手动签收

【消费者】

/**
 * 消费端消息限流
 * 
 * @author ITCloud
 */
public class LimitConsumer {
	public static void main(String[] args) throws Exception {
		// 1.创建连接
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost("192.168.186.130");
		connectionFactory.setPort(5672);
		connectionFactory.setUsername("admin");
		connectionFactory.setPassword("admin");
		connectionFactory.setVirtualHost("/");
		// 2.获取连接,创建channel
		Connection connection = connectionFactory.newConnection();
		Channel channel = connection.createChannel();
		
		// 3.交换机和 队列声明
		String queueName = "limit.queue";
		String exchanegName = "limit.exchange";
		channel.queueDeclare(queueName, true, false, false, null);
		channel.exchangeDeclare(exchanegName, "direct", true, false, null);
		
		channel.queueBind(queueName, exchanegName, "limit.queue");
		
		// 4.创建一个简单的消费者
		com.rabbitmq.client.Consumer consumer = new DefaultConsumer(channel) {
			
			
			@Override
			public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
					throws IOException {
				System.out.println("消费端:" + new String(body));
				//设置手动消息接收,和channel.basicQos(0, 2, false);中的2对应,大于一条数据
				//如果没有下面的消息手动接收,则只能接收一次消息,即2条消息
				//第二个参数:true表示是否批量接收消息;和channel.basicQos(0, 2, false)中的2对应,每次手动签收两条消息
				channel.basicAck(envelope.getDeliveryTag(), true);
			}
		};
		
		//限流的关键步骤
		//第一个参数:限制消息的大小,这里0不做限制
		//第二个参数:每次接收消息的数量,这里表示每一次只能接收两条消息
		//第三个参数:表示在哪里做限制,false在consumer端做限制,true表示在channel上做限制
		channel.basicQos(0, 2, false);
		//这里设置成非自动确认接收消息
		channel.basicConsume(queueName, false, consumer);
	}
}

【生产者】

/**
 * @author ITCloud
 */
public class LimitProducer {
	public static void main(String[] args) throws Exception {
		//1.创建连接工厂类
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost("192.168.186.130");
		connectionFactory.setPort(5672);
		connectionFactory.setUsername("admin");
		connectionFactory.setPassword("admin");
		connectionFactory.setVirtualHost("/");
		//2.获取连接,创建channel
		Connection connection = connectionFactory.newConnection();
		Channel channel = connection.createChannel();
		
		//3. 发送消息
		
		for (int x = 0; x < 5; x++) {
			String msg = "Hello world RabbitMQ!!" + x;
			channel.basicPublish("limit.exchange", "limit.queue", null, msg.getBytes());
		}
		
		//4.关闭相关连接
		channel.close();
		connection.close();
	
	}
}

7. mandatory

boolean mandatory 参数为true的时候,当消息不可达(例如queue,exchange或者rountingkey不存在)的时候,消息将会被返回给生产者;false的时候,则消息则会被丢弃

示例:

【生产者】

/**
 *  return消息机制:
 *  	当我们投递消息时候,rountingKey或者exchange或者队列不存在,则将消息返回回来
 * @author ITCloud
 */
public class ReturnProducer {
	public static void main(String[] args) throws Exception {
		//1.创建连接工厂类
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost("192.168.186.130");
		connectionFactory.setPort(5672);
		connectionFactory.setUsername("admin");
		connectionFactory.setPassword("admin");
		connectionFactory.setVirtualHost("/");
		//2.获取连接,创建channel
		Connection connection = connectionFactory.newConnection();
		Channel channel = connection.createChannel();
		
		//3. 发送消息
		String msg = "Hello world RabbitMQ!! return listener";
		
		channel.addReturnListener(new ReturnListener() {
			
			@Override
			public void handleReturn(int replyCode, String replyText, String exchange, String routingKey,
					AMQP.BasicProperties properties,  byte[] body) throws IOException {
				System.out.println("消息未能投递成功,被返回:" + new String(body));
			}
		});
		//第三个参数 true表示会将不可达消息,返回到ReturnListener监听器中,
		//如果设置为false,则消息不可达之后直接被丢弃
		channel.basicPublish("return.exchange", "return.queue.xx", true, null, msg.getBytes());
		//4.关闭相关连接
//		channel.close();
//		connection.close();

	}
}

8. TTL 消息过期处理

对于消息的过期处理有两种方式,当下面两个同时使用的时候,会选择TTL较小的那个,当超过设置的时间,消息将变成死信队列。TTL如果设置成0,表示此时必须有消费者消费该队列,否则消息将被丢弃

8.1 消息过期

消息过期的处理就是在发送消息的时候设置消息的过期属性

BasicProperties properties = new BasicProperties.Builder()
				.deliveryMode(2)
				.contentEncoding("UTF-8")
				.expiration("10000") //设置消息过去时间为10秒,当十秒钟未能被消费就会自动删除
				.build();

【生产者】

public class TTLProducer {
	public static void main(String[] args) throws Exception{
		// 1.创建连接
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost("192.168.186.130");
		connectionFactory.setPort(5672);
		connectionFactory.setUsername("admin");
		connectionFactory.setPassword("admin");
		connectionFactory.setVirtualHost("/");
		// 2.获取连接,创建channel
		Connection connection = connectionFactory.newConnection();
		Channel channel = connection.createChannel();
		
		//3. 发送消息
		String msg = "Send msg by RabbitMQ!! ttl";
		BasicProperties properties = new BasicProperties.Builder()
				.deliveryMode(2)
				.contentEncoding("UTF-8")
				.expiration("10000") //设置消息过去时间为10秒,当十秒钟未能被消费就会自动删除
				.build();
		channel.basicPublish("TTL.exchange", "TTL.queue", properties, msg.getBytes());
		
		channel.close();
		connection.close();
	}
}

8.2 队列过期

在声明队列的时候设置队列的过期参数

// 4.创建一个消息级别的过期消息
Map<String, Object> arguments = new HashMap<>();
arguments.put("x-message-ttl", 10000); //这里不作演示了,可以直接放到queueDeclare()最后一个参数中
channel.queueDeclare("TTL.queue", true, false, false, arguments);

9. DLX死信队列

死信队列三种情况

  • 消息被拒绝( Basic.Reject/Basic.Nack ),井且设置 requeue 参数为 false;
  • 消息过期;
  • 令队列达到最大长度。

【消费者】

/**
  死信队列,将为消费的消息保存在另外一个队列中
 *  	消息被拒绝( Basic.Reject/Basic.Nack ),井且设置 requeue 参数为 false;
		消息过期;
		令队列达到最大长度。
 */
public class DlxConsumer {
	public static void main(String[] args) throws Exception {
		// 1.创建连接
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost("192.168.186.130");
		connectionFactory.setPort(5672);
		connectionFactory.setUsername("admin");
		connectionFactory.setPassword("admin");
		connectionFactory.setVirtualHost("/");
		// 2.获取连接,创建channel
		Connection connection = connectionFactory.newConnection();
		Channel channel = connection.createChannel();

		// 3.交换机和队列声明
		channel.exchangeDeclare("test.dlx.exchange", "direct", true, false, null);
		//表示该队列支持死信队列
		Map<String, Object> arguments = new HashMap<>();
		arguments.put("x-dead-letter-exchange", "dlx.exchange");
		channel.queueDeclare("test.dlx.queue", true, false, false, arguments);
		channel.queueBind("test.dlx.queue", "test.dlx.exchange", "test.dlx.queue");
		
		//5.死信队列和死信交换机,当消费者没有消费会将消息转换到dlx.exchange这个交换机的队列中
		channel.exchangeDeclare("dlx.exchange", "fanout", true, false, null);
		channel.queueDeclare("dlx.queue", true, false, false, null);
		channel.queueBind("dlx.queue", "dlx.exchange", "");

		Consumer consumer = new DefaultConsumer(channel) {
			@Override
			public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
					throws IOException {
				System.out.println("消费端:" + new String(body));
			}
		};
		// true表示自动接收
		channel.basicConsume("test.dlx.queue", true, consumer);
	}
}

【生产者】

这里设置消息10000ms过期

public class DlxProducer {
	public static void main(String[] args) throws Exception{
		// 1.创建连接
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost("192.168.186.130");
		connectionFactory.setPort(5672);
		connectionFactory.setUsername("admin");
		connectionFactory.setPassword("admin");
		connectionFactory.setVirtualHost("/");
		// 2.获取连接,创建channel
		Connection connection = connectionFactory.newConnection();
		Channel channel = connection.createChannel();
		
		//3. 发送消息
		String msg = "Send msg by RabbitMQ!! ttl";
		BasicProperties properties = new BasicProperties.Builder()
				.deliveryMode(2)
				.contentEncoding("UTF-8")
				.expiration("10000") //设置消息过去时间为10秒,当十秒钟未能被消费就会进入死信队列
				.build();
		channel.basicPublish("test.dlx.exchange", "test.dlx.queue", properties, msg.getBytes());
		
		channel.close();
		connection.close();
	}
}

10. 扩展

10.1 延迟队列

使用场景:订单延迟付款,当用户没有付款情况下,然后做后续处理

延迟队列和定时器很相似

实现方式:通过TTL和DXL来实现延迟队列

设置一个消息的过期时间10分钟,当10分钟之后队列会发送到死信队列中,然后消费者再消费死信队列中的消息,这就达到了延迟消费的目的

10.2 优先级队列

队列声明的时候确定优先级

Map<String, Object> args =new HashMap<String, Object> (); 
args.put ("x-rnax-priority", 10);  //设置优先级
channel.queueDeclare ("queue.priority", true, false , false , args) ; 

消息发送的时候确定优先级

AMQP.BasicProperties.Builder builder= new AMQP.BasicProperties.Builder() ; 
builder.priority(5);
AMQP.BasicProperties properties = builde.build(); 
channel.basicPublish ("exchange_priority","rk_priority", properties, messages.getBytes()); 

二、Spring家族整合

1.单纯的Spring整合

这里使用的是SpringBoot的配置,因为使用起来比较方便,但是整合的思想还是spring的整合思想

1.1 引入jar

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
</dependency>

然后添加SpringBoot的启动类

1.2 对rabbitmq进行配置

@Configuration
public class RabbitmqConfig {
	
	/**
	 * 连接工厂类
	 * @return
	 */
	@Bean
	public ConnectionFactory connectionFactory() {
		CachingConnectionFactory factory = new CachingConnectionFactory();
		factory.setAddresses("192.168.186.130:5672");
		factory.setUsername("admin");
		factory.setPassword("admin");
		factory.setHost("/");
		return factory;
	}
	
	/**
	 * RabbitAdmin 主要的作用是进行队列,交换机的声明,以及他们之间的绑定
	 * @param connectionFactory
	 * @return
	 */
	@Bean
	@Autowired
	public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
		RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
		rabbitAdmin.setAutoStartup(true); //项目启动的时候注入,否则Spring容器不会注册RabbitAdmin
		return rabbitAdmin;
	}
}

1.3 测试RabbitAdmin

@Autowired
private RabbitAdmin rabbitAdmin;
/**
	 * 声明队列和交换机测试
	 */
@Test
public void exchangeAndQueue() {
    //交换机声明
    rabbitAdmin.declareExchange(new DirectExchange("spring.direct.exchange", true, false,  								null));
    rabbitAdmin.declareExchange(new FanoutExchange("spring.fanout.exchange", true, false, 								null));
    rabbitAdmin.declareExchange(new TopicExchange("spring.topic.exchange", true, false, null));
    //队列声明
    rabbitAdmin.declareQueue(new Queue("spring.direct.queue", true, false, false, null));
    rabbitAdmin.declareQueue(new Queue("spring.fanout.queue", true, false, false, null));
    rabbitAdmin.declareQueue(new Queue("spring.topic.queue", true, false, false, null));
    //队列交换机绑定,方式一
    //Binding.DestinationType.QUEUE表示是交换机和交换机之间进行绑定还是交换机和队列之间进行绑定
    rabbitAdmin.declareBinding(new Binding("spring.direct.queue", 
                                           Binding.DestinationType.QUEUE, "spring.direct.exchange", "spring.direct.queue", null));

    //绑定方式二,fanout不需要rountingkey所以没有with方法
    //此时绑定需要注意的是:队列和交换机必须已经存在
    rabbitAdmin.declareBinding(BindingBuilder
                               .bind(new Queue("spring.fanout.queue", true, false, false, null))
                               .to(new FanoutExchange("spring.fanout.exchange", true, false, 								null)));

    //此时绑定需要注意的是:队列和交换机必须已经存在
    rabbitAdmin.declareBinding(BindingBuilder
                               .bind(new Queue("spring.topic.queue", true, false, false, null))
                               .to(new TopicExchange("spring.topic.exchange", true, false, 									null))
                               .with("spring.topic.#"));
}

1.4 Queue和Exchange自动注入

当项目启动的时候会自定创建队列和交换机并且绑定

	@Bean
	public Queue hmqu_interfaceLog() {
		return new Queue("hmqu.interfaceLog", true, false, false, null);
	}
	
	@Bean
	public TopicExchange bean_topic_exchange() {
		return new TopicExchange("bean.topic.exchange", true, false, null);
	}
	
	@Bean
	public Binding bind(Queue hmqu_interfaceLog, TopicExchange bean_topic_exchange) {
		return BindingBuilder.bind(hmqu_interfaceLog)
				.to(bean_topic_exchange).with("hmqu.interfaceLog");
	}

1.5 RabbitTemplate的使用

RabbitTemplate主要的作用进行消息的发送

	@Bean
	@Autowired
	public RabbitTemplate rabbitmqTemplate(ConnectionFactory connectionFactory) {
		RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
		return rabbitTemplate;
	}
/**
	 * 测试RabbitTemplate进行消息的发送
	 */
@Test
public void rabbitTemplateTest() {
    //1. 创建消息
    MessageProperties proprerties = new MessageProperties();
    proprerties.setContentEncoding("UTF-8");
    proprerties.getHeaders().put("self_header", "header_value"); //自定义头信息
    Message message = new Message("hello SpringRabbit".getBytes(), proprerties);


    rabbitTempalte.convertAndSend("spring.topic.exchange", "spring.topic.hello.world", message, new MessagePostProcessor() {
        @Override
        public Message postProcessMessage(Message message) throws AmqpException {
            System.out.println("给消息添加额外消息");
            message.getMessageProperties().getHeaders().put("second_header", "second_value");
            return message;
        }
    });
}

1.6 SimpleMessageListenerContainer

org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer主要的作用:

  • 监听队列(多个队列)、自动声明、自动启动
  • 设置消费者数量,最大和最小数量
  • 设置消息的接收模式、是否重回队列
  • 设置消费者标签生成策略,是否独占消费者属性
  1. 设置一个Controller来进行消息的动态发送
@RestController
@RequestMapping("/rabbit/send")
public class RabbitController {

	@Autowired
	public RabbitTemplate rabbitTemplate;

	@GetMapping("/msg")
	public String sendMsg() {
		MessageProperties properties = new MessageProperties();
		properties.setContentType("text/plain");
		Message message = new Message("hello consumer test".getBytes(), properties);
		rabbitTemplate.convertAndSend("bean.topic.exchange", "hmqu.interfaceLog", message,
				new MessagePostProcessor() {
					@Override
					public Message postProcessMessage(Message message) throws AmqpException {
						System.out.println("给消息添加额外消息");
						return message;
					}
				});
		return "success";
	}
}
  1. 配置消费者监听器
/**
	 * 主要对消息进行消费
	 * @return
	 */
@Bean
@Autowired
public SimpleMessageListenerContainer consumer(ConnectionFactory connectionFactory) {
    SimpleMessageListenerContainer consumer = new SimpleMessageListenerContainer(connectionFactory);
    //设置需要监听的队列
    consumer.setQueues(hmqu_interfaceLog());
    //设置接收模式
    consumer.setAcknowledgeMode(AcknowledgeMode.AUTO); //自动签收
    //设置消费者数量
    consumer.setConcurrentConsumers(2);
    //设置最大消费者数量
    consumer.setMaxConcurrentConsumers(5);
    //消息是否重回队列,通常是false
    consumer.setDefaultRequeueRejected(false);
    //设置消费者标签生成策略
    consumer.setConsumerTagStrategy(queue -> {
        return queue + "_" + UUID.randomUUID().toString();
    });
    //消费者监听消费
    consumer.setMessageListener(new ChannelAwareMessageListener() {
        @Override
        public void onMessage(Message message, Channel channel) throws Exception {
            String msg = new String(message.getBody());
            System.err.println("----------消费者: " + msg);
        }
    });
    return consumer;
}

启动SpringBoot项目,访问http://localhost:8080/rabbit/send/msg即可发现消息监听器

1.7 MessageListenerAdapter

可以使用MessageListenerAdapter对队列进行监听

/**
	 * 主要对消息进行消费
	 * @return
	 */
@Bean
@Autowired
public SimpleMessageListenerContainer consumer(ConnectionFactory connectionFactory) {
    SimpleMessageListenerContainer consumer = new SimpleMessageListenerContainer(connectionFactory);
    //设置需要监听的队列
    consumer.setQueues(hmqu_interfaceLog());
    //设置接收模式
    consumer.setAcknowledgeMode(AcknowledgeMode.AUTO); //自动签收
    //设置消费者数量
    consumer.setConcurrentConsumers(2);
    //设置最大消费者数量
    consumer.setMaxConcurrentConsumers(5);
    //消息是否重回队列
    consumer.setDefaultRequeueRejected(false);
    //设置消费者标签生成策略=
    consumer.setConsumerTagStrategy(queue -> {
        return queue + "_" + UUID.randomUUID().toString();
    });
    //============================================================
    //消费者消费
    //		consumer.setMessageListener(new ChannelAwareMessageListener() {
    //			@Override
    //			public void onMessage(Message message, Channel channel) throws Exception {
    //				String msg = new String(message.getBody());
    //				System.err.println("----------消费者: " + msg);
    //			}
    //		});
    //==========================Adapter==============================
    //第一种使用方式 使用Adapter,消费端进行消息处理
    MessageListenerAdapter adapter01 = new MessageListenerAdapter(new MessageDelegate());

    //第二种使用方式
    MessageListenerAdapter adapter02 = new MessageListenerAdapter(new MessageDelegate());
    adapter02.setDefaultListenerMethod("stringMessage"); //修改默认监听方法名称

    //第三种使用消息转换器
    MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageDelegate());
    adapter.setDefaultListenerMethod("converterMessage");
    adapter.setMessageConverter(new TextMessageConverter());

    consumer.setMessageListener(adapter);
    return consumer;
}

adapter.setMessageConverter消息转换器设置,默认使用的是SimpleMessageConverter转换器,使用者也可以自定义,在这里我使用自定义的TextMessageConverter具体实现如下所示

package com.itcloud.spring.delegate;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.support.converter.MessageConversionException;
import org.springframework.amqp.support.converter.MessageConverter;
/**
 * 这个写法就是默认的SimplMessageConverter
 * @author ITCloud
 *
 */
public class TextMessageConverter implements MessageConverter {
	/**
	 * 转换java对象到Message对象
	 */
	@Override
	public Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException {
		return new Message(object.toString().getBytes(), messageProperties);
	}

	/**
	 * 转换Message为java对象
	 */
	@Override
	public Object fromMessage(Message message) throws MessageConversionException {
		String contentType = message.getMessageProperties().getContentType();
		if (null != contentType && contentType.contains("text")) {
			return new String(contentType.getBytes());
		}
		return contentType.getBytes();
	}

}

MessageConverter有很多子类

​ 各种转换器的使用简单示例

// 1.1 支持json格式的转换器
/**
        MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageDelegate());
        adapter.setDefaultListenerMethod("consumeMessage");

        Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
        adapter.setMessageConverter(jackson2JsonMessageConverter);

        container.setMessageListener(adapter);
        */



// 1.2 DefaultJackson2JavaTypeMapper & Jackson2JsonMessageConverter 支持java对象转换
/**
        MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageDelegate());
        adapter.setDefaultListenerMethod("consumeMessage");

        Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();

        DefaultJackson2JavaTypeMapper javaTypeMapper = new DefaultJackson2JavaTypeMapper();
        jackson2JsonMessageConverter.setJavaTypeMapper(javaTypeMapper);

        adapter.setMessageConverter(jackson2JsonMessageConverter);
        container.setMessageListener(adapter);
        */


//1.3 DefaultJackson2JavaTypeMapper & Jackson2JsonMessageConverter 支持java对象多映射转换
/**
        MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageDelegate());
        adapter.setDefaultListenerMethod("consumeMessage");
        Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
        DefaultJackson2JavaTypeMapper javaTypeMapper = new DefaultJackson2JavaTypeMapper();

        Map<String, Class<?>> idClassMapping = new HashMap<String, Class<?>>();
		idClassMapping.put("order", com.bfxy.spring.entity.Order.class);
		idClassMapping.put("packaged", com.bfxy.spring.entity.Packaged.class);

		javaTypeMapper.setIdClassMapping(idClassMapping);

		jackson2JsonMessageConverter.setJavaTypeMapper(javaTypeMapper);
        adapter.setMessageConverter(jackson2JsonMessageConverter);
        container.setMessageListener(adapter);
        */

//1.4 ext convert

MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageDelegate());
adapter.setDefaultListenerMethod("consumeMessage");

//全局的转换器:分配不同消息使用不同的转换器
ContentTypeDelegatingMessageConverter convert = new ContentTypeDelegatingMessageConverter();

TextMessageConverter textConvert = new TextMessageConverter();
convert.addDelegate("text", textConvert);
convert.addDelegate("html/text", textConvert);
convert.addDelegate("xml/text", textConvert);
convert.addDelegate("text/plain", textConvert);

Jackson2JsonMessageConverter jsonConvert = new Jackson2JsonMessageConverter();
convert.addDelegate("json", jsonConvert);
convert.addDelegate("application/json", jsonConvert);

ImageMessageConverter imageConverter = new ImageMessageConverter();
convert.addDelegate("image/png", imageConverter);
convert.addDelegate("image", imageConverter);

PDFMessageConverter pdfConverter = new PDFMessageConverter();
convert.addDelegate("application/pdf", pdfConverter);


adapter.setMessageConverter(convert);
container.setMessageListener(adapter);

自定义的一个PDF,或者imag转换器

package com.bfxy.spring.convert;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.UUID;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.support.converter.MessageConversionException;
import org.springframework.amqp.support.converter.MessageConverter;

public class PDFMessageConverter implements MessageConverter {

	@Override
	public Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException {
		throw new MessageConversionException(" convert error ! ");
	}

	@Override
	public Object fromMessage(Message message) throws MessageConversionException {
		System.err.println("-----------PDF MessageConverter----------");
		
		byte[] body = message.getBody();
		String fileName = UUID.randomUUID().toString();
		String path = "d:/010_test/" + fileName + ".pdf";
		File f = new File(path);
		try {
			Files.copy(new ByteArrayInputStream(body), f.toPath());
		} catch (IOException e) {
			e.printStackTrace();
		}
		return f;
	}

}

MessageConverter作用是对消息进行转换,默认情况下,MessageConverter在消息传输的过程中使用的是二进制方式进行传输的,当我们需要实现自己的传输方式可以使用这个类来自定义;例如前面,消息的发送不支持application/pdf,这时候可以使用自定义来实现消息传输。

SpringBoot整合Rabbitmq

1.消息可靠性投递(生产者)

  1. 配置application.yml
spring:
  rabbitmq:
    addresses: 192.168.186.130
    username: admin
    password: admin
    virtual-host: /
    connection-timeout: 15000
    publisher-confirms: true # 开启消息确认机制
    publisher-returns: true # 消息返回机制
    template:
      mandatory: true # 设置为true,当队列或者rountingkey不存在,则重回队列
  1. RabbitSendConfig
/**
 *  消息可靠性投递
 * @author ITCloud
 *
 */
@Component
public class RabbitSendConfig {
	
	@Autowired
	private RabbitTemplate rabbitTemplate;
	
	//消息可靠性投递
	public void send(String exchange, String routingKey, Message message) {
		//消息确认,确认消息是否被投递到rabbitmq中
		//exchaneg不存在会导致nack
		rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
			System.err.println("correlationData:" + correlationData + ",ack:" + ack + ", cause:" + cause);
			if (ack) {
				//消息正确投递了,这时候可以更改数据可状态
			} else {
				//消息未能正确投递,再做处理
			}
		});
		
		//消息重回队列,当queue不存在或者routingKey不存在的时候重回队列
        //其实重回队列意义不大,可以不使用重回队列
		rabbitTemplate.setReturnCallback((msg, replyCode, replyText, ex, rKey) -> {
			System.err.println("msg:" + new String(msg.getBody()) + ",replyCode:" + replyCode + ",replyText:" 
					+ replyText + ",exchange:" + ex + ", rkey:" + rKey);
			//TODO 重回队列要做一些补偿措施
		});
		rabbitTemplate.send(exchange, routingKey, message);
	}
	
}
  1. 测试
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class SpringBootRabbitTest {
	
	@Autowired
	private RabbitSendConfig send;

	/*
	 * 消息确认和重回队列测试
	 */
	@Test
	public void confirmAndReturnTest() {
		MessageProperties proprerties = new MessageProperties();
		proprerties.setContentEncoding("UTF-8");
		proprerties.getHeaders().put("self_header", "header_value"); //自定义头信息
		Message message = new Message("spring boot message".getBytes(), proprerties);
		this.send.send("spring.boot.exchange", "spring.boot.queue", message);
	}
}

2. 消费者

spring:
  rabbitmq:
    addresses: 192.168.186.130
    username: admin
    password: admin
    virtual-host: /
    connection-timeout: 15000
    publisher-confirms: true # 开启消息确认机制
    publisher-returns: true # 消息返回机制
    template:
      mandatory: true # 设置为true,当队列或者rountingkey不存在,则重回队
    listener:
      simple:
        acknowledge-mode: MANUAL # 签收模式:AUTO, MANUAL, NONE
        concurrency: 5
        max-concurrency: 10
@Component
public class RabbitConsumer {

	@RabbitHandler
	@RabbitListener(queues = {"spring.boot.queue"})
	public void consumerMessage(Message message, Channel channel) {
		System.err.println("接收消息:" + new String(message.getBody()));
		System.err.println(message.getMessageProperties().getDeliveryTag());
		//设置手动签收
		try {
			channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

3. 对象接收

还是倾向于

@Payload要接受的对象

@Headers请求头

	
@RabbitHandler
@RabbitListener(queues = {"spring.boot.queue"})
public void onOrderMessage(@Payload Order order, 
                           Channel channel, 
                           @Headers Map<String, Object> headers) throws Exception {
    System.err.println("--------------------------------------");
    System.err.println("消费端order: " + order.getId());
    Long deliveryTag = (Long)headers.get(AmqpHeaders.DELIVERY_TAG);
    //手工ACK
    channel.basicAck(deliveryTag, false);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值