四.RabbitMQ进阶(一)

1.immediate参数和mandatory参数:

这两个参数都是channel.basicPublish()发布消息中的两个参数,而这两个参数都具有当消息传递过程中到达不了消费者处进行消费的时候将消息返回给生产者的功能


/*
	1.mandatory参数:队列层面
		当设置为true时,如果交换器无法根据rountingKey找到对应的队列时,会调用Basic.Return命令将消息返回给生产者
		如果设置为false,则直接丢弃消息

	2.immediate参数:消费者层面
		当设置为true时,如果检测到匹配的队列没有任何对应的消费者时,会调用Basic.Return命令将消息返回给生产者
		
		注意:rabbitmq3.0版本已经取出了immediate参数的设置,会影响镜像队列的性能
*/

//生产者设置监听器用来接收返回的消息

channel.addReturnListener(new ReturnListener(){

	public void handleReturn(int replyCode,String replyText
							  String exchangeName,String rountingKey,
							  AMQP.BasicProperties basicProperties,
							  byte[] body){
		String message = new String(body);
		System.out.print(message);
	}
});

2.备份交换器

生产者如果发送消息的时候不设置mandatory参数,并且消息无法找到匹配的队列的情况下消息会被丢失。
备份交换器的作用就是不用设置mandatory参数,而且也不需要设置接收返回消息的监听器,将未被路由的消息存储在RabbitMQ当中,在必要的时候再去处理这些消息

//可以在声明交换器的时候添加altenate-exchange参数来添加该交换器的备份交换器

Map<String,Object> properties = new HashMap<>();
properties.put("alternate-exchange","myAe");
//声明备份交换器
channel.exchangeDeclare("myAe","fanout",true,false,null);
//将备份交换器添加到普通交换器上,以map的形式添加
channel.exchangeDeclare("normalChange","direct",true,false,args);
//声明普通队列
channel.queueDeclare("normalQueue",true,false,false,null);
//缓存队列
channel.queueDeclare("unrountingQueue",true,false,false,null);

//普通队列和交换器绑定
channel.queuBind("normalQueue","normalExchange","normalKey");
//缓存队列和备份交换器绑定,不需要设置rountingKey
channel.queueBind("unrountingQueue","myAe","");

上述代码声明了一个普通交换器和备份交换器,并将备份交换器绑定到普通交换器上
如果此时发送一条消息到normalExchange上,如果路由键为"normalKey",则会正常路由到normalQueue中
如果路由键是其他的,比如"errorKey",此时不能路由到队列上,但是设置了备份交换器,会将消息发送给myAe备份交换器中,进而发送到unrountingQueue这个队列中

注意,上述备用交换器的类型是fanout,同时建议设置为这种类型
如果使用direct或者topic类型,以direct为例,并且有一个与之相绑定的队列(路由键为key1)。由于消息被重新发送到备份交换器时的使用的路由键和从生产者发出的路邮件是一样的,所以假设其携带路由键为key2的消息被转发到这个备份交换器,但是由于key1和key2没有完全匹配,备份路由器不能将其路由到其缓存队列,所以消息会丢失

所以对于备份交换器,总结了以下几种情况:

1.如果设置的备份交换器的名称不存在,rabbitmq不会出现异常,而且会丢弃掉该条消息

2.如果备份交换器没有绑定任何队列,rabbitmq不会出现异常,而且会丢弃掉该条消息

3.如果备份交换器没有任何匹配的队列,同2

4.如果备份交换器和mandatory参数一起使用,那么mandatory参数会失效

3.过期时间TTL—>time to live

设置消息的TTL

/*
	设置消息的TTL有如下两种方式:
		1.设置队列的TTL,所有在同一队列的消息有具有相同的TTL(队列也会被删除)---->在TTL内队列没有消费者,也没有重新声明
		2.设置消息的TTL,如果同时设置了队列的TTL,两者取其小
		
*/


//设置队列的TTL
Map<String,Object> properties = new HashMap<>();
properties.put("x-message-ttl",6000);
channel.queueDeclare(queueName,durable,exclusive,autoDelete,properties);

//设置消息的TTL
AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder();
builder.deliveryMode(2);
builder.expiration("6000");
//或者可以之间创建BasicProperties对象然后进行设置 
AMQP.BasicProperties properties = builder.build();
channel.basicPublish(exchangeName,rountingKey,mandatory,properties,"message".getBytes());

上述设置TTL是通过代码实现的,还可以通过设置HTTP的API接口来实现(略)
重启rabbitmq,持久化的消息或者队列的TTL会重新计算

