RabbitMQ

1、MQ的认知

MQ:Message Queue 消息队列

是一个服务,接收消息(数据),先进先出。

作用:

1、把串行工作改为并行进行,提升运行效率

在这里插入图片描述
2、流量削峰

在这里插入图片描述
都有哪些MQ?

市面上比较火爆的几款MQ:

ActiveMQ,RocketMQ,Kafka,RabbitMQ。

  • 语言的支持:ActiveMQ,RocketMQ只支持Java语言,Kafka可以支持多们语言,RabbitMQ支持多种语言。

  • 效率方面:ActiveMQ,RocketMQ,Kafka效率都是毫秒级别,RabbitMQ是微秒级别的。

  • 消息丢失,消息重复问题: RabbitMQ针对消息的持久化,和重复问题都有比较成熟的解决方案。

  • 学习成本:RabbitMQ非常简单。

RabbitMQ是由Rabbit公司去研发和维护的,最终是在Pivotal。

RabbitMQ严格的遵循AMQP协议,高级消息队列协议,帮助我们在进程之间传递异步消息。

2、搭建RabbitMQ的环境

1、先安装RabbitMQ的依赖环境erlang

​ 最好默认路径(非中文),一路下一步即可

2、配置环境变量,新建环境变量

在这里插入图片描述
在这里插入图片描述
安装rabbitmq,最后默认路径,一路下一步

新建系统环境变量

在这里插入图片描述编辑系统环境变量path

在这里插入图片描述启动监控程序

进入到rabbitmq的安装路径下的sbin文件夹下,cmd打开命令窗口,拖拽rabbitmq-plugins.bat后,输入 enable rabbitmq-management 来启动

在这里插入图片描述
到程序下:

在这里插入图片描述启动后,访问网址

在这里插入图片描述

3、RabbitMQ的体系结构

在这里插入图片描述

4、RabbitMQ的通讯

4、1 简单通讯方式

特点:1、一个生产者

​ 2、一个交换机—默认的

​ 3、一个队列—默认的

​ 4、一个消费者

在这里插入图片描述1、新建生产者

2、添加jar依赖

<packaging>war</packaging>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.rabbitmq</groupId>
        <artifactId>amqp-client</artifactId>
        <version>5.6.0</version>
    </dependency>
</dependencies>

3、配置文件、启动类

4、在common中编写工具类:获取rabbitmq服务的连接

package com.qf.health2205common.util;

import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

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

public class RabbitMQUtil {
    private static ConnectionFactory factory;
    static {
        factory=new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setPort(5672);
//        factory.setUsername("guest");
//        factory.setPassword("guest");
//        factory.setVirtualHost("/");
    }
    //获取连接的方法
    public static Connection getMQConnection(){
        Connection connection=null;
        try {
            connection=factory.newConnection();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }
        return connection;
    }
}

5、在业务逻辑层写入消息到队列中

package com.qf.health2205publisher.service.impl;

import com.qf.health2205common.util.RabbitMQUtil;
import com.qf.health2205publisher.service.PublisherService;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import org.springframework.stereotype.Service;

import java.io.IOException;

@Service
public class PublisherServiceImpl implements PublisherService {
    //声明RabbitMQ的连接对象
    private Connection connection= RabbitMQUtil.getMQConnection();
    @Override
    public String addMessageToMq(String meg) {
        //定义管道
        Channel channel=null;
        String result="";
        try {
            channel=connection.createChannel();

            //把消息通过管道,写入交换机中
            //参数1:交换机的名字,“”  使用默认的交换机
            //参数2:是队列的名字,在同一服务器内,队列不能重名
            //参数3:写入消息时的携带信息,null
            //参数4:写入的消息,必须是byte[]类型
            channel.basicPublish("","java220501",null,meg.getBytes());
            result= "add Message success!";
        } catch (IOException e) {
            result="add Message failed!";
            e.printStackTrace();
        }
        return result;
    }
}

