RabbitMQ 常见使用场景

        概念

RabbitMQ 消息队列。消息队列是典型的:生产者、消费者模型。生产者不断向消息队列中生产消息,消费者不断的从队列中获取消息。因为消息的生产和消费都是异步的,而且只关心消息的发送和接收,没有业务逻辑的侵入,这样就实现了生产者和消费者的解耦

1.0 消息队列

        1.1 消息

        消息包含消息体和标签,消息题表示带有业务逻辑的数据,如json数据,标签表示这这个消息的key,比如交换机名称和路由键。

        消费者连接到 RabbitMQ 服务器,并订阅到队列上。当消费者消费一条消息时,只是消费消息的消息体。在消息路由的过程中,消息的标签会丢弃,存入到队列中的消息只有消息体,消费者也只会消费到消息体,也就不知道消息的生产者是谁,当然消费者也不需要知道

        1.2 队列

        RabbitMQ中的消息都只能存储在队列中,RabbitMQ 的生产者生产消息并最终投递到队列中,消费者可以从队列中获取消息并消费。

        多个消费者可以订阅同一个队列,这时队列中的消息会被平均分摊 CRound-Robin ,即轮询)给多个消费者进行处理,而不是每个消费者都收到所有的消息并处理。

2.0 交换机

        生产者将消息发送给交换器时, 需要一个RoutingKey。交换器和队列绑定时需要一个BindingKey。当BindingKey和RoutingKey相匹时, 消息会被路由到对应的队列中。在绑定多个队列到同一个交换器的时候,这些绑定允许使用相同的BindingKey。

        BindingKey并不是在所有的情况下都能生效,它依赖于交换器类型。如fanout 类型的交换器就会无视BindingKey,而是将消息路由到所有绑定到该交换器的队列中。

3.0 工作模式

        3.1 简单的工作模式一个生产者一个消费者

        生产者java代码实现

//1.0 建立连接
        ConnectionFactory factory = new ConnectionFactory();

        //设置mq服务器地址信息
        factory.setHost("172.1.1.183");
        factory.setPort(5672);
        factory.setVirtualHost("/fei");
        factory.setUsername("root");
        factory.setPassword("1234");

        // 2.0 建立连接
        Connection connection = factory.newConnection();

        // 3.0 建立通道
        Channel channel = connection.createChannel();

        //4.0 建立队列
        String queueName = "simple.queue";
        // 参数含义  队列名称,是否持久化,是否独占,没有消费是否丢弃,参数列表
        channel.queueDeclare(queueName, false, false, false, null);

        // 5.0 发送消息
        String message = "hello, word!";
        channel.basicPublish("", queueName, null, message.getBytes());
        System.out.println("发送消息成功:【" + message + "】");

        //5.0 关闭通道
        channel.close();
        connection.close();

消费者java代码的实现

// 1.建立连接
        ConnectionFactory factory = new ConnectionFactory();
        // 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
        factory.setHost("172.1.1.183");
        factory.setPort(5672);
        factory.setVirtualHost("/fei");
        factory.setUsername("root");
        factory.setPassword("1234");
        // 1.2.建立连接
        Connection connection = factory.newConnection();

        // 2.创建通道Channel
        Channel channel = connection.createChannel();

        // 3.创建队列
        String queueName = "simple.queue";
        channel.queueDeclare(queueName, false, false, false, null);

        // 4.订阅消息
        channel.basicConsume(queueName, true, new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body) throws IOException {
                // 5.处理消息
                String message = new String(body);
                System.out.println("接收到消息:【" + message + "】");
            }
        });
        System.out.println("等待接收消息。。。。");

    }

springboot集成rabbitmq代码实现

@RestController
@RequestMapping("/rabbitmq")
public class RabbitMQController {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @RequestMapping("/simpleMode")
    public ResponseResult simple(){

        // 参数1 队列名称     参数2 消息内容
        rabbitTemplate.convertAndSend("simpleMode","hello word");
        return new ResponseResult(200,"生产消息成功");
    }
}

消费者

