• RabbitMQ消息的处理


    Confirm确认与Return返回消息

    Confirm消息确认机制:

    消息的确认,是指生产者投递消息后,如果Broker收到消息,则会给我们生产这一个应答。

    生产者进行接收应答,用来确定这条消息是否正常的发送到Broker,这种方式也是消息的可靠性投递的核心保障。

    确认机制流程图

    如何实现Confirm确认消息?

    第一步:在channel上开启确认模式:channel.confirmSelect()

    第二步:在channel上添加监听:addConfirmListener,监听成功和失败的返回结果,根据具体的结果对消息进行重新发送、或者记录日志等后续处理。

    消费者

    public class Consumer {
    	public static void main(String[] args) throws Exception {
    		//1 创建ConnectionFactory
    		ConnectionFactory connectionFactory = new ConnectionFactory();
    		connectionFactory.setHost("101.132.178.134");
    		connectionFactory.setPort(5672);
    		connectionFactory.setVirtualHost("/");
    		
    		//2 获取connection
    		Connection connection = connectionFactory.newConnection();
    		
    		//3 通过Connection创建一个新的Channel
    		Channel channel = connection.createChannel();
    		
    		String exchangeName = "test_confirm_exchange";
    		String routingKey = "confirm.#";
    		String queueName = "test_confirm_queue";
    		
    		//4 声明交换机和队列 然后进行绑定设置, 最后制定路由Key
    		channel.exchangeDeclare(exchangeName, "topic", true);
    		channel.queueDeclare(queueName, true, false, false, null);
    		channel.queueBind(queueName, exchangeName, routingKey);
    		
    		//5 创建消费者 
    		QueueingConsumer queueingConsumer = new QueueingConsumer(channel);
    		channel.basicConsume(queueName, true, queueingConsumer);
    		
    		while(true){
    			Delivery delivery = queueingConsumer.nextDelivery();
    			String msg = new String(delivery.getBody());
    			System.err.println("消费端: " + msg);
    		}
    	}
    }
    

    生产者

    public class Producer {
    	public static void main(String[] args) throws Exception {
    
    		//1 创建ConnectionFactory
    		ConnectionFactory connectionFactory = new ConnectionFactory();
    		connectionFactory.setHost("101.132.178.134");
    		connectionFactory.setPort(5672);
    		connectionFactory.setVirtualHost("/");
    		
    		//2 获取C	onnection
    		Connection connection = connectionFactory.newConnection();
    		
    		//3 通过Connection创建一个新的Channel
    		Channel channel = connection.createChannel();
    
    		//4 指定我们的消息投递模式: 消息的确认模式 
    		channel.confirmSelect();
    		
    		String exchangeName = "test_confirm_exchange";
    		String routingKey = "confirm.save";
    		
    		//5 发送一条消息
    		String msg = "Hello RabbitMQ Send confirm message!";
    		channel.basicPublish(exchangeName, routingKey, null, msg.getBytes());
    		
    		//6 添加一个确认监听
    		channel.addConfirmListener(new ConfirmListener() {
    			@Override
    			public void handleNack(long deliveryTag, boolean multiple) throws IOException {
    				System.err.println("-------no ack!-----------");
    			}
    			@Override
    			public void handleAck(long deliveryTag, boolean multiple) throws IOException {
    				System.err.println("-------ack!-----------");
    			}
    		});
    	}
    }
    

    Return消息机制

    Return Listener用于处理一些不可路由的消息

    我们的消息生产者,通过指定一个Exchange和Routingkey,把消息送到某一个队列中,然后我们的消费者监听队列,进行消息处理操作。

    但是在某些情况下,如果我们在发送消息的时候,当前的exchange不存在或者指定的路由key路由不到,这个时候我们需要监听这种不可达的消息,就要使用return listener。

    在基础API中有一个关键的配置项:

    Mandatory:如果为true,则监听会接收到路由不可达的消息,然后进行后续处理,如果为false,那么broker端自动删除该消息。(默认false)

    public class Consumer {
    	public static void main(String[] args) throws Exception {
    
    		ConnectionFactory connectionFactory = new ConnectionFactory();
    		connectionFactory.setHost("101.132.178.134");
    		connectionFactory.setPort(5672);
    		connectionFactory.setVirtualHost("/");
    		
    		Connection connection = connectionFactory.newConnection();
    		Channel channel = connection.createChannel();
    		
    		String exchangeName = "test_return_exchange";
    		String routingKey = "return.#";
    		String queueName = "test_return_queue";
    		
    		channel.exchangeDeclare(exchangeName, "topic", true, false, null);
    		channel.queueDeclare(queueName, true, false, false, null);
    		channel.queueBind(queueName, exchangeName, routingKey);
    		
    		QueueingConsumer queueingConsumer = new QueueingConsumer(channel);
    		
    		channel.basicConsume(queueName, true, queueingConsumer);
    		
    		while(true){
    			Delivery delivery = queueingConsumer.nextDelivery();
    			String msg = new String(delivery.getBody());
    			System.err.println("消费者: " + msg);
    		}
    	}
    }
    
    public class Producer {
    	public static void main(String[] args) throws Exception {
    		
    		ConnectionFactory connectionFactory = new ConnectionFactory();
    		connectionFactory.setHost("101.132.178.134");
    		connectionFactory.setPort(5672);
    		connectionFactory.setVirtualHost("/");
    		
    		Connection connection = connectionFactory.newConnection();
    		Channel channel = connection.createChannel();
    		
    		String exchange = "test_return_exchange";
    		String routingKey = "return.save";
    		String routingKeyError = "abc.save";
    		
    		String msg = "Hello RabbitMQ Return Message";
    		
    		
    		channel.addReturnListener(new ReturnListener() {
    			@Override
    			public void handleReturn(int replyCode, String replyText, String exchange,
    					String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
    				
    				System.err.println("---------handle  return----------");
    				System.err.println("replyCode: " + replyCode);
    				System.err.println("replyText: " + replyText);
    				System.err.println("exchange: " + exchange);
    				System.err.println("routingKey: " + routingKey);
    				System.err.println("properties: " + properties);
    				System.err.println("body: " + new String(body));
    			}
    		});
    		
    		channel.basicPublish(exchange, routingKeyError, true, null, msg.getBytes());
    		//channel.basicPublish(exchange, routingKeyError, true, null, msg.getBytes());
    	}
    }
    

    RabbitMQ消费端自定义监听

    我们一般在代码中编写while循环,进行consumer.nextDelivery方法进行获取下一条消息,然后进行消费处理,比较low。
    使用自定义的Consumer更加的方便,解耦性更强

    自定义Consumer

    public class MyConsumer extends DefaultConsumer {
    	
    	public MyConsumer(Channel channel) {
    		super(channel);
    	}
    	
    	@Override
    	public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    		System.err.println("-----------consume message----------");
    		System.err.println("consumerTag: " + consumerTag);
    		System.err.println("envelope: " + envelope);
    		System.err.println("properties: " + properties);
    		System.err.println("body: " + new String(body));
    	}
    }
    
    public class Consumer {
    	public static void main(String[] args) throws Exception {
    		
    		ConnectionFactory connectionFactory = new ConnectionFactory();
    		connectionFactory.setHost("192.168.11.76");
    		connectionFactory.setPort(5672);
    		connectionFactory.setVirtualHost("/");
    		
    		Connection connection = connectionFactory.newConnection();
    		Channel channel = connection.createChannel();
    		
    		String exchangeName = "test_consumer_exchange";
    		String routingKey = "consumer.#";
    		String queueName = "test_consumer_queue";
    		
    		channel.exchangeDeclare(exchangeName, "topic", true, false, null);
    		channel.queueDeclare(queueName, true, false, false, null);
    		channel.queueBind(queueName, exchangeName, routingKey);
    		
    		channel.basicConsume(queueName, true, new MyConsumer(channel));
    	}
    }
    
    public class Producer {
    	public static void main(String[] args) throws Exception {
    		
    		ConnectionFactory connectionFactory = new ConnectionFactory();
    		connectionFactory.setHost("192.168.11.76");
    		connectionFactory.setPort(5672);
    		connectionFactory.setVirtualHost("/");
    		
    		Connection connection = connectionFactory.newConnection();
    		Channel channel = connection.createChannel();
    		
    		String exchange = "test_consumer_exchange";
    		String routingKey = "consumer.save";
    		
    		String msg = "Hello RabbitMQ Consumer Message";
    		
    		for(int i =0; i<5; i ++){
    			channel.basicPublish(exchange, routingKey, true, null, msg.getBytes());
    		}
    	}
    }
    

    RabbitMQ消费端限流策略

    消费端限流

    我们RabbitMQ服务器有上万条未处理的消息,我们随便打开一个消费者客户端,会出现下面情况:巨量的消息瞬间全部推送过来,但是我们单个客户端无法同时处理这么多数据。(导致服务器崩溃,线上故障)生产端一次推送几百条数据库,客户端只接收一两条,在高并发的情况下,不能再生产端做限流,只能在消费端处理。

    解决方法

    RabbitMQ提供了一种qos(服务质量保证)功能,在非自动确认消息的前提下,如果一定数据的消息(通过基于consumer或者channel设置qos的值)未被确认前,不进行消费新的消息。

    void BasicQos(uint prefetchSize,ushort prefetchCount,bool global);
    

    消费端体现,一次最多能处理多少条消息(基本上为1),限流策略在什么上应用(channel--true,consumer---false)

    • prefetchSize:0
    • prefetchCount:会告诉RabbitMQ不要同时给一个消费者推送多余n个消息,一旦有n个消息还没有ack,则该consumer将block调,知道有消息ack
    • global:truefalse是否将上面设置应用于channel,简单的说就是上面限制是channel级别的还是consumer级别,基本使用false

    prefetchSize和global这两项,rabbitmq没有实现,暂不研究
    prefetch_count在no_ack=false的情况下生效,在自动应答的情况下两个值不生效

    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, AMQP.BasicProperties properties, byte[] body) throws IOException {
    		System.err.println("-----------consume message----------");
    		System.err.println("consumerTag: " + consumerTag);
    		System.err.println("envelope: " + envelope);
    		System.err.println("properties: " + properties);
    		System.err.println("body: " + new String(body));
    		channel.basicAck(envelope.getDeliveryTag(), false);
    	}
    }
    
    public class Consumer {
    	public static void main(String[] args) throws Exception {
    		
    		ConnectionFactory connectionFactory = new ConnectionFactory();
    		connectionFactory.setHost("192.168.11.76");
    		connectionFactory.setPort(5672);
    		connectionFactory.setVirtualHost("/");
    		
    		Connection connection = connectionFactory.newConnection();
    		Channel channel = connection.createChannel();
    		
    		String exchangeName = "test_qos_exchange";
    		String queueName = "test_qos_queue";
    		String routingKey = "qos.#";
    		
    		channel.exchangeDeclare(exchangeName, "topic", true, false, null);
    		channel.queueDeclare(queueName, true, false, false, null);
    		channel.queueBind(queueName, exchangeName, routingKey);
    		
    		//1 限流方式  第一件事就是 autoAck设置为 false
    		channel.basicQos(0, 1, false);
    		channel.basicConsume(queueName, false, new MyConsumer(channel));
    	}
    }
    
    public class Producer {
    	public static void main(String[] args) throws Exception {
    		
    		ConnectionFactory connectionFactory = new ConnectionFactory();
    		connectionFactory.setHost("192.168.11.76");
    		connectionFactory.setPort(5672);
    		connectionFactory.setVirtualHost("/");
    		
    		Connection connection = connectionFactory.newConnection();
    		Channel channel = connection.createChannel();
    		
    		String exchange = "test_qos_exchange";
    		String routingKey = "qos.save";
    		
    		String msg = "Hello RabbitMQ QOS Message";
    		
    		for(int i =0; i<5; i ++){
    			channel.basicPublish(exchange, routingKey, true, null, msg.getBytes());
    		}
    		
    	}
    }
    

    RabbitMQ消费端ACK与重回队列机制,TTL,死信队列详解

    消费端的手工ACK和NACK

    消费端进行消费的时候,如果由于业务异常我们可以进行日志的记录,然后进行补偿。

    如果由于服务器宕机等严重问题,那么我们就需要手工进行ACK保障消费端成功。

    消费端重回队列

    为了对没有处理成功的消息,把消息重新回递给Broker

    一般我们在实际应用中,都会关闭重回队列,也就是设置为false

    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, AMQP.BasicProperties properties, byte[] body) throws IOException {
    		System.err.println("-----------consume message----------");
    		System.err.println("body: " + new String(body));
    		try {
    			Thread.sleep(2000);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		if((Integer)properties.getHeaders().get("num") == 0) {
    			channel.basicNack(envelope.getDeliveryTag(), false, true);
    		} else {
    			channel.basicAck(envelope.getDeliveryTag(), false);
    		}
    	}
    }
    
    public class Consumer {
    
    	
    	public static void main(String[] args) throws Exception {
    
    		ConnectionFactory connectionFactory = new ConnectionFactory();
    		connectionFactory.setHost("192.168.11.76");
    		connectionFactory.setPort(5672);
    		connectionFactory.setVirtualHost("/");
    		
    		Connection connection = connectionFactory.newConnection();
    		Channel channel = connection.createChannel();
    		
    		String exchangeName = "test_ack_exchange";
    		String queueName = "test_ack_queue";
    		String routingKey = "ack.#";
    		
    		channel.exchangeDeclare(exchangeName, "topic", true, false, null);
    		channel.queueDeclare(queueName, true, false, false, null);
    		channel.queueBind(queueName, exchangeName, routingKey);
    		// 手工签收 必须要关闭 autoAck = false
    		channel.basicConsume(queueName, false, new MyConsumer(channel));
    	}
    }
    
    public class Producer {
    	public static void main(String[] args) throws Exception {
    		
    		ConnectionFactory connectionFactory = new ConnectionFactory();
    		connectionFactory.setHost("192.168.11.76");
    		connectionFactory.setPort(5672);
    		connectionFactory.setVirtualHost("/");
    		
    		Connection connection = connectionFactory.newConnection();
    		Channel channel = connection.createChannel();
    		
    		String exchange = "test_ack_exchange";
    		String routingKey = "ack.save";
    
    		for(int i =0; i<5; i ++){
    			
    			Map<String, Object> headers = new HashMap<String, Object>();
    			headers.put("num", i);
    			
    			AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
    					.deliveryMode(2)
    					.contentEncoding("UTF-8")
    					.headers(headers)
    					.build();
    			String msg = "Hello RabbitMQ ACK Message " + i;
    			channel.basicPublish(exchange, routingKey, true, properties, msg.getBytes());
    		}
    	}
    }
    

    TTL队列/消息

    TTL是time to live的缩写,也就是生存时间

    RabbitMQ支持消息的过期时间,在消息发送时可以进行指定

    RabbitMQ支持队列的过期时间,从消息入队列开始计算,只要超过了队列的超过时间配置,那么消息会自动的清除

    AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
    					.deliveryMode(2)
    					.contentEncoding("UTF-8")
    					.expiration("10000")
    					.build();
    

    消息10s过期,TTL是队列过期时间

    DLX死信队列

    DLX,Dead-Letter-Exchange

    利用DLX,当消息在一个队列中变成死信之后,它能够被重新publish到另一个exchange,这个exchange就是DLX。

    消息变成死信情况

    • 消息被拒绝(basic.reject/basic.nack)并且request=false
    • 消息TTL过期
    • 队列达到最大的长度

    DLX也是一个正常的exchange,和一般的exchange没有区别,他能在任何的队列上被指定,实际上就是设置某个队列的属性。

    当这个队列中有死信时,RabbitMQ就会自动的将这个消息重新发布到设置的exchange上去,进而被路由到另一个队列。 

    可以监听这个队列中消息做相应的处理,这个特性可以弥补RabbitMQ3.0以前支持的immediate参数的功能。 

    死信队列设置

    首先要设置死信队列的exchange和queue,并进行绑定,然后我们进行正常声明交换机,队列,绑定,只不过我们需要在队列加上一个参数:arguments.put("x-dead-letter-exchange","dlx.exchange");

    这样消息在过期、request、队列达到最大长度时, 消息就可以直接路由到死信队列。 

    public class MyConsumer extends DefaultConsumer {
    
    
    	public MyConsumer(Channel channel) {
    		super(channel);
    	}
    
    	@Override
    	public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    		System.err.println("-----------consume message----------");
    		System.err.println("consumerTag: " + consumerTag);
    		System.err.println("envelope: " + envelope);
    		System.err.println("properties: " + properties);
    		System.err.println("body: " + new String(body));
    	}
    }
    
    public class Consumer {
    	public static void main(String[] args) throws Exception {
    		ConnectionFactory connectionFactory = new ConnectionFactory();
    		connectionFactory.setHost("192.168.11.76");
    		connectionFactory.setPort(5672);
    		connectionFactory.setVirtualHost("/");
    		
    		Connection connection = connectionFactory.newConnection();
    		Channel channel = connection.createChannel();
    		
    		// 这就是一个普通的交换机 和 队列 以及路由
    		String exchangeName = "test_dlx_exchange";
    		String routingKey = "dlx.#";
    		String queueName = "test_dlx_queue";
    		
    		channel.exchangeDeclare(exchangeName, "topic", true, false, null);
    		
    		Map<String, Object> agruments = new HashMap<String, Object>();
    		agruments.put("x-dead-letter-exchange", "dlx.exchange");
    		//这个agruments属性,要设置到声明队列上
    		channel.queueDeclare(queueName, true, false, false, agruments);
    		channel.queueBind(queueName, exchangeName, routingKey);
    		
    		//要进行死信队列的声明:
    		channel.exchangeDeclare("dlx.exchange", "topic", true, false, null);
    		channel.queueDeclare("dlx.queue", true, false, false, null);
    		channel.queueBind("dlx.queue", "dlx.exchange", "#");
    		channel.basicConsume(queueName, true, new MyConsumer(channel));
    	}
    }
    
    public class Producer {
    	public static void main(String[] args) throws Exception {
    		
    		ConnectionFactory connectionFactory = new ConnectionFactory();
    		connectionFactory.setHost("192.168.11.76");
    		connectionFactory.setPort(5672);
    		connectionFactory.setVirtualHost("/");
    		
    		Connection connection = connectionFactory.newConnection();
    		Channel channel = connection.createChannel();
    		
    		String exchange = "test_dlx_exchange";
    		String routingKey = "dlx.save";
    		
    		String msg = "Hello RabbitMQ DLX Message";
    		
    		for(int i =0; i<1; i ++){
    			AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
    					.deliveryMode(2)
    					.contentEncoding("UTF-8")
    					.expiration("10000")
    					.build();
    			channel.basicPublish(exchange, routingKey, true, properties, msg.getBytes());
    		}
    	}
    }
    
  • 相关阅读:
    imperva_waf导入ssl证书
    博科光纤交换机初始化配置
    xss测试代码
    生成树注意事项
    [转载]Basics of the Unix Philosophy
    [转载]GSview注册码
    [转载]tar命令详解
    [转载]WinEdt 6 注册 试用期30天永不过期
    [转载+修改]计数排序
    [转载]C++ 关于声明,定义,类的定义,头文件作用,防止头文件在同一个编译单元重复引用,不具名空间
  • 原文地址:https://www.cnblogs.com/cky-2907183182/p/12733141.html
Copyright © 2020-2023  润新知