消费者的controller

package com.qf.health2205publisher.controller;

import com.qf.health2205publisher.service.PublisherService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/pmq")
public class PublisherController {
    @Autowired
    private PublisherService publisherService;

    @GetMapping("/add1")
    public String add1(String meg){
        return publisherService.addMessageToMq(meg);
    }
}

创建消费者服务

4、2 work 工作队列通讯方式

特点:一个生产者、一个交换机、一个队列,多个消费者

在这里插入图片描述
设置每个消费者的消费能力:一次只消费一个消息

//定义消费者每次只消费1个消息
channel.basicQos(1);

自动消息确认删除的缺点:消费者有可能没有把消息消费成功,但是消息已经从队列删除了,导致消息的丢失

设置手动消息确认删除:当消息消费成功后,才从队列中删除该消息。

在这里插入图片描述又增加一个消费者,两个消费者从同一个队列中消费消息

4、3 发布订阅通讯方式

特点:生产者一个,交换机一个,多个队列,每个队列对应若干个消费者

场景:一个消息,同时投递到多个队列中

下订单: 饿了么 作为生产者,把一个订单信息同时写入 通知商家的队列 ----商家接单程序是消费者

​ 通知骑手的队列 —骑手接单程序是消费者

​ 库存队列 —写入订单到数据库的消费者

生产者:

1、交换机的类型:fanout 广播类型

2、声明多个队列

@Override
public String addMessageToMq2(String meg) {
    //获取管道
    Channel channel=null;
    String result="";
    try {
        channel=connection.createChannel();
        //定义交换机
        //参数1:交换机的名字 ,要唯一
        //参数2:交换机的类型  FANOUT---广播类型   能把一个消息同时投递到多个队列中
        channel.exchangeDeclare("exchange1", BuiltinExchangeType.FANOUT);

        //队列绑定
        //参数1:声明的队列的名字,不要重名
        //参数2:哪个交换机向此队列投递消息,交换机的名字
        //参数3:路由规则,"" 无路由规则
        channel.queueBind("java220502","exchange1","");
        channel.queueBind("java220503","exchange1","");
        
        //写入消息
        channel.basicPublish("exchange1","",null,meg.getBytes());
        result="add2 success";
    } catch (IOException e) {
        result="add2 failed";
        e.printStackTrace();
    }

    return result;
}

消费者:让不同的消费者从不同的队列中来消费消息

在这里插入图片描述

4、4 route路由通讯方式

特点:一个生产者,一个交换机,多个队列,每个队列对应自己的若干个消费者

交换机根据路由规则,把消息投递到符合路由规则的队列中

在这里插入图片描述
生产者端:

1、定义多个队列,指定路由规则

2、交换机的类型是direct定向类型

@Override
public String addMessageToMq3(String meg, String route) {
    Channel channel=null;
    String result="";
    try {
        channel=connection.createChannel();

        //定义交换机的类型
        //参数1:交换机的名字
        //参数2:交换机的类型---DIRECT,把消息投递到符合路由规则的队列中,路由规则中不允许有通配符
        channel.exchangeDeclare("exchange2",BuiltinExchangeType.DIRECT);

        //绑定队列
        //参数1:队列的名字
        //参数2:指定哪个交换机向此队列写入消息
        //参数3:路路由规则
        channel.queueBind("java220504","exchange2","food");
        channel.queueBind("java220505","exchange2","play");
        
        //写入消息
        //参数2:写入消息时传递的路由规则
        channel.basicPublish("exchange2",route,null,meg.getBytes());
        result="add3 success";
    } catch (IOException e) {
        result="add3 failed";
        e.printStackTrace();
    }
    return result;
}

消费者端,修改消费者消费的队列即可

交换机把消息写到符合路由规则的队列中。

4、5 topic路由通讯方式

特点:一个生产者,一个交换机,多个队列,每个队列对应消费者

