RabbitMQ学习整理————RabbitMQ高级特性

  • 前言

    之前上一篇博客整理了rabbitmq中的三种交换机的类型,今天整理一下rabbitmq的一些高级特性,包括confirm确认消息,return返回消息,消费端限流策略以及自定义消费者使用等等,我只是想把学到的东西通过博客的方式记录下来,可能有点乱,如果有不正确的地方,希望大家可以指出交流。主要学习教程是慕课网的《RabbitMQ消息中间件技术精讲》。

 

  • Confirm确认消息

    Confirm确认消息是指生产者投递消息之后,Broker如果收到消息,会给生产者一个应答。生产者根据应答来确定消息是否正常发送到Broker。

    实现步骤:

  1. 首先在channel开启确认模式。channel.confirmSelect();
  2. 添加监听,监听成功和失败的返回结果,进行后续处理。channel.addConfirmListener();

    代码实现

/**
 * 消费者
 * @author Y
 * @date 2020年2月8日
 */
public class Consumer {
	public static void main(String[] args) throws Exception {
		//创建连接工厂
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost("192.168.159.128");
		connectionFactory.setPort(5672);
		connectionFactory.setVirtualHost("/");
		//获得connection
		Connection connection = connectionFactory.newConnection();
		//获得channel对象
		Channel channel = connection.createChannel();
		String exchangeName = "test_confirm_exchange";
		String exchangeType = "direct";
		String rountingKey = "confirm.add"; 
		String queueName = "test_confirm_queue";
		
		//声明交换机 队列 绑定交换机和队列并且指定对应的路由KEY
		channel.exchangeDeclare(exchangeName, exchangeType, true, true, null);
		channel.queueDeclare(queueName, false, false, false, null);
		channel.queueBind(queueName, exchangeName, rountingKey);
		
		QueueingConsumer queueingConsumer = new QueueingConsumer(channel);
		
		//设置channel  自动确认
		channel.basicConsume(queueName, true,queueingConsumer);
		
		while(true) {
			Delivery delivery = queueingConsumer.nextDelivery();
			String msg = new String(delivery.getBody(),"utf-8");
			System.out.println("消费端:"+msg);
		}
	}
}


/**
 * 生产者
 * @author Y
 * @date 2020年2月8日
 */
public class Producer {
	public static void main(String[] args) throws Exception {
		//获得connection工厂
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost("192.168.159.128");
		connectionFactory.setPort(5672);
		connectionFactory.setVirtualHost("/");
		//获得connection
		Connection connection = connectionFactory.newConnection();
		//获得channel
		Channel channel = connection.createChannel();
		//指定confirm确认消息
		channel.confirmSelect();
		//生产者发送消息
		String exchangeName = "test_confirm_exchange";
		String routingKey = "confirm.add";
		String msg = "test confirm exchange";
		channel.basicPublish(exchangeName, routingKey, null, msg.getBytes());
		//监听confirm确认消息
		channel.addConfirmListener(new ConfirmListener() {
			
			//消息投递失败
			@Override
			public void handleNack(long deliveryTag, boolean multiple) throws IOException {
				System.out.println("----------ack error--------------");
				
			}
			
			//消息投递成功
			@Override
			public void handleAck(long deliveryTag, boolean multiple) throws IOException {
				System.out.println("----------ack--------------");
				System.out.println(deliveryTag + Boolean.toString(multiple));
			}
		});
		
		//channel.close();
		//connection.close();
	}

}

    我这边只模拟运行成功的情况,消息发送到了一个存在的交换机上面,运行producer代码,运行结果:

  • Return返回消息

    Return Listener用于处理一些不可被路由的消息,在某些情况下,指定的exchangeName不存在或者routingKey路由不到,如果我们需要监听这种不可送达的消息,这时候就需要使用Return Listener。

    实现步骤

  1. 在发送消息的时候,我们可以指定mandatory这个参数为true,这样监听器就会监听到不可送达的消息,并且做后续处理。如果设置为false,则服务端默认会删除改消息。

    代码实现

