RabbitMQ消息队列学习笔记

 

初识RabbitMQ

Routing Key:路由键,用来指示消息的路由转发,相当于快递的地址

Exchange: 交换机,相当于快递的分拨中心

Queue:消息队列,消息最终被送到这里等待consumer取走

Binding: exchange和queue之间的虚拟连接,用于message的分发依据

Exchange

Exchange是 AMQP协议和RabbitMQ的核心组件。Exchange的功能是根据绑定关系和路由键为消息提供路由,将消息转发至相应的队列。Exchange有4种类型: Direct / Topic / Fanout / Headers ,其中Headers使用很少,以前三种为主。

Direct Exchange(直接路由)

Message中的Routing Key如果和Binding Key一致,Direct Exchange则将message发到对应的queue中。

Fanout Exchange(广播路由)

每个发到Fanout Exchange的message都会分发到所有绑定的queue上去。扇形交换机(广播)

Topit Exchange(话题路由)

根据Routing Key及通配规则,Topic Exchange将消息分发到目标Queue中。

通配符".*"匹配一个,如:queue.*.go 匹配 queue.shuai.go

通配符".#"匹配多个,如:queue.# 匹配 queue.shuai.go和queue.shuai等

用于模拟rabbitmq的网站:RabbitMQ Simulator

使用docker安装rabbitmq

docker run -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management

通过15672端口,可以访问rebbitmq的前台管理工具默认用户名和密码都是:guest

下载并安装延迟队列

docker ps 查看运行的容器,看容器对应的ID

下载对应版本的延迟队列插件:

Release 3.9.0 · rabbitmq/rabbitmq-delayed-message-exchange · GitHub

上传到服务器的/home/soft/文件夹下,拷贝到容器中

docker cp /home/soft/rabbitmq_delayed_message_exchange-3.9.0.ez 7f8eabb93cf8:/plugins

docker exec -it 7f8eabb93cf8 /bin/bash 进入rabbitmq容器

rabbitmq-plugins list 查看rabbitmq有哪些插件,以及运行状况

rabbitmq-plugins enable rabbitmq_delayed_message_exchange启用插件的命令

 可以看到延迟队列已经启用了

使用命令行工具口诀

◆想看什么就List什么

◆想清空什么就purge什么

◆想删除什么就Delete什么

◆一切问题记得使用--help

状态查看

◆查看connection: rabbitmqctl list_connections

◆查看消费者: rabbitmqctl list_consumers

◆查看交换机: rabbitmqctl list_exchanges

队列相关

◆查看队列: rabbitmqctl list_queues 

◆删除队列: rabbitmqctl delete_queue

◆清空队列: rabbitmqctl purge_queue

用户相关

◆新建用户: rabbitmqctl add_user

◆修改用户密码: rabbitmqctl change_password

◆删除用户: rabbitmqctl delete_user

◆查看用户: rabbitmqctl list_users

◆设置用户角色: rabbitmqctl rabbitmqctl set_user_tags

应用启停

◆启动应用: rabbitmqctl start_app

◆关闭应用: rabbitmqctl stop_app,保留Erlang虚拟机(暂停)

◆关闭应用: rabbitmqctl stop,并关闭Erlang虚拟机

集群相关

◆加入集群: rabbitmqctl join_cluster

◆离开集群: rabbitmqctl reset

镜像队列

◆设置镜像队列: rabbitmqctl sync_queue

◆取消镜像队列: rabbitmqctl cancel_sync_queue

RabbitMQ基本用法(Java)

◆交换机数量不能过多,一般来说同一个业务,或者同一类业务使用同一-个交换机

◆合理设置队列数量,- -般来说一个微服务监听一 个队列,或者一个微服务的一个业务监听-个队列

◆合理配置交换机类型,使用Topic模式时仔细设置绑定键

如何发送消息

1.新建ConnectionFactory

ConnectionFactory connectionFactory = new ConnectionFactory(); 
connectionFactory.setHost("localhost");
connectionFactory.setHost("localhost");

2.使用try-with-resource新建连接

try (Connection connection = connectionFactory.newConnection(); 
    Channel channel = connection.createChannel()) {
        //业务逻辑
}

3.新建Exchange

channel.exchangeDeclare(
                     "exchange.name",         //交换机名字
                     BuiltinExchangeType.DIRECT,  //交换机类型
                     true,      //durable:是否要持久化
                     false,     //autoDelete:交换机没有被使用时,是否删掉
                     null);     //arguments:特殊属性