​ 交换机把消息投递到符合路由规则的队列中,但是路由规则支持通配符

支持通配符的路由规则的语法:

字符串1.字符串2.字符串3

同配置只允许出现 * #

*仅代表一组字符串 # 代表若干组字符串

java.part4.* 路由规则中只要是java.part4.任何字符串都可以

java.# java.part1.javase java.part2.javaweb java.part3.framework

#.java

在这里插入图片描述生产者:

1、交换机的类型是topic

2、路由规则中使用通配符

@Override
public String addMessageToMq4(String meg, String route) {
    Channel channel=null;
    String result="";
    try {
        channel=connection.createChannel();

        //定义交换机的类型
        //参数2:交换机的类型---TOPIC,支持陆游与规则中的通配符
        channel.exchangeDeclare("exchange3",BuiltinExchangeType.TOPIC);

        //队列绑定:交换机   带通配符的路由规则
        channel.queueBind("java220506","exchange3","java.part4.*");
        channel.queueBind("java220507","exchange3","#.python");

        //写入消息
        channel.basicPublish("exchange3",route,null,meg.getBytes());
        result="add4 success";
    } catch (IOException e) {
        result="add4 failed";
        e.printStackTrace();
    }

    return result;
}

5、消息的可靠性

MQ属于内存存储消息,需要确保消息不丢失

在这里插入图片描述1、使用confirm机制确保生产者成功的将消息写入队列

2、使用return机制记录交换机写入队列失败的消息

3、让队列持久化,确保队列在MQ当即后,依然不丢失消息

4、使用手动Ack,确保消息被成功消费

5、1:Confirm机制 确保生产者把消息成功的写入交换机

在这里插入图片描述当批量把消息写入交换机时,需要批量确认

@Override
public String addMessageToMq5(String meg) {
    Channel channel=null;
    String result="";
    try {
        channel=connection.createChannel();
        
        //开启confirm机制
        channel.confirmSelect();
        //批量写入
        for(int i=0; i<100; i++){
            meg=meg+i;
            channel.basicPublish("","java220508",null,meg.getBytes());
        }
        //当批量写入消息到交换机,如果有1个消息写入失败,那么之前写入的消息均撤销,并且抛出异常
        channel.waitForConfirmsOrDie();
        result="add5 success";
    } catch (IOException | InterruptedException e) {
        result="add5 failed";
        e.printStackTrace();
    }
    return null;
}

异步确认:添加监听,监听批量操作中的每一个消息,谁写入成功了,谁写入失败,认为对写入失败的消息进行处理,无需对写入成功的消息进行撤销。

@Override
public String addMessageToMq6(String meg) {
    Channel channel=null;
    String result="";
    try {
        channel=connection.createChannel();

        channel.confirmSelect();
        for(int i=0; i<99; i++){
            meg=meg+i;
            channel.basicPublish("","java220509",null,meg.getBytes());
        }
        //异步confirm ,添加监听
        channel.addConfirmListener(new ConfirmListener() {
            @Override
            public void handleAck(long l, boolean b) throws IOException {
                //监听到某个消息写入交换机成功后,回调的方法
                System.out.println(l+"消息写入成功!");
            }

            @Override
            public void handleNack(long l, boolean b) throws IOException {
                //监听到某个消息写入交换机失败,回调的方法
                System.out.println(l+"消息写入失败!");
            }
        });
        result="add6 success";
    } catch (IOException e) {
        result="add6 failed";
        e.printStackTrace();
    }

    return result;
}

5、2 使用return机制,确保交换机里的消息成功的写入队列中

在这里插入图片描述【面试题】在rabbit mq中,如何确保消息的可靠性?

从如上4点。

6、RabbitMQ的常见问题及解决方案

6、1:消息的重复消费问题

当一个队列有多个消费者时,如果其中一个消费者正在消费某个消息,另一个消费者也来消费此消息,造成同一个消息被多个消费者重复消费。