public class Consumer {
	public static void main(String[] args) throws Exception {
		//创建连接工厂
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost("192.168.159.128");
		connectionFactory.setPort(5672);
		connectionFactory.setVirtualHost("/");
		//获得connection
		Connection connection = connectionFactory.newConnection();
		//获得channel对象
		Channel channel = connection.createChannel();
		String exchangeName = "test_return_exchange";
		String exchangeType = "topic";
		String rountingKey = "return.*";
		String queueName = "test_return_queue";
		
		//声明交换机 队列 绑定交换机和队列并且指定对应的路由KEY
		channel.exchangeDeclare(exchangeName, exchangeType, true, true, null);
		channel.queueDeclare(queueName, false, false, false, null);
		channel.queueBind(queueName, exchangeName, rountingKey);
		
		//设置channel
		channel.basicConsume(queueName, true,new MyConsumer(channel));
		
	}

}


public class Producer {
	public static void main(String[] args) throws Exception {
		//获得connection工厂
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost("192.168.159.128");
		connectionFactory.setPort(5672);
		connectionFactory.setVirtualHost("/");
		//获得connection
		Connection connection = connectionFactory.newConnection();
		//获得channel
		Channel channel = connection.createChannel();
		//消息投递失败后处理
		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("after return");
				System.out.println("replyCode:"+replyCode+"\n"+"replyText:"
				+replyText+"\n"+"exchange:"+exchange+"\n"+
				"routingKey:"+routingKey+"\n"+"body:"+new String(body));
			}
		});
		
		//生产者发送消息
		String exchangeName = "test_return_exchange";
		String routingKey = "test.add";
		String msg = "test return exchange";
		//这边设置mandatory 为true 监听未投递成功的消息
		channel.basicPublish(exchangeName, routingKey, true, null, msg.getBytes());
		
	}

}

    运行生产者代码,这边指定了一个不符合匹配规则的routingKey,消息不会送达,运行结果:

   自己测试的时候也指定了一个不存在的交换机,好像并没有走Return Listener监听,不知道为什么。。。

  • 自定义消费者使用

    以前代码里,消费端接收消息的时候,都是通过consumer.nextDelivery();循环的去获取数据,也可以通过继承DefaultConsumer类,重写handleDelivery方法去实现自定义方法监听的功能,其实上面说明Return返回消息的时候已经说明了,这里就简单贴一下代码。

/**
 * 生产者
 * @author Y
 * @date 2020年2月2日
 */
public class Producer {	
	
	public static void main(String[] args) throws Exception {
		//创建连接工厂
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost("192.168.159.128");
		connectionFactory.setPort(5672);
		connectionFactory.setVirtualHost("/");
		//获得connection
		Connection connection = connectionFactory.newConnection();
		//获得channel对象
		Channel channel = connection.createChannel();
		String exchangeName = "test_fanout_exchange";
		String rountingKey = "";//fanout不需要路由规则  消息会到所有与交换机绑定的队列上
		String msg = "test fanout exchange";
		channel.basicPublish(exchangeName,rountingKey, null, msg.getBytes());

		channel.close();
		connection.close();
	}
}


/**
 * 消费者
 * @author Y
 * @date 2020年2月2日
 */
public class Consumer {
	public static void main(String[] args) throws Exception {
		//创建连接工厂
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost("192.168.159.128");
		connectionFactory.setPort(5672);
		connectionFactory.setVirtualHost("/");
		//获得connection
		Connection connection = connectionFactory.newConnection();
		//获得channel对象
		Channel channel = connection.createChannel();
		
		String exchangeName = "test_fanout_exchange";
		String exchangeType = "fanout";
		String rountingKey = ""; //fanout不需要路由规则  消息会到所有与交换机绑定的队列上
		String queueName = "test_fanout_queue";
		
		//声明交换机
		channel.exchangeDeclare(exchangeName, exchangeType, true, true, null);
		//声明队列
		channel.queueDeclare(queueName, false, false, false, null);
		//将交换机与队列进行绑定
		channel.queueBind(queueName, exchangeName, rountingKey);
		
		//6 设置Channel 自定义消费者
		channel.basicConsume(queueName, true, new MyConsumer(channel));
	}
}