4.新建Queue

channel.queueDeclare(
                     "queue.name",    //队列的名称
                     true,            //durable:是否持久话
                     false,           //exclusive:是否独占
                     false,           //autoDelete:交换机没有被使用时,是否删掉
                     null);           //arguments:特殊属性

5.新建Binding

channel.queueBind( 
            "queue.name", 
            "exchange.name",
            "key.name");

5.使用basicPublish发送消息

channel.basicPublish("exchange.name", "key.name", null, messageToSend.getBytes());

6.定义回调函数

DeliverCallback deliverCallback = (consumerTag, message) -> { 
    //业务逻辑
};

7.使用basicConsume消费消息

@Async 
public void handleMessage() { 
channel.basicConsume("queue.name", true, deliverCallback, consumerTag -> {

     });
}

示例:

发送消息

ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");

try (Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel()) {
    String messageToSend = objectMapper.writeValueAsString(orderMessageDTO);
    channel.basicPublish("exchange.order.restaurant", "key.restaurant", null,             messageToSend.getBytes());
}

处理接收消息

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.imooc.food.orderservicemanager.dao.OrderDetailDao;
import com.imooc.food.orderservicemanager.dto.OrderMessageDTO;
import com.imooc.food.orderservicemanager.enummeration.OrderStatus;
import com.imooc.food.orderservicemanager.po.OrderDetailPO;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

@Slf4j
@Service
public class OrderMessageService {

    @Autowired
    private OrderDetailDao orderDetailDao;
    ObjectMapper objectMapper = new ObjectMapper();


    //初始化消息通道
    @Async
    public void handleMessage() throws IOException, TimeoutException, InterruptedException {
        log.info("start linstening message");
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        connectionFactory.setHost("localhost");
        try (Connection connection = connectionFactory.newConnection();
             Channel channel = connection.createChannel()) {

            /*------------监听要申明交换机和队列,并声明绑定关系-------------*/
            channel.exchangeDeclare(
                    "exchange.order.restaurant",
                    BuiltinExchangeType.DIRECT,
                    true,
                    false,
                    null);

            channel.queueDeclare(
                    "queue.order",
                    true,
                    false,
                    false,
                    null);

            channel.queueBind(
                    "queue.order",
                    "exchange.order.restaurant",
                    "key.order");


            /*---------------------deliveryman---------------------*/
            channel.exchangeDeclare(
                    "exchange.order.deliveryman",
                    BuiltinExchangeType.DIRECT,
                    true,
                    false,
                    null);


            channel.queueBind(
                    "queue.order",
                    "exchange.order.deliveryman",
                    "key.order");

            //监听queue.order队列,一旦收到消息,调用deliverCallback方法,处理消息。
            channel.basicConsume("queue.order", true, deliverCallback, consumerTag -> {
            });
            while (true) {
                Thread.sleep(100000);
            }
        }
    }


    //处理收到的消息
    DeliverCallback deliverCallback = (consumerTag, message) -> {
        String messageBody = new String(message.getBody());
        log.info("deliverCallback:messageBody:{}", messageBody);
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        try {
            OrderMessageDTO orderMessageDTO = objectMapper.readValue(messageBody,
                    OrderMessageDTO.class);
            OrderDetailPO orderPO = orderDetailDao.selectOrder(orderMessageDTO.getOrderId());

            switch (orderPO.getStatus()) {

                case ORDER_CREATING:
                    if (orderMessageDTO.getConfirmed() && null != orderMessageDTO.getPrice()) {
                        orderPO.setStatus(OrderStatus.RESTAURANT_CONFIRMED);
                        orderPO.setPrice(orderMessageDTO.getPrice());
                        orderDetailDao.update(orderPO);
                        try (Connection connection = connectionFactory.newConnection();
                             Channel channel = connection.createChannel()) {
                            String messageToSend = objectMapper.writeValueAsString(orderMessageDTO);
                            channel.basicPublish("exchange.order.deliveryman", 
                                    "key.deliveryman",
                                    null,
                                    messageToSend.getBytes());
                        }
                    } else {
                        orderPO.setStatus(OrderStatus.FAILED);
                        orderDetailDao.update(orderPO);
                    }
                    break;
                case RESTAURANT_CONFIRMED:
                    break;
                case DELIVERYMAN_CONFIRMED:
                    break;
                case SETTLEMENT_CONFIRMED:
                    break;
            }

        } catch (JsonProcessingException | TimeoutException e) {
            e.printStackTrace();
        }
    };
}

 配置线程池