@Component
@RabbitListener(queuesToDeclare = @Queue("simpleMode"))
public class SimpleConsumer {
    Logger logger =LoggerFactory.getLogger(SimpleConsumer.class);
    @RabbitHandler
    public void reception(String message){
        logger.info("简单模式接收到队列 simpleMode 消息为-->{}",message);
    }
}

        3.2 工作队列 

        work queues 模型,创建一个工作队列,用于在多个工作者之间分配耗时的任务。也就是说,多个消费者可以消费同一个消息队列里面的消息,并且是通过轮询的形式,比如说有1,2,3,4这四个消息,按这个模型平均分配的原则,C1和C2分别可以消费两条消息,且都是按顺序的

        生产者消息

    @RequestMapping("/workQueue")
    public ResponseResult workQueue(){
        for(int i =0;i<20;i++){
            rabbitTemplate.convertAndSend("workQueue","work->"+i);
            logger.info("工作队列模式 向队列workQueue发送消息为-->work->{}",i);
        }
        return new ResponseResult(200,"生产消息成功");

    }

消费者

@Component
public class workQueueConsumer {
    Logger logger = LoggerFactory.getLogger(workQueueConsumer.class);
    @RabbitListener(queuesToDeclare = @Queue("workQueue"))
    public void workQueue1(String message){
        logger.info("工作队列模式,消费者1号收到workQueue队列消息为 --> {}",message);
    }
    @RabbitListener(queuesToDeclare = @Queue("workQueue"))
    public void workQueue2(String message){
        logger.info("工作队列模式,消费者2号收到workQueue队列消息为 --> {}",message);
    }
}

        3.3 发布-订阅模型

        多个消费者可以同时接收到消息并进行消费

        生产者代码

    @RequestMapping("/fanoutQueue")
    public ResponseResult fanoutQueue(){
        for(int i =0;i<5;i++){
            rabbitTemplate.convertAndSend("fanout_exchange","","fanout+"+i);
            logger.info("发布订阅模式 向队列workQueue发送消息为-->fanout->{}",i);
        }
        return new ResponseResult(200,"生产消息成功");
    }

        交换机以及绑定代码


@Configuration
public class RabbitConfig {
    private static final  String FANOUT_EXCHANGE_NAME = "fanout_exchange";
    private static final String FANOUT_QUEUE_A = "fanout_queue_a";
    private static final String FANOUT_QUEUE_B = "fanout_queue_b";
    private static final String FANOUT_QUEUE_C = "fanout_queue_c";
//    private final String FANOUT_QUEUE_NAME3 = "fanout_queue3";

    //创建交换机
    @Bean
    public FanoutExchange fanoutExchange()
    {
        return ExchangeBuilder.
                fanoutExchange(FANOUT_EXCHANGE_NAME) //交换机类型 ;参数为名字
//                .topicExchange(FANOUT_EXCHANGE_NAME)
                .durable(true)//是否持久化,true即存到磁盘,false只在内存上
                .build();
    }

    //创建队列
    @Bean
    public Queue fanoutA()
    {
        return new Queue(FANOUT_QUEUE_A);
    }
    @Bean
    public Queue fanoutB()
    {
        return new Queue(FANOUT_QUEUE_B);
    }
    @Bean
    public Queue queueC(){
        return new Queue(FANOUT_QUEUE_C);
    }

    // 将三个队列绑定到交换机上【队列订阅交换机】 ------------------------------------------------------------------
    @Bean
    Binding bindingExchangeA() {
        return BindingBuilder.bind(fanoutA()).to(fanoutExchange());
    }
    @Bean
    Binding bindingExchangeB() {
        return BindingBuilder.bind(fanoutB()).to(fanoutExchange());
    }
    @Bean
    Binding bindingExchangeC() {
        return BindingBuilder.bind(queueC()).to(fanoutExchange());
    }

        消费者代码


@Component
public class FanoutConsumer {
    Logger logger = LoggerFactory.getLogger(FanoutConsumer.class);
    @RabbitListener(queues = "fanout_queue_a")
    public void fanoutMessage1(String message){
        logger.info("发布订阅模式,消费者1号收到fanout_queue队列消息为 --> {}",message);
    }
    @RabbitListener(queues = "fanout_queue_b")
    public void fanoutMessage2(String message){
        logger.info("发布订阅模式,消费者2号收到fanout_queue队列消息为 --> {}",message);
    }
    @RabbitListener(queues = "fanout_queue_c")
    public void fanoutMessage3(String message){
        logger.info("发布订阅模式,消费者3号收到fanout_queue队列消息为 --> {}",message);
    }
}

        3.4 路由模式

        Routing 模型,也称直连模型,可以对消息指明路由键名称,消息队列绑定路由键名称,这样交换机就能根据路由键名称将消息匹配到相应的消息队列上。