/**
 * 自定义消费者
 * @author Y
 * @date 2020年2月3日
 */
public class MyConsumer extends DefaultConsumer {
	public MyConsumer(Channel channel) {
		super(channel);
	}

	@Override
	public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
			throws IOException {
		System.out.println("handle message");
		System.out.println("consumerTag:"+consumerTag);
		System.out.println("envelope:"+envelope);
		System.out.println("properties:"+properties);
		System.out.println("body:"+new String(body));
	}
}

    运行结果

    可以看出输出了自定义消费监听方法的打印

  • 消费端限流策略

    如果MQ服务器上面堆积了很多没有处理的消息,这时候我们随便打开一个消费端去消费,很多的数据同时推送过来,单个消费端没有能力去处理这么多的数据。

    RabbitMQ提供了一种qos(质量保证服务)功能,在消息不是自动确认的前提之下,如果一定量数目(手动去设置)的消息没有被确认前,不会进行新的消息的消费。

    实现步骤

  1. channel调用BasicQos去设置,这边主要就讲一个参数,prefetchCount用于指定消息没有被ack之前,MQ最多给消费者推送的消息。(自己解释都有点困难。。。)、

    代码实现

/**
 * 消费端限流
 * @author Y
 * @date 2020年2月3日
 */
public class Consumer {
	
	public static void main(String[] args) throws Exception {
		//创建连接工厂
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost("192.168.159.128");
		connectionFactory.setPort(5672);
		connectionFactory.setVirtualHost("/");
		//获得connection
		Connection connection = connectionFactory.newConnection();
		//获得channel对象
		Channel channel = connection.createChannel();
		
		String exchangeName = "test_limt_exchange";
		String exchangeType = "topic";
		String rountingKey = "limt.#"; //fanout不需要路由规则  消息会到所有与交换机绑定的队列上
		String queueName = "test_limt_queue";
		
		//声明交换机 队列 绑定交换机和队列并且指定对应的路由KEY
		channel.exchangeDeclare(exchangeName, exchangeType, true, true, null);
		channel.queueDeclare(queueName, false, false, false, null);
		channel.queueBind(queueName, exchangeName, rountingKey);
		
		//指定消费端限流策略  消费端一次处理一条消息
		channel.basicQos(0, 1, false);
		//必须要指定autoAck为false 手动接收
		channel.basicConsume(queueName, false, new MyConsumer(channel));
		
	}
}


/**
 * 消费端限流
 * @author Y
 * @date 2020年2月3日
 */
public class Producer {
	public static void main(String[] args) throws IOException, TimeoutException {
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost("192.168.159.128");
		connectionFactory.setPort(5672);
		connectionFactory.setVirtualHost("/");
		
		Connection connection = connectionFactory.newConnection();
		Channel channel = connection.createChannel();
		
		String exchangeName = "test_limt_exchange";
		String routingKey = "limt.add";
		
		String msg = "test limt exchange";
		//这边一次发送5条消息
		for (int i = 0; i <5 ; i++) {
			channel.basicPublish(exchangeName, routingKey, null, msg.getBytes());
		}
		
	}
	
}


/**
 * 自定义消费者
 * @author Y
 * @date 2020年2月3日
 */
public class MyConsumer extends DefaultConsumer {
	
	private Channel channel;

	public MyConsumer(Channel channel) {
		super(channel);
		this.channel = channel;
	}

	@Override
	public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
			throws IOException {
		System.out.println("handle message");
		System.out.println("consumerTag:"+consumerTag);
		System.out.println("envelope:"+envelope);
		System.out.println("properties:"+properties);
		System.out.println("body:"+new String(body));
		
		//手动ack消息
		channel.basicAck(envelope.getDeliveryTag(), false);
	}

}

    这边生产者发送了5条消息,消费端是等一条消息ack完成之后,消费下一条消息,运行结果:

    这边我们如果注释掉消费端ack消息的代码,再次运行,因为我们没有ack消息,消费端会挂起,直到消息被ack。查看RabbitMQ管控平台,也可以看到,有一条消息是Unacked,还有4条消息是Ready状态等待消费。

    okok,今天先整理这么多吧。。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值