@Configuration 
@EnableAsync 
public class AsyncTaskConfig implements AsyncConfigurer {
// ThredPoolTaskExcutor的处理流程 
// 当池子大小小于corePoolSize,就新建线程,并处理请求 
// 当池子大小等于corePoolSize,把请求放入workQueue中,池子里的空闲线程就去workQueue中取任务 
// 当workQueue放不下任务时,就新建线程入池,并处理请求,如果池子大小撑到了maximumPoolSize, 
// 当池子的线程数大于corePoolSize时,多余的线程会等待keepAliveTime长时间,如果无请求可处理

@Override 
@Bean 
public Executor getAsyncExecutor() { 
    ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor(); 
    //设置核心线程数 
    threadPool.setCorePoolSize(10); 
    //设置最大线程数 
    threadPool.setMaxPoolSize(100); 
    //线程池所使用的缓冲队列 
    threadPool.setQueueCapacity(10); 
    //等待任务在关机时完成--表明等待所有线程执行完                 
    threadPool.setWaitForTasksToCompleteOnShutdown(true); 
    // 等待时间 (默认为0,此时立即停止),并没等待xx秒后强制停止         
    threadPool.setAwaitTerminationSeconds(60); 
    // 线程名称前缀 
    threadPool.setThreadNamePrefix("Rabbit-Async-"); 
    // 初始化线程 
    threadPool.initialize(); 
    return threadPool;
}
@Override 
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { 
    return null;
}

使用线程池启动basicConsume

public class RabbitConfig {

    @Autowired 
    OrderMessageService orderMessageService;

    @Autowired 
    public void startListenMessage() throws IOException, TimeoutException,         InterruptedException{
        //调用接收消息的方法
        orderMessageService.handleMessage();
    }
}

RabbitMQ高级用法(Java)

◆需要使用RabbitMQ发送端确认机制,确认消息成功发送到RabbitMQ并被处理.

◆需要使用RabbitMQ消息返回机制,若没发现目标队列,中间件会通知发送方

◆需要使用RabbitMQ消费端确认机制,确认消息没有发生处理异常

◆需要使用RabbitMQ消费端限流机制,限制消息推送速度,保障接收端服务稳定

◆大量堆积的消息会给RabbitMQ产生很大的压力,需要使用RabbitMQ消息过期时间,防止消息大量积压

◆过期后会直接被丢弃,无法对系统运行异常发出警报,需要使用RabbitMQ死信队列,收集过期消息,以供分析

发送端消息确认机制的三种方式

单条同步确认机制的实现方法

◆配置channel,开启确认模式: channel.confirmSelect()

◆每发送一条消息,调用channel.waitForConfirms()方法,等待确认

        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");

        try (Connection connection = connectionFactory.newConnection();
             Channel channel = connection.createChannel()) {
            String messageToSend = objectMapper.writeValueAsString(orderMessageDTO);
            channel.confirmSelect();
            channel.basicPublish("exchange.order.restaurant", "key.restaurant", null, messageToSend.getBytes());
            log.info("message sent");
            if(channel.waitForConfirms()){
                log.info("confirm OK");
            }else {
                log.info("confirm Failed");
            }
            Thread.sleep(100000);
        }

多条同步确认机制的实现方法(不推荐)

◆配置channel,开启确认模式: channel.confirmSelect()

◆发送多条消息后,调用channel.waitForConfirms()方法,等待确认

        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");

        try (Connection connection = connectionFactory.newConnection();
             Channel channel = connection.createChannel()) {
            String messageToSend = objectMapper.writeValueAsString(orderMessageDTO);
            channel.confirmSelect();
            for (int i = 0; i < 10; i++) {
                channel.basicPublish("exchange.order.restaurant", "key.restaurant", null, messageToSend.getBytes());
                log.info("message sent");
            }
            if(channel.waitForConfirms()){
                log.info("confirm OK");
            }else {
                log.info("confirm Failed");
            }
            Thread.sleep(100000);
        }

异步确认机制的实现方法(不推荐)

◆配置channel,开启确认模式: channel.confirmSelect()

◆在channel. 上添加监听: addConfirmListener, 发送消息后,会回调此方法,通知是否发送成功

◆异步确认有可能是单条,也有可能是多条,取决于MQ

        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");