4.死信交换器DLX—>Dead-Letter-Exchange

也可以称为死信交换器。当消息在一个队列中变成死信(TTL等)之后,它能被重新发送到另一个交换器中,这个交换器就是DLX

消息被变成死信的情况:

1.消息被拒绝,并且设置了requeue参数为false(如果是true,则重新加入到队列进行消费)
2.消息过期
3.队列达到最大长度

添加死信交换器

Map<String,Object> properties = new HashMap<>();
properties.put("dlx_exchange","direct");
//指定路由键,如果未指定,则使用原来的路由键
properties.put("x-dead-letter-rounting-key","dlx-rounting-key");
channel.queueDeclare(queueName,durable,exclusive,autoDelete,properties);

5.延迟队列

消息到达队列后,并不会直接交给消费者,而是等待特定时间后,才能进行消费

在这里插入代码片

延迟队列的实现可以使用TTL+DLX来完成

以10s的延迟队列为例:

	设置普通一个队列和一个dlx队列,消费者订阅dlx队列而不是普通队列
	然后设置消息的过期时间为10s
	10s过后发送的dlx队列被消费者消费

5.优先级队列

为队列设置优先级可以被有限消费,最低为0,最高为用户设置

使用场景:

	如果消费者的消费速度大于生产者的生产速度,那么优先级没有什么意义,因为消息刚到就被消费了
	但是如果消息有堆积,并且刚来的消费者订阅了多个队列,而且至少有两个队列具有消息堆积,那么设置优先级就有必要了

设置优先级:

properties.put("x-max-priority",10);

6.确认机制:消息有没有正常到达服务器

两种解决方案:
	1.事务机制实现
	2.发送发确认机制

事务

try{
	//将当前信道设置成事务模式
	channel.txSelect();
	//发送信息
	channel.basicPublish(...);
	//提交事务
	channel.txCommit();
}catch(Exception e){
	e.printStackTrace();
	channel.txRollback();
}

发送方确认机制
将信道设置为confirm模式,在该信道发布的消息都会被指派一个唯一的ID,一旦消息被投递到相应的队列后,rabbitmq会发送个确认(包含消息的ID)给生产者,如果消息是持久化的,会在写入磁盘后发出确认。

try{
	channel.confirmSelect();
	channel.basicPublish(...);
	if(!channel.waitForConfirms())
			System.out.print("消息发送失败");
	}catch(Exception e){
		e.printStackTrace();
	}

7.消息分发

当rabbitmq拥有多个消费者时,队列中的消息是以轮询的方式将消息分发给消费者。
每条消息只会发给某一个消费者

但是这种分发模式性能不高,如果有多个消费者处理的比较快,而另一部分消费者已经处理完成。
这样的话如果采取轮询的方式使得有些线程是空闲的,而有些线程处理不完

解决方法:使用channle.basicQos(int prefetchCount)这个方法

例:
在订阅消费队之前,消费client调用了channle.basicQos(5),之后订阅了某个队列进行消费。rabbitmq会保存一个消费者的列表,每发送一条消息都会为消费者计数,如果某个消费者未确认消息的上限达到了5,那么rabbitmq以后不会向其发送任何消息,直到该消费者将未确认消息的数量减少至小于5.类似“滑动窗口”

/*
	如果是以get方式拉取消息的,则basicQos无效
	basicQos方法有三个重载的方法
	@param:
		prefetchCount:该消费者所能接受未确认消息的数量
		prefetchSize:该消费者所能接受未确认消息的总大小(单位是B)
		global:如果是true,当前信道上的所有消费者都遵循该值
				如果是false,新创建的消费者遵循此值(有可能有早channel调用Qos之前创建的消费者)
*/
void basicQos(int prefetchCount);
void basicQos(int prefetchSize,int prefetchCount,boolean global);
void basicQos(int prefetchCount,boolean global);

8.弃用QueueingConsumer

在rabbitmq3.x版本使用的比较多,但是在4.x版本就已经被弃用了

queueingConsumer的用法:

//使用QueueingConsumer的时候一定要添加channel.basicQos(64);
QueueingConsumer consumer = new QueueingConsumer(channel);

channel.basicConsume(QUEUE_NAME,false,"consumer_zzh",consumer);

while(true){
	QueueingConsumer .Delivery delivery = consumer.nextDelivery();
	String msg = new String(delivery.getBody());
	channel.basicAck(delivery.getEnvelop().getDeliveryTag(),false);

}

弃用原因:内存溢出假象

你可能感兴趣的:(RabbitMQ笔记)