消息中间件——RabbitMQ(八)高级特性全在这里!(下)(1)

高级特性全在这里!(下)

前言


上一篇消息中间件——RabbitMQ(七)高级特性全在这里!(上)中我们介绍了消息如何保障100%的投递成功?,幂等性概念详解,在海量订单产生的业务高峰期,如何避免消息的重复消费的问题?,Confirm确认消息、Return返回消息。这篇我们来介绍下下面内容。

  • 自定义消费者

  • 消息的限流(防止占用内存过多,节点宕机)

  • 消息的ACK与重回队列

  • TTL消息

  • 死信队列

1. 自定义消费者


1.1 消费端自定义监听

我们一般就在代码中编写while循环,进行consumer.nextDelivery方法进行获取下一条消息,然后进行消费处理!

但是这种轮训的方式肯定是不好的,代码也比较low。

  • 我们使用自定义的Consumer更加的方便,解耦性更加的强,也是在实际工作中最常见的使用方式!

消费端自定义监听

1.2 代码演示

1.2.1 生产者

/**

  • @ClassName: Producer

  • @Description: 生产者

  • @author Coder编程

  • @date2019年7月30日 下午23:15:51

*/

public class Producer {

public static void main(String[] args) throws Exception {

//1 创建ConnectionFactory

Connection connection = ConnectionUtils.getConnection();

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());

}

}

}

1.2.2 消费者

/**

  • @ClassName: Consumer

  • @Description: 消费者

  • @author Coder编程

  • @date2019年7月30日 下午23:13:51

*/

public class Consumer {

public static void main(String[] args) throws Exception {

// 创建ConnectionFactory

Connection connection = ConnectionUtils.getConnection();

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);

//实现自己的MyConsumer()

channel.basicConsume(queueName, true, new MyConsumer(channel));

}

}

1.2.3 自定义类:MyConsumer

/**

  • @ClassName: MyConsumer

  • @Description: TODO

  • @author Coder编程

  • @date 2019年7月30日 下午23:11:55

*/

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));

}

}

1.3 打印结果

打印结果

2. 消费端限流


2.1 什么是消费端的限流?

  • 假设一个场景,首先,我们Rabbitmq服务器有上万条未处理的消息,我们随便打开一个消费者客户端,会出现下面情况:

  • 巨量的消息瞬间全部推送过来,但是我们单个客户端无法同时处理这么多数据!这个时候很容易导致服务器崩溃,出现故障。

为什么不在生产端进行限流呢?

因为在高并发的情况下,客户量就是非常大,所以很难在生产端做限制。因此我们可以用MQ在消费端做限流。

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

在限流的情况下,千万不要设置自动签收,要设置为手动签收

  • void BasicQos(uint prfetchSize,ushort prefetchCount,bool global);

参数解释:

prefetchSize:0

prefetchCount:会告诉RabbitMQ不要同时给一个消费者推送多于N个消息,即一旦有N个消息还没有ack,则该consumer将block掉,直到有消息ack。

global: true\false 是否将上面设置应用于channel,简单点说,就是上面限制是channel级别还是consumer级别。

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

第一个参数:消息的限制大小,消息多少兆。一般不做限制,设置为0

第二个参数:一次最多处理多少条,实际工作中设置为1就好

第三个参数:限流策略在什么上应用。在RabbitMQ一般有两个应用级别:1.通道 2.Consumer级别。一般设置为false,true 表示channel级别,false表示在consumer级别

2.2 代码演示

2.2.1 生产者

/**

  • @ClassName: Producer

  • @Description: 生产者

  • @author Coder编程

  • @date2019年7月30日 下午23:15:51

*/

public class Producer {

public static void main(String[] args) throws Exception {

//1 创建ConnectionFactory

Connection connection = ConnectionUtils.getConnection();

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());

}

}

}

2.2.2 消费者

/**

  • @ClassName: Consumer

  • @Description: 消费者

  • @author Coder编程

  • @date2019年7月30日 下午23:13:51

*/

public class Consumer {

public static void main(String[] args) throws Exception {

//1 创建ConnectionFactory

Connection connection = ConnectionUtils.getConnection();

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

//设置为1,表示一条一条数据处理

channel.basicQos(0, 1, false);

channel.basicConsume(queueName, false, new MyConsumer(channel));

}

}

2.2.3 自定义类:MyConsumer

/**

  • @ClassName: MyConsumer

  • @Description: TODO

  • @author Coder编程

  • @date 2019年7月30日 下午23:11:55

*/

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));