        try (Connection connection = connectionFactory.newConnection();
             Channel channel = connection.createChannel()) {
            String messageToSend = objectMapper.writeValueAsString(orderMessageDTO);
            channel.confirmSelect();
            channel.addConfirmListener(new ConfirmListener() {
                public void handleAck(long deliveryTag, boolean multiple) throws IOException {
                   log.info("Ack, deliveryTag: {}, multiple: {}",  deliveryTag, multiple);
                }
                public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                    log.info("Nack, deliveryTag: {}, multiple: {}", deliveryTag, multiple);
                }
            });
            for (int i = 0; i < 10; i++) {
                channel.basicPublish("exchange.order.restaurant", "key.restaurant", null, messageToSend.getBytes());
                log.info("message sent");
            }
            Thread.sleep(100000);
        }

消息返回机制

消息发送后,中间件会对消息进行路由 若没有发现目标队列,中间件会通知发送方
Return Listener 会被调用

在RabbitMQ基础配置中有一个关键配置项:Mandatory

Mandatory若为false,RabbitMQ将直接丢弃无法路由的消息。

Mandatory若为true,RabbitMQ才会处理无法路由的消息。

channel.basicPublish("exchange.order.restaurant", 
                    "key.order",
                    true,             //Mandatory属性
                    null, 
                    messageToSend.getBytes());
//接收返回数据
channel.addReturnListener(new ReturnCallback() {
    @Override
    public void handle(Return returnMessage) {
        log.info("Message Return: returnMessage{}", returnMessage);
        //除了打印log,可以加别的业务操作
    }
});

消费端确认机制

◆默认情况下,消费端接收消息时,消息会被自动确认(ACK)

◆消费端消息处理异常时,发送端与消息中间件无法得知消息处理情况

自动ACK:消费端收到消息后,会自动签收消息

手动ACK:消费端收到消息后,不会自动签收消息,需要我们在业务代码中显式签收消息

发送方开启手动签收

channel.basicConsume("queue.order", 
                        false,         //autoAck: false手动确认  true自动确认
                        deliverCallback, 
                        consumerTag -> {
            });

接收方签收

//手动签收
channel.basicAck(message.getMessageProperties().getDeliveryTag(), 
                false               //multiple: false单条确定,true多条确定
);

接收方拒收

channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);

重回队列(不建议使用)

◆若设置了重回队列,消息被NACK之后,会返回队列末尾,等待进一步被处理
◆一般不建议开启重回队列,因为第-次处理异常的消息,再次处理,基本上也是异常

//开启重回队列
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);

消费端限流机制(QoS原理:一定数量的消息未确认,不再推送)

◆prefetchCount: 针对一个消费端最多推送多少未确认消息

◆global: true:针对整个消费端限流false:针对当前channel

◆prefetchSize:0 (单个消息大小限制, 一般为0)

◆prefetchSize与global两项,RabbitMQ暂时未实现

channel.basicQos(2);
channel.basicConsume("queue.order", 
                        false,         //autoAck: false手动确认  true自动确认
                        deliverCallback, 
                        consumerTag -> {
            });

消息过期机制(过期时间TTL)

RabbitMQ的过期时间称为TTL (Time to Live),

生存时间 RabbitMQ的过期时间分为消息TTL和队列TTL

消息TTL设置了单条消息的过期时间
队列TTL设置了队列中所有消息的过期时间

怎么设置

TTL的设置主要考虑技术架构与业务

TTL应该明显长于服务的平均重启时间

建议TTL长于业务高峰期时间

设置单条消息超时时间TTL(二选一即可)

AMQP.BasicProperties properties = new AMQP.BasicProperties.builder().expiration("15000").build();

channel.basicPublish("exchange.order.restaurant", "key.restaurant", properties, messageToSend.getBytes());

设置队列超时时间TTL(二选一即可)

Map<String, Object> args = new HashMap<String, Object>(); 
args.put("x-message-ttl", 10000);    //队列中消息的过期时间
args.put("x-max-length", 5);         //队列最大长度
args.put("x-expire", 10000);         //队列的过期时间,一般不建议使用

channel.queueDeclare(
        "queue.restaurant",
        true,
        false,
        false,
        args);

死信队列

 死信队列就是普通的队列,过期的消息会经过死信交换机(普通交换机),路由到死信队列,再由专门监听死信队列的服务,处理这些超时消息。