        生产者代码

    /**
     * Routing 模型 路由模式
     * @return
     */
    @RequestMapping("/routing")
    public ResponseResult routing() {
        rabbitTemplate.convertAndSend("direct_exchange", "info","路由消息hello info!");
        rabbitTemplate.convertAndSend("direct_exchange", "dev","路由消息hello dev!");
        rabbitTemplate.convertAndSend("direct_exchange", "test","路由消息hello test!");
        logger.info("路由模式 向队列workQueue发送消息为-->route->{}","路由消息hello word!");
        return new ResponseResult(200,"路由生产者成功");
    }

        交换机绑定代码

@Configuration
public class RabbitConfig {
    private static final  String FANOUT_EXCHANGE_NAME = "fanout_exchange";
    private static final  String DIRECT_EXCHANGE_NAME = "direct_exchange";
    private static final String FANOUT_QUEUE_A = "fanout_queue_a";
    private static final String FANOUT_QUEUE_B = "fanout_queue_b";
    private static final String FANOUT_QUEUE_C = "fanout_queue_c";
//    private final String FANOUT_QUEUE_NAME3 = "fanout_queue3";

    //创建交换机
    @Bean
    public FanoutExchange fanoutExchange()
    {
        return ExchangeBuilder.
                fanoutExchange(FANOUT_EXCHANGE_NAME) //交换机类型 ;参数为名字
//                .topicExchange(FANOUT_EXCHANGE_NAME)
                .durable(true)//是否持久化,true即存到磁盘,false只在内存上
                .build();
    }
    @Bean
    public DirectExchange directExchange() {
        /**
         * directExchange的参数说明:
         * 1. 交换机名称
         * 2. 是否持久化 true:持久化,交换机一直保留 false:不持久化,用完就删除
         * 3. 是否自动删除 false:不自动删除 true:自动删除
         */
        return  ExchangeBuilder
                .directExchange(DIRECT_EXCHANGE_NAME)
                .durable(true)
                .build();
    }


    //创建队列
    @Bean
    public Queue fanoutA()
    {
        return new Queue(FANOUT_QUEUE_A);
    }
    @Bean
    public Queue fanoutB()
    {
        // 参数1队列名和 参数2否持久化
        return new Queue(FANOUT_QUEUE_B,true);
    }
    @Bean
    public Queue queueC(){
        return new Queue(FANOUT_QUEUE_C,true);
    }

    // 将三个队列绑定到交换机上【队列订阅交换机】 ------------------------------------------------------------------
    @Bean
    Binding bindingExchangeA() {
        return BindingBuilder.bind(fanoutA()).to(directExchange()).with("info");
    }
    @Bean
    Binding bindingExchangeB() {
        return BindingBuilder.bind(fanoutB()).to(directExchange()).with("test");
    }
    @Bean
    Binding bindingExchangeC() {
        return BindingBuilder.bind(queueC()).to(directExchange()).with("dev");
    }

}

消费端

@Component
public class RoutingConsumer {
    Logger logger = LoggerFactory.getLogger(RoutingConsumer.class);
    @RabbitListener(queues = "fanout_queue_a")
    public void fanoutMessage1(String message){
        logger.info("路由模式,消费者a号收到fanout_queue队列消息为 --> {}",message);
    }
    @RabbitListener(queues = "fanout_queue_b")
    public void fanoutMessage2(String message){
        logger.info("路由模式,消费者b号收到fanout_queue队列消息为 --> {}",message);
    }
    @RabbitListener(queues = "fanout_queue_c")
    public void fanoutMessage3(String message){
        logger.info("路由模式,消费者c号收到fanout_queue队列消息为 --> {}",message);
    }
}

        3.5 主题模式

        在Routing原有模型的基础上进行了增强,可以使用通配符的形式来匹配路由键。

        和路由模式差不多,只不过改边了一下路由键的通配符。这里不进行阐述。

4.0 消息的可靠性

        rabbitmq由 生产者→交换机→队列→消费者这些组成,消息丢失可以再这些部分传递或者使用中丢失,所以保证消息的可靠性可以从以下三点保证。

确认模式(confirm):可以监听消息是否从生产者成功传递到交换机。
退回模式(return):可以监听消息是否从交换机传递到队列。
消费者消息确认(Consumer Ack)可以监听消费者是否成功处理消息。