锁:锁的是线程

在分布式系统下,传统的锁就失效,无法锁进程。

需要分布式锁:使用Redis

1、当某个消费者要消费消息时,先到Redis中读取某个变量(锁)的值

2、如果读取不到或者读取到的是0,意味着当前服务可以消费消息

3、把锁变量的值改为1,同时去消费消息

4、如果读取到的是1,则不去消费消息

5、当消费消息的进程消费结束后,把锁变量的值改为0.

6、【强调】没有获得锁的进程,需要自行每间隔一段时间,去尝试获取锁。

@Override
public void getMessageFromMq() {
    //说明,消费者需要一直监控队列,所以是无返回值的
    Channel channel=null;
    try {
         channel=connection.createChannel();
        //定义消费者每次只消费1个消息
        channel.basicQos(1);
        //定义队列
        //参数1:从哪个队列中获取消息,队列的名字
        //参数2:队列是否支持持久化   需要支持,当MQ服务器宕机重启后,还能加载到消息,防止消息的丢失
        //参数3:是否排外,是否只允许一个消费者消费
        //参数4:当消费者不存在时,是否删除队列?
        //参数5:是否有其他携带消息处理
        channel.queueDeclare("java220506",true,false,false,null);
        Channel channel1=channel;
        //定义监听:监控队列中是否有消息,一旦有消息,就获取消息
        DefaultConsumer defaultConsumer=new DefaultConsumer(channel){
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                Jedis jedis= RedisUtil.getJedis();
                jedis.select(3);
                String lock=jedis.get("mqLock");
                if(lock==null){
                    //加锁
                    jedis.setex("mqLock",3,"1");
                    //可以消费
                    //当监听到队列中有消息时,就调用此方法从队列中获取消息
                    String meg=new String(body);
                    //消费
                    System.out.println("consumer---从队列java220506中获取到消息:"+meg);
                    //在此处判断消息是否消费成功,如果成功则从队列中删除消息
                    channel1.basicAck(envelope.getDeliveryTag(),false);
                    //消费结束  解锁
                    jedis.set("mqLock","0");
                }else{
                    int intLock=Integer.parseInt(lock);
                    if(intLock==0){
                        jedis.set("mLock","1");
                        //当监听到队列中有消息时,就调用此方法从队列中获取消息
                        String meg=new String(body);
                        //消费
                        System.out.println("consumer---从队列java220506中获取到消息:"+meg);
                        //在此处判断消息是否消费成功,如果成功则从队列中删除消息
                        channel1.basicAck(envelope.getDeliveryTag(),false);
                        jedis.set("mqLock","0");
                    }
                }
            }
        };
        //设置如何删除消息
        //参数1:从哪个队列删除
        //参数2:true  当消费者从队列取出消息后,立刻删除
        //此种删除策略:自动消息确认删除,当消费者把消息取出后,立刻删除消息

        //设置手动ACK  参数2设置为false  消息取出后不删除
        channel.basicConsume("java220506",false,defaultConsumer);

        System.out.println("消费者开始监听java220506队列……");
        System.in.read();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

【注意】加锁使用setex();给锁设置有效期,防止死锁,防止当前进程加上锁后,宕机了,宕机后就不能解锁,造成死锁,设置时间后,在时间内完成消费。

6、2 如何处理消息堆积问题

消息堆积:队列中的消息存满了

造成原因:

1、生产者生产消息快,消费消息慢

2、消费者宕机,无法消费

解决方案:

1、增加消费者的消费能力—开启当前消费者的多线程模式
—增加消费者服务

2、设置死信交换机,死信就会自动进入死信交换机,死信交换机把死信写入死信队列。

3、使用延迟队列:当队列满后,再有消息,直接写入磁盘,并且记录写入顺序

死信:当队列满后,再有消息写入时,队列头部的消息就是死信

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值