什么消息会成为死信?

消息被拒绝(reject/nack)并且requeue=false

消息过期(TTL到期)

队列达到最大长度

设置死信队列

1.设置转发、接收死信的交换机和队列: 

Exchange: dlx.exchange

Queue: dlx.queue

RoutingKey: #

2.在需要设置死信的队列加入参数: 

x-dead-letter-exchange = dlx.exchange

RabbitMQ与SpringBoot整合

RabbitAdmin:用来管理RabbitMQ

创建方法:

ConnectionFactory connectionFactory = new CachingConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setPassword("guest");
connectionFactory.setUsername("guest");

//declareExchange:创建交换机  (常用)
//deleteExchange:删除交换机   (尽量不要在代码里删除操作)
//declareQueue:创建队列       (常用)
//deleteQueue:删除队列        (尽量不要在代码里删除操作)
//purgeQueue:清空队列         (尽量不要在代码里删除操作)
//declareBinding:新建绑定关系 (常用)
//removeBinding:删除绑定关系  (尽量不要在代码里删除操作)
//getQueueProperties:查询队列属性

RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
//创建队列
Exchange exchange = new DirectExchange("exchange.order.restaurant");
rabbitAdmin.declareExchange(exchange);
//创建队列 
Queue queue = new Queue("queue.order");
rabbitAdmin.declareQueue(queue);
//绑定
Binding binding = new Binding(
        "queue.order",
        Binding.DestinationType.QUEUE,
        "exchange.order.restaurant",
        "key.order",
        null);
rabbitAdmin.declareBinding(binding);

利用spring boot的特性,RabbitAdmin声明式配置

创建方法:等同于上面的

    @Bean
    public Exchange exchange1() {
        return new DirectExchange("exchange.order.restaurant");
    }

    @Bean
    public Queue queue1() {
        return new Queue("queue.order");
    }

    @Bean
    public Binding binding1() {
        return new Binding(
                "queue.order",
                Binding.DestinationType.QUEUE,
                "exchange.order.restaurant",
                "key.order",
                null
        );
    }

    @Bean
    public ConnectionFactory connectionFactory() {
        CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setPassword("guest");
        connectionFactory.setUsername("guest");
        connectionFactory.setPublisherConfirmType(
            CachingConnectionFactory.ConfirmType.CORRELATED);//开启发送端消息确认,单点确认
        connectionFactory.setPublisherReturns(true);    //开启消息返回机制
        connectionFactory.createConnection();//让spring调用,一般不需要配置
        return connectionFactory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(@Autowired ConnectionFactory connectionFactory) {
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        rabbitAdmin.setAutoStartup(true);
        return rabbitAdmin;
    }

RabbitTemplate(收发)

创建RabbitTemplate的Bean

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setMandatory(true);      //开启消息返回机制
        //消息没有送达的处理
        rabbitTemplate.setReturnsCallback(returnedMessage -> {
            log.info("message:{}, replyCode:{}, replyText:{}, Exchange:{}, routingKey:{}",
                    returnedMessage.getMessage(), returnedMessage.getReplyCode(),
                    returnedMessage.getReplyText(), returnedMessage.getExchange(),
                    returnedMessage.getRoutingKey());
        });
        //消息送达的处理
        rabbitTemplate.setConfirmCallback((correlationData, b, s) -> {
            log.info("correlationData:{}, ack:{}, cause:{}",
                    correlationData, b, s);
        });
        return rabbitTemplate;
    }

在代码中使用

MessagePostProcessor messagePostProcessor = message -> {
    //设置消息的持久
    message.getMessageProperties()
            .setDeliveryMode(MessageDeliveryMode.PERSISTENT);
    //设置消息延迟的时间,单位ms
    message.getMessageProperties()
            .setDelay(30*60*1000);
    //设置消息过期的时间,单位ms
    message.getMessageProperties()
            .setExpiration("15000");
    return message;
};

//需要返回的消息
CorrelationData correlationData = new CorrelationData();
correlationData.setId(orderId);

rabbitTemplate.convertAndSend(
        RabbitMqConfig.DELAY_LAZY_EXCHANGE,
        RabbitMqConfig.DELAY_LAZY_QUEUE,
        order.getOrderId(),
        messagePostProcessor
        correlationData);    //在这里加入需要返回的消息

//支持自己写
rabbitTemplate.execute(channel -> {
    channel.
    。。。。。。。。。。
})

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值