//需要做签收,false表示不支持批量签收

channel.basicAck(envelope.getDeliveryTag(), false);

}

}

2.2.4 测试结果

我们先注释掉:channel.basicAck(envelope.getDeliveryTag(), false);然后启动Consumer。

查看Exchange

Exchange

查看Queues

Queues

然后再启动Producer。查看打印结果:

打印结果

我们会发现消费端,只收到了一条消息。这是为什么呢?

第一点因为我们在consumer中

channel.basicConsume(queueName, false, new MyConsumer(channel));

第二个参数设置为false为手动签收。

第二点在qos中设置只接受一条消息。如果这一条消息不给Broker Ack应答的话,那么Broker会认为你并没有消费完这一条消息,那么就不会继续发送消息。

channel.basicQos(0, 1, false);

可以看下管控台,unack=1,Ready=4,total=5.

queues


接下来我们放开注释channel.basicAck(envelope.getDeliveryTag(), false); 进行消息签收。重启服务。

3.1 打印结果

打印结果

可以看到正常打印五条结果

4. 消费端ACK与重回队列


4.1 消费端的手工ACK和NACK

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

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

4.2 消费端的重回队列

消费端重回队列是为了对没有处理成功的消息,把消息重新传递给Broker!

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

4.3 代码演示

4.3.1 生产者

/**

  • @ClassName: Producer

  • @Description: 生产者

  • @author Coder编程

  • @date2019年7月30日 下午23:15:51

*/

public class Producer {

public static void main(String[] args) throws Exception {

//1创建ConnectionFactory

Connection connection = ConnectionUtils.getConnection();

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());

}

}

}

4.3.2 消费者

/**

  • @ClassName: Consumer

  • @Description: 消费者

  • @author Coder编程

  • @date2019年7月30日 下午23:13:51

*/

public class Consumer {

public static void main(String[] args) throws Exception {

//1创建ConnectionFactory

Connection connection = ConnectionUtils.getConnection();

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));

}

}

4.3.3 自定义类:MyConsumer

/**

  • @ClassName: MyConsumer

  • @Description: TODO

  • @author Coder编程

  • @date 2019年7月30日 下午23:11:55

*/

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) {

//Nack三个参数 第二个参数:是否是批量,第三个参数:是否重回队列(需要注意可能会发生重复消费,造成死循环)

channel.basicNack(envelope.getDeliveryTag(), false, true);

} else {

channel.basicAck(envelope.getDeliveryTag(), false);

}

}

}

5.1 打印结果:

打印结果

注意:

可以看到重回队列会出现重复消费导致死循环的问题,这时候最好设置重试次数,比如超过三次后,消息还是消费失败,就将消息丢弃。

6. TTL队列/消息


6.1 TTL

  • TTL是Time To Live的缩写,也就是生存时间

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

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

6.2 代码演示

6.2.1 直接通过管控台进行演示

添加queue

通过管控台创建一个队列

x-max-length 队列的最大大小

x-message-ttl 设置10秒钟,如果消息还没有被消费的话,就会被清除。

queue添加成功

添加exchange

添加exchangeexchange添加成功

Queue与Exchange进行绑定

读者福利

由于篇幅过长,就不展示所有面试题了,感兴趣的小伙伴

35K成功入职:蚂蚁金服面试Java后端经历!「含面试题+答案」

35K成功入职:蚂蚁金服面试Java后端经历!「含面试题+答案」

35K成功入职:蚂蚁金服面试Java后端经历!「含面试题+答案」

更多笔记分享

35K成功入职:蚂蚁金服面试Java后端经历!「含面试题+答案」
01pY2hhZWxfSE0=,size_16,color_FFFFFF,t_70)

通过管控台创建一个队列

x-max-length 队列的最大大小

x-message-ttl 设置10秒钟,如果消息还没有被消费的话,就会被清除。

queue添加成功

添加exchange

添加exchangeexchange添加成功

Queue与Exchange进行绑定

读者福利

由于篇幅过长,就不展示所有面试题了,感兴趣的小伙伴

[外链图片转存中…(img-UdHfAUvf-1718927684737)]

[外链图片转存中…(img-wO3llyde-1718927684738)]

[外链图片转存中…(img-6eDOScdz-1718927684738)]

更多笔记分享

[外链图片转存中…(img-03pIUyzb-1718927684739)]

  • 21
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值