        三种模式刚好监听完RabbitMQ的一整套流程。即我们能够由这三种模式得到消息的传递及处理的结果。

        4.1 confirm 模式实现

        在配置文件中配置

publisher-confirm-type: correlated
           rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
                // 参数1 相关配置信息 参数2 交换机是否收到rabbitmq的消息  参数3 失败的原因
                @Override
                public void confirm(CorrelationData correlationData, boolean b, String s) {
                    if(b){
                        logger.info("生产者消息发送成功!!!");
                    }else {
                        logger.info("生产者消息发送到rabbitmq失败:原因为 -->{}",s);
                        //失败后处理的业务逻辑
                    }
                }
            });
             //消息进行发送
             rabbitTemplate.convertAndSend("fanout_exchange","","fanout+"+i);

        4.2 退回模式(return)

        配置文件配置

publisher-returns: true

        代码实现

//定义退回模式的回调方法
            rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
                /**
                 * @param returnedMessage 失败后将失败信息封装到参数中
                 */
                @Override
                public void returnedMessage(ReturnedMessage returnedMessage) {
                    logger.info("消息对象:{}",returnedMessage.getMessage());
                    logger.info("错误码:{}",returnedMessage.getReplyCode());
                    logger.info("错误信息:{}",returnedMessage.getReplyText());
                    logger.info("交换机:{}",returnedMessage.getExchange());
                    logger.info("路由键:{}",returnedMessage.getRoutingKey());
                    //处理消息
                }
            });

            rabbitTemplate.convertAndSend("fanout_exchange","","fanout+"+i);
            logger.info("发布订阅模式 向队列workQueue发送消息为-->fanout->{}",i);

        4.3 消费者消息确认

        默认情况下是开启自动确认,当消费者出现异常,消息会在消息队列中清空,可以选择手动确认。

        配置文件中开启配置

listener:
  simple:
    acknowledge-mode: manual

        代码中实现手动确认

    Logger logger = LoggerFactory.getLogger(FanoutConsumer.class);
    @RabbitListener(queues = "fanout_queue_a")
    public void fanoutMessage1(Message message, Channel channel) throws InterruptedException, IOException {

        //消息投递序号,消息每次投递该值都会+1
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try
        {
            //int i = 1 / 0;//模拟处理消息出现bug
            logger.info("发布订阅模式,消费者1号收到fanout_queue队列消息为 --> {}",message);
            //签收消息
            /**
             * 参数1:消息投递序号
             * 参数2:是否一次可以签收多条消息
             */
            channel.basicAck(deliveryTag,true);
        }catch (Exception e)
        {
            System.out.println("消息消费失败");
            Thread.sleep(2000);
            //拒签消息
            /**
             * 参数1:消息投递序号
             * 参数2:是否可以一次拒签多条消息
             * 参数3:拒签后消息是否重回队列
             */
            channel.basicNack(deliveryTag,true,true);
        }
        
    }

5.0 高级特性

        5.1 消费端限流

        消费限流其实就是规定消费端获取消息的快慢限制。首先需要配置开启手动确认和消费端最多拉取消息的数量。

    listener:
      simple:
        #手动开启ack
        acknowledge-mode: manual
        #消费端最多拉取消息的数量
        prefetch: 10

        5.2 公平分发和不公平分发

由于消费者的处理消息速度不同,可能导致有些性能高的消费者处于等待状态,多个消息平均分发给消费者所以称为公平分发。

不公平分发则是消费者消费完可以继续消费消息。将prefetch设置为1即可。

        5.3 消息存活时间

当消息到达存活时间后还没有被消费,就会被移除队列。

消息存活时间又分为队列存活时间和单个消息存活时间。

队列存活时间,当QueueBuilder.ttl(时间)就可设置。

        5.4 优先队列

就是说在队列中指定可以设置的最大优先级,然后再对单条设置一个优先级数;可以在创建队列中设置,从new Queue(队列名)更改为QueueBuilder.maxpriority(10)设置返回队列。

        5.5 死信队列

死信队列和普通队列没有太大区别,只不过交换机更换为死信交换机deadLetterExchange。

死信队列用于:

1.队列消息长度到达限制。

2.消费者拒签消息,并且不把消息重新放回原队列。

3.消息到达存活时间未被消费。

        5.6 延迟队列

消息进入延迟队列不会立马进行消费,只有达到规定时间才能被消费。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

周小代

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值