RabbitMQ学习笔记

RabbitMQ

1.入门

1.概念

  • MQ:消息队列,是一种跨进程通信机制,用于上下游传递消息
  • RabbitMQ:消息中间件,接受,存储并转发消息

2.优点

  • 流量消峰:提高系统稳定型

    优点:避免消息量大时造成服务器宕机

    缺点:访问速度下降

  • 应用解耦:提高系统容错性和可维护性

    当子系统出现故障时,要处理的消息会缓存在MQ中,直到恢复,同时该过程中主系统不会故障

  • 异步处理:提升用户体验和系统吞吐量

    A服务发送给B服务后,B服务处理完将发消息给MQ,再由MQ发给A

  • 缺点

    系统可用性降低,复杂度提高,一致性问题

3.相关概念

  • Broker :接收和分发消息的应用,RabbitMQ Server 就是 Message Broker

  • Virtual Host :出于多租户和安全因素设计的,把 AMQP 的基本组件划分到一个虚拟的分组中,类似于网络中的 NameSpace 概念

  • Connection :provider/ consumer 和 broker 之间的 TCP 连接。

  • Channel :在 connection 内部建立的逻辑连接,如果应用程序支持多线程,通常每个 thread 创建单独的 channel 进行通讯,AMQPmethod 包含了 channel id帮助客户端和 message broker 识别 channel,所以channel 之间是完全隔离的。Channel 作为轻量级的 Connection 极大减少了操作系统建立 TCP connection 的开销

  • Exchange :message 到达 broker 的第一站,根据分发规则,匹配查询表中的 Routing Key,分发消息到 Queue 中去。

    常用的类型有: Direct (Point-To-Point) Topic (Publish-Subscribe) Fanout (MultiCast)

  • Queue :消息最终被送到这里等待 consumer 取走。

  • Binding : Exchange 和 Queue 之间的虚拟连接,binding 中可以包含 routing key。

在这里插入图片描述

4.四大核心概念

  • 生产者
  • 消费者
  • 交换机
  • 队列

5.工作模式

  • 简单模式(“Hello,World”)

  • 工作模式(work queues)

  • 发布与订阅模式(Publish/Subscribe)

  • 路由模式(routing)

  • 主题模式(topics)

  • 发布确认模式(RPC)

2.安装(Linux)

1.官网

  • https://www.rabbitmq.com/getstarted.html

2.创建好相关目录,并加入相关安装包

rpm -ivh erlang-21.3-1.el7.x86_64.rpm
yum install socat -y [从远端下载并安装]
rpm -ivh rabbitmq-server-3.8.8-1.el7.noarch.rpm

3.RabbitMQ命令

1.启动,关闭,重启

  • /sbin/service rabbitmq-server start
  • service rabbitmq-server stop
  • service rabbitmq-server restart

2.查看MQ状态

  • /sbin/rabbitmqctl status

3.开机自启动

  • chkconfig rabbitmq-server on

4.安装管理界面

  • rabbitmq-plugins enable rabbitmq management

5.关闭防火墙

  • systemctl stop firewalld
  • service iptables stop

6.监控平台界面

  • http://ip地址:15672

7.添加用户

  • 在linux中的/etc/rabbitmqm目录下新建文件:touch rabbitmq.config
  • 文件中:[{rabbit,[{loopback_users,[]}]}]

4.用户管理

1.超级管理员(Administrator)

  • 可登陆管理控制台,可查看所有的信息,并且可以对用户,策略进行操作。

2.监控者(Monitoring)

  • 可登陆管理控制台,同时可以查看 Rabbitmq 节点的相关信息(进程数,内存使用情况,磁盘使用情况等)

3.策略制定者(Policymaker)

  • 可登陆管理控制台, 同时可以对 policy 进行管理。但无法查看节点的相关信息(上图红框标识的部分)。

4.普通管理者(Management)

  • 仅可登陆管理控制台,无法看到节点信息,也无法对策略进行管理。

5.其他(Others)

  • 无法登陆管理控制台,通常就是普通的生产者和消费者。

6.查看用户列表

  • /sbin/rabbitmqctl list_users

7.创建用户

  • rabbitmqctl add user [用户名] [密码]

8.删除用户

  • rabbitmqctl delete user Username

9.修改密码

  • rabbitmqctl change_password Username Newpassword

10.设置角色

  • rabbitmqctl set_user_tags 用户名 角色名1 角色名2

5.用户权限

1.介绍

  • 指用户对exchange,queue的操作权限,包括配置,读写
    • 配置权限:exchange,queue的声明和删除
    • 读写权限:从queue获取消息,向exchange发送消息以及queue和exchange的绑定

2.操作指令

  • 查看用户权限: /sbin/rabbitmqctl list_user_permissions 用户名

  • 设置权限:rabbitmqctl set_permissions -p [主机] [用户名] [配置权限] [写权限] [读权限]

    例如: rabbitmqctl set permissions -p “/” andy “." ".” “.*”

  • 清除权限:rabbitmqctl clear permissions [-p VHostPath] 用户名

6.简单模式

在这里插入图片描述

1.创建个虚拟主机

  • http://192.168.227.131:15672网页的Virtual Hosts创建

2.导入依赖

  • <dependency>
     <groupId>com.rabbitmq</groupId>
     <artifactId>amqp-client</artifactId>
     <version>5.6.0</version>
    </dependency>
    

3.编码步骤

  • 创建连接工厂 :ConnectionFactory factory = new ConnectionFactory();

  • 设置相关参数

    factory.setHost("192.168.227.131");		//ip地址
    factory.setPort(5672);		//端口默认值
    factory.setVirtualHost("myhost");		//虚拟机名
    factory.setUsername("andy");		//用户名
    factory.setPassword("123");		//密码
    

    端口号默认为:5672 ,否则会报错

  • 创建连接Connection: Connection connection = factory.newConnection( );

  • 创建Channel: Channel channel = connection.createChannel();

  • 创建队列Queue: channel.queueDeclare(“myqueue”,true,true,false,null);

    ​ 参数一:queue(队列名)

    ​ 参数二:durable(是否持久化)当mq重启后,消息是否还在

    false:不持久化,创建者关闭连接后会自动删除队列(关闭通道不会)

    true:消息会持久化,创建者退出后队列和消息都能保存

    ​ 参数三:autoData:是否自动删除(true:没有消费者时自动删除)

    ​ 参数四:exclusive:是否独占(只有一个消费者监听)

    true:只能有一个人使用该队列

    ​ 参数五:参数值

  • 发送消息(简单模式:basicPublish): channel.basicPublish(“”,“myqueue”,null,msg.getBytes());

    • exchange:交换机名称(简单模式下交换机使用默认)
    • routingKey:路由名称(简单模式下使用对列名)
    • props:消息配置信息(可配置消息持久化)
    • body:发送消息数据

4.消费者

  • 创建连接工厂

  • 创建连接

  • 创建通道

  • 调用channel.basicConsumer(queueName,true,deliverCallback,cancelCallback)

    • 1.消费哪个消息

    • 2.消费成功后是否自动应答,true:自动应答,false:手动应答

    • 3.消费未成功的回调方法(自己声明)

      DeliverCallback deliverCallback = (consumerTag,message)->{
                  System.out.println(new String(message.getBody()));
      };
      
    • 4.消费取消消费的回调方法(自己声明)

      CancelCallback cancelCallback = consumerTag ->{
                  System.out.println("消息消费被中断");
       };
      

7.工作队列模式

1.介绍

  • 工作队列又称任务队列,避免立即执行资源密集型任务,而不得不等待它完成.把任务封装为消息并发送到队列,当多个工作线程时,这些工作线程将一起处理任务

在这里插入图片描述

2.消息应答

  • 消费者在接受消息并处理完毕后,告诉RabbitMQ处理完成,RabbitMQ便可以删除该消息
  • 自动应答:消息被接受后便认为传输成功,适用在消费者可以高效并处理这些消息时使用

3.应答方法

  • Channel.basicAck(肯定确认):RabbitMQ确定消息传送并处理成功,可以删除该消息
  • Channel.basicNack(否定确认)
  • Channel.basicReject(否定确认):比上面少了参数:是否批量应答

4.Multiplc解释

  • 是否开启批量应答
  • true:批量应答Channel上没应答的消息(例如:传送消息 5 6 7 8,若当前为8且应答,则5~8之间未应答的都会应答)
  • false:不批量(只应答8)

5.消息自动重新入队

  • 某个消费者故障导致消息未确认,此时若有其他消费者可处理,RabbitMQ则分发给其他消费者

6.消息手动应答

  • 消费者

    //采用手动应答
    channel.basicConsume(name,false,deliverCallback,cancelCallback);
    
    DeliverCallback deliverCallback = (consumerTag, message)->{
    /** 1.消息标记Tag  2.是否批量应答 */
    	channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
    };
    

7.RabbitMQ持久化

  • 队列持久化

    • 如果没有持久化,则在RabbitMQ重启或宕机会删除队列
    • 创建队列时,durable参数为true则启用持久化
    • 若队列本来不是持久化,则要删除后再重新创建,不然会报错
  • 消息持久化

    • 消息持久化不等于队列持久化,若是没持久化,则是保存在内存中
    • 在生产者调用basicPublish方法时的basicProperties参数设置持久化
    • basicProperties:MessageProperties.PERSISTENT_TEXT_PLAIN
    • ==注意:==不能完全保证不会丢失消息,在保存到磁盘时有个间隔点,此时还没保存到磁盘
  • 不公平分发

    • 避免某个消费者A处理速度大大高于消费者B,而导致A处理完便处于空闲状态
    • Channel.basicQos(1);//将变为不公平分发(能者多劳)
    • 在消费者接受消息前设置
  • 预取值

    • 指定消费者最大堆积多少消息
    • 在消费者接受消息前设置:Channel.basicQos(X) //x为最大数量,但不能为 0或1

8.发布确认

1.原理

  • 生产者将通道设置成confirm,则所有在该通道发布的消息都会被指派一个唯一ID(从1开始);当消息投递到队列后,broker发送一个确认给生产者(包含唯一ID);如果消息和队列持久化,则确认消息是在消息写入磁盘后发出的.
  • 好处:该模式是异步,生产者可以在等待通道返回确认时继续发送消息;消息确认后,生产者可通过回调方法处理消息;若因RabbitMQ自身原因导致消息丢失,则会发送nack消息,生产者同样可在回调方法中处理.

2.策略

  • 前提:队列和消息都要持久化
  • 开启发布确认方法
    • 默认:不开启
    • 在声明队列之前,创建完通道之后:Channel.confirmSelect()

3.单个确认发布

  • 是一种同步确认发布方式,只有一个消息被确认发布后,后续消息才能继续发布,如果在指定时间内返回消息,则会抛异常
  • 缺点:发布速度慢,若是没确认,则会阻塞后续消息的发布,最多提供每秒不超过数百条发布消息的吞吐量
  • 开启发布确认:channel.confirmSelect();
  • 获取返回值:boolean flag = channel.waitForConfirms();

4.批量确认发布

  • 先发布一批,再一起确认可以极大提高吞吐量
  • 缺点:当发生故障时,不知道哪个消息出问题
  • 等一批数量足够便:channel.waitForConfirms();

5.异步确认发布

  • 利用回调函数达到消息可靠性传递

  • 缺点:比较复杂 优点:可靠性和效率高

  • 在开启消息确认之后,确认消息之前添加监听器和回调方法

    //准备消息监听器,查看哪些失败哪些成功
    //确认成功
    ConfirmCallback ackCallBack = (deliveryTag,multiple)->{
        System.out.println("确认消息:"+deliveryTag);
    };
    //失败:1.消息的标记,2.是否为批量确认
    ConfirmCallback nackCallBack = (deliveryTag,multiple)->{
    	System.out.println("未确认消息:"+deliveryTag);
    };
    channel.addConfirmListener(ackCallBack,nackCallBack);
    
  • 如何处理未确认(并发链路队列)

    • 开启确认消息之后,创建一个线程安全有序的哈希表(可以轻松将序号与消息关联,根据序号批量删除条目)

      ConcurrentSkipListMap<Long,String> confirms = new ConcurrentSkipListMap<>(); 
      
    • 消息发送之后,记录所有要发送的消息

      confirms.put(channel.getNextPublishSeqNo(),message);
      
    • 删除掉已确认的消息(在消息确认方法中)

      //删除已确认
      ConcurrentSkipListMap<Long,String> confirmed = confirms.headMap(deliverTag);
      confirmed.clear();   //批量删除
      confirms.remove();	//单个删除
      

6.三种对比

单独发布同步等待确认,操作简单,吞吐量有限
批量发布批量同步等待确认,简单,合理吞吐量,但出现问题难推断
异步处理最佳性能和资源使用,实现复杂

9.交换机

1.介绍

  • RabbitMq核心思想:生产者发送消息不会直接到队列,而是发送到交换机,再由交换机推送到队列
  • Exchanges的类型
    • 直接:direct
    • 主题:topic
    • 标题:headers
    • 扇出:fanout
  • 临时队列:具有随机名称的非持久化队列,一旦断开消费者连接,队列会自动删除
    • 创建方法:String queueName = channel.queueDeclare().getQueue
  • 绑定:交换机与队列捆绑(可以设置RoutingKey)
    • 可通过RoutingKey指定发送给哪个队列

2.无名exchange

  • 默认的交换机类型,在发送消息时,用(“”)空字符串标识
  • channel.basicPublish(“”,“myqueue”,null,msg.getBytes()); 第一个参数

3.发布订阅模式(Fanout)

  • 交换机接收到消息后,广播到所有它绑定的队列(系统有默认的Fanout交换机)

  •  //创建fanout交换机
     channel.exchangeDeclare("logs","fanout");
     //生成临时队列,队列名称随机,消费者断开后删除
     String queue = channel.queueDeclare().getQueue();
     //绑定
     channel.queueBind(queue,"logs","");
     //回调方法
     DeliverCallback deliverCallback = (consumerTag,message)->{
     System.out.println("{SYS01}"+new String(message.getBody()));
     };
     channel.basicConsume(queue,true,deliverCallback,consumerTag->{});
    

4.路由模式(direct)

  • 队列与交换机的绑定要指定一个RoutingKey (路由 key).消息的发送方在向 Exchange 发送消息时,也必须指定消息的 RoutingKey.Exchange 根据消息的 Routing Key 进行判断,只有队列的 Routingkey 与消息的 Routing key 完全—致,才会接收到消息。
  • 创建direct交换机:channel.exchangeDeclare(交换机名, BuiltinExchangeType.DIRECT);
  • 绑定队列:channel.queueBind(队列名,交换机名,routingKey);

5.主体模式(topic)

  • 发送的RoutinKey是一个单词列表,以点号分隔开,最多不超过255个字节

  • 替换符

    • *(星号):代替一个单词

    • #:代替0个或多个单词

      *.rabbit.*      #.orange.*
      
  • 不匹配任何绑定:队列不接收,并且被丢弃

  • 同一队列匹配两个,只接受一次消息

10.死信队列

1.介绍

  • 死信:无法被消费的消息
  • 当消息出现异常时,便投入死信队列
  • 应用场景:为了保证订单业务数据不丢失,当用户下单成功却未在指定时间支付时.

2.死信来源

  • 消息TTL(存活时间)过期

  • 队列达到最大长度(队列满)

  • 消息被拒(消息应答时为reject或nack并且queue==false)

在这里插入图片描述

3.设置过期时间

1.消费者C1
  • 声明死信和普通交换机(都为direct)

  • 声明死信和普通队列

    • 普通队列

      //设置死信交换机
      map.put("x-dead-letter-exchange",deadExchange);
      //设置Routingkey
      map.put("x-dead-letter-routing-key","lisi");
      //创建队列
      channel.queueDeclare(normalQueue,false,false,false,map);
      
    • 死信队列

      channel.queueDeclare(deadQueue,false,false,false,null);
      
  • 绑定路由

    channel.queueBind(normalQueue,normalExchange,"zhangsan");
    channel.queueBind(deadQueue,deadExchange,"lisi");
    
  • 接受消息(普通队列)

    //消息确认回调方法
    DeliverCallback deliverCallback = (c, m)->{
    	System.out.println("{ConsumerA}接收的消息:"+new String(m.getBody(),"UTF-8"));
    };
    channel.basicConsume(normalQueue,true,deliverCallback,c->{});
    
2.消费者C2
  • 接受消息(死信队列)

     DeliverCallback deliverCallback = (c,m)->{
     	System.out.println("{消费者B}收到的消息:"+new String(m.getBody(),"UTF-8"));
     };
     channel.basicConsume(deadQueue,true,deliverCallback,c->{});
    
3.生产者
  • 设置消息存活时间(单位为毫秒)

    AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().expiration("10000").build();
    
  • 发送消息

    channel.basicPublish(normalExchange,"zhangsan",properties,msg.getBytes());
    

4.设置队列最大值

  • 去掉过期时间

  • 在普通队列的参数里添加

    map.put("x-max-length",6);
    
  • 删除掉已存在的普通队列

5.消息被拒

  • 去掉设置的队列最大值,删除掉已存在队列

  • 消息回调方法中

    DeliverCallback deliverCallback = (c, m)->{
    	String s =new String(m.getBody(),"UTF-8");
    	if(s.equals("msg5")){
    		System.out.println("{ConsumerA}被拒绝的消息:"+s);
    		//拒绝该消息,且不返回队列
    		channel.basicReject(m.getEnvelope().getDeliveryTag(),false);
    	}
    	System.out.println("{ConsumerA}接收的消息:"+s);
    	//同意消息
    	channel.basicAck(m.getEnvelope().getDeliveryTag(),false);
    };
    
  • 一定要关闭自动应答

    channel.basicConsume(normalQueue,false,deliverCallback,c->{});
    

11.延迟队列

1.介绍

  • 概念:队列内部有序,用来存放需要指定时间被处理的元素的队列
  • 应用场景:订单十分钟内未支付,30秒内输入验证码

2.SpringBoot整合RabbitMQ

  • 导入依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
    <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-swagger2</artifactId>
                <version>2.7.0</version>
            </dependency>
            <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-swagger-ui</artifactId>
                <version>2.7.0</version>
            </dependency>
    
  • 相关配置(application.properties)

    spring.rabbitmq.host=192.168.227.131
    spring.rabbitmq.port=5672
    spring.rabbitmq.username=andy
    spring.rabbitmq.password=123
    
  • 接口文档

    @Configuration
    @EnableSwagger2
    public class SwaggerConfig {
        @Bean
        public Docket webApiConfig(){
            return new Docket(DocumentationType.SWAGGER_2)
                    .groupName("webApi")
                    .apiInfo(webApiInfo())
                    .select()
                    .build();
        }
        private ApiInfo webApiInfo(){
            return new ApiInfoBuilder()
                    .title("rabbitMq接口文档")
                    .description("描述rabbitmq微服务接口定义")
                    .version("1.0")
                    .contact(new Contact("enjoy6288","http://www.baixxx.com","23xxxxx@qqcom"))
                    .build();
        }
    }
    

3.队列TTL

在这里插入图片描述

4.配置类代码

  • 声明交换机

    //声明普通交换机
    @Bean("XExchange")
    public DirectExchange xExchange(){
    	return new DirectExchange(XExchange);
    }
    //声明死信交换机
    @Bean("YExchange")
    public DirectExchange yExchange(){
    	return new DirectExchange(YExchange);
    }
    
  • 声明队列

    @Bean("normalQA") //队列A
    public Queue queueA(){
    	Map<String,Object> map = new HashMap<>(3);
        map.put("x-dead-letter-exchange",YExchange);
        map.put("x-dead-letter-routing-key","YY");
        map.put("x-message-ttl",10000);
        return QueueBuilder.durable(normalQA).withArguments(map).build();
    }
    @Bean("normalQB")	//队列B
    public Queue queueB(){
        Map<String,Object> map = new HashMap<>(3);
        map.put("x-dead-letter-exchange",YExchange);
        map.put("x-dead-letter-routing-key","YY");
        map.put("x-message-ttl",30000);
        return QueueBuilder.durable(normalQB).withArguments(map).build();
    }
    @Bean("deadQC")	//死信队列
    public Queue deadQC(){
    	return QueueBuilder.durable(deadQC).build();
    }
    

    设置过期时间的参数是long类型,不能用字符串

  • 交换机与队列绑定

    @Bean
    public Binding queueABindX(@Qualifier("normalQA") Queue QA, @Qualifier("XExchange")DirectExchange XE){
    	return BindingBuilder.bind(QA).to(XE).with("XA");
    }
    @Bean
    public Binding queueBBindX(@Qualifier("normalQB") Queue QA,@Qualifier("XExchange")DirectExchange XE){
    	return BindingBuilder.bind(QA).to(XE).with("XB");
    }
    @Bean
    public Binding queueCBindY(@Qualifier("deadQC") Queue QA,@Qualifier("YExchange")DirectExchange XE){
    	return BindingBuilder.bind(QA).to(XE).with("YY");
    }
    

    使用@Qualifier注解时注意别导错包,不然会报错

5.生产者

  • http://localhost:8080/ttl/sendMsg/这是消息 (输入该网址)
@Slf4j
@RestController
@RequestMapping("/ttl")
public class TTLController {
    @Autowired
    private RabbitTemplate template;
    @RequestMapping("/sendMsg/{message}")
    public void sendMsg(@PathVariable String message){
        log.info("当前时间:{},发送一条消息给队列{}",new Date().toString(),message);
        template.convertAndSend("X","XA","来自ttl为10s的队列:"+message);
        template.convertAndSend("X","XB","来自ttl为30s的队列:"+message);
    }
}

6.消费者

@Slf4j
@Component
public class QueueConsumer {
    @RabbitListener(queues = "QC")
    public void getMessage(Message message, Channel channel){
        String msg = new String(message.getBody());
        log.info("当前时间:{},收到死信队列消息:{}",new Date().toString(),msg);
    }
}

@RabbitListener(queues=“死信队列名”)

7.优化延迟队列

  • 以上存在的问题:每新增一个时间需求,就要新增一个队列

  • 解决:新增一个没有设置过期时间的通用队列,在生产者发消息时才设置过期时间

  • 配置类

    @Bean("normalQD")
    public Queue normalQD(){
    	Map<String,Object> map = new HashMap<>(3);
    	map.put("x-dead-letter-exchange",YExchange);
    	map.put("x-dead-letter-routing-key","YY");
    	return QueueBuilder.durable(normalQD).withArguments(map).build();
    }
    @Bean
    public Binding queueCBindY(@Qualifier("deadQC") Queue QA,@Qualifier("YExchange")DirectExchange XE){
            return BindingBuilder.bind(QA).to(XE).with("YY");
    }
    
  • 生产者

    @RequestMapping("/sendTTL/{msg}/{ttl}")
    public void sendTTL(@PathVariable String msg,@PathVariable String ttl){
    	log.info("当前时间:{},发送一条设置了过期时间:{}(毫秒)的消息给队列:{}",new Date().toString(),ttl,msg);
    	template.convertAndSend("X","XC","来自无ttl的队列:"+msg,m->{
    	m.getMessageProperties().setExpiration(ttl);
    	return m;
    	});
    }
    

    ==m.getMessageProperties().setExpiration(ttl);==设置过期时间

  • 存在的问题:由于消息在队列是按顺序的,==RabbitMQ只会检测第一个是否过期.==若第一个过期时间长与第二个,则会造成第二个消息在第一个结束后才执行(队列A:20秒.队列B:2秒,先发A的话B在20秒后才被接收到,先发B则不会)

8.使用插件实现延迟队列

  • 代码架构图

在这里插入图片描述

1.添加插件
  • 官网下载:https://www.rabbitmq.com/community-plugins.html

    rabbitmq_delayed_message_exchange-3.8.0.ez文件

  • 放置文件:/usr/lib/rabbitmq/lib/rabbitmq_server-3.8.8/plugins

  • 安装插件:rabbitmq-plugins enable rabbitmq_delayed_message_exchange

2.配置类
  • @Bean
    public CustomExchange delayedExchange(){
    	Map<String,Object> map = new HashMap();
    	//设置交换机类型
    	map.put("x-delayed-type","direct");
    	//参数列表:交换机名,交换机类型,是否持久化,是否自动删除,参数
    	return new CustomExchange(EXCHANGE,"x-delayed-message",true,false,map);
    }
    @Bean
    public Queue delayedqueue(){
    	return new Queue(QUEUE);
    }
    @Bean
    public Binding queueBindingExchange(@Qualifier("delayedqueue")Queue queue,
    	@Qualifier("delayedExchange")CustomExchange exchange){
    	return BindingBuilder.bind(queue).to(exchange).with(ROUTING_KEY).noargs();
    }
    
  • 不同点:(1)交换机参数只设置了交换机类型;(2)新建队列: new Queue(QUEUE);

3.生产者
  • @RequestMapping("/sendDelayed/{msg}/{ttl}")
    public void sendTTL(@PathVariable String msg,@PathVariable Integer ttl){
    	log.info("当前时间:{},发送一条设置了过期时间:{}(毫秒)的消息给队列:{}",new Date().toString(),ttl,msg);
    	template.convertAndSend("delayed.exchange","delayed.routingKey","来自无ttl的队列:"+msg,m->{
    	m.getMessageProperties().setDelay(ttl);
    	return m;
    	});
    }
    
  • 不同点:m.getMessageProperties().setDelay(ttl)后面是setDelay()方法,参数要Integer类型

4.消费者
  •  @RabbitListener(queues = "delayed.queue")
        public void getDelayedMsg(Message msg){
            String message = new String(msg.getBody());
            log.info("当前时间:{},收到的消息:{}",new Date().toString(),message);
        }
    
  • 没啥区别,但记得添加@Slf4j和@Component注解!!!

9.总结

  • 基于死信的情况下:在队列设置ttl;基于延迟插件:在交换机中设置ttl
  • 延迟队列可实现:消息可靠发送,死信队列保证消息至少消费一次以及未被处理的不会被丢弃
  • 可以解决单点故障问题,不会因单个节点挂掉导致队列不可用或消息丢失

12.发布确认Plus版

1发布确认springBoot版本

  • 配置类

        @Bean
        public DirectExchange confirmExchange(){
            return new DirectExchange(exchange);
        }
        @Bean
        public Queue confirmQueue(){
            return QueueBuilder.durable(queue).build();
        }
        @Bean
        public Binding confirmBinding(@Qualifier("confirmExchange")DirectExchange exchange
        ,@Qualifier("confirmQueue")Queue q){
            return BindingBuilder.bind(q).to(exchange).with(routingKey);
        }
    
  • 生产者

    @RequestMapping("/sendMessage/{msg}")
        public void sendMessage(@PathVariable("msg")String msg){
            template.convertAndSend("confirm.exchange","confirm.key",msg);
            log.info("发送消息内容为:{}",msg);
        }
    
  • 消费者

    @RabbitListener(queues="confirm.queue")
        public void getMsg(Message msg){
            String m = new String(msg.getBody());
            log.info("接收到的消息为:{}",m);
        }
    
  • 测试地址:http://localhost:8080/confirm/sendMessage/你好!

  • 回调接口

    • 在配置文件中添加:spring.rabbitmq.publisher-confirm-type=correlated

      none:禁用发布确认模式(默认值)

      correlated:发布消息成功后触发回调方法

      simple:第一个效果:和correlated类似

      第二个效果:发布消息成功后使用rabbitTemplate调用waitForConfirms或waitConfirmsOrDie方法,等待broker返回发送结果,根据结果判断下一步.waitConfirmsOrDie返回false会关闭channel

    • 创建回调方法类实现RabbitTemplate.ConfirmCallback

    @Autowired
        private RabbitTemplate template;
        //注入该
         @PostConstruct
        public void init(){
            template.setConfirmCallback(this);
        }
        /**
         * 交换机确认回调方法
         * @  correlationData:保存回调消息的ID和相关信息
         * @  ack:交换价收到消息(接收到为:true;失败为:false)
         * @  cause:原因(成功为null,失败则是失败原因)
         */
        @Override
        public void confirm(CorrelationData correlationData, boolean ack, String cause) {
            String id = correlationData!=null?correlationData.getId():"";
    
            if(ack){
                log.info("交换机收到id为:{}的消息",id);
            }
            else{
                log.info("交换机没收到id为:{}的消息,原因:{}",id,cause);
            }
        }
     
    

    注意:要记得注入该回调方法,并且是在实例化回调方法且注入RabbitTemplate之后,否则报空指针异常;并且要加==@Component注解==

    • 生产者

      CorrelationData correlationData = new CorrelationData("1"); //创建个对象
      template.convertAndSend("confirm.exchange","confirm.key",msg,correlationData);
      
    • 测试结果

      交换机出错:会报错,找不到对应交换机

      routingKey出错:不会报错,回调方法收到信息,但是消费者接收不到消息!!!

2.回退消息

  • Mandatory参数:==当消息传递过程中不可到达目的地时返回消息给生产者

    在配置文件中开启回退消息:spring.rabbitmq.publisher-returns=true

  • 回调接口:上面的回调方法类再实现对应方法

    @Override
    public void returnedMessage(ReturnedMessage returnedMessage) {
    	String msg = new String( returnedMessage.getMessage().getBody());
    	log.info("消息:{},被交换机:{}退回,退回原因:{},路由Key:{}",
    	msg,returnedMessage.getExchange(),returnedMessage.getReplyText(),returnedMessage.getRoutingKey()
    	);
    }
    

    在消息不可达时返回给生产者,只有不可达目的地才回退

    记得注入到template中:template.setReturnsCallback(this);

  • 结果

3.备份交换机

  • 代码架构图
    在这里插入图片描述

    当交换机无法发送消息给消费者时,便会把无法发送的消息发给备份交换机,备份交换机再发给备份队列和报警队列

  • 配置类修改

    • 创建一个Fanout类型交换机和两个队列

    • 修改已创建的交换机的代码

      @Bean("confirmExchange")
      public DirectExchange confirmExchange(){
      	return ExchangeBuilder.directExchange(exchange).durable(true)
      	.withArgument("alternate-exchange",backupExchange).build(); 
      }
      

      参数:1.创建的交换机名;2.是否持久化;3.备份的交换机

  • 创建报警消费者

  • 删除掉原来创建的交换机

  • 测试结果

    1.当消息不可达时,会从报警消费者出输出

    2.若是mandatory参数与备份交换机同时开启,消息会从备份交换机处输出,备份交换机优先级更高

13.RabbitMQ其他知识点

1.幂等性

1.概念:

用户对同一操作发起一次或多次请求的结果一致,不会产生副作用.(例如:用户因为网络原因造成付款成功后返回结果失败,第二次点击付款成功后,却扣两次钱)

2.消息重复消费

若消费者返回ack时网络中断,则MQ可能会再将该消息发给其他消费者或者重连后再一次消费该消息.

3.解决思路

一般使用全局ID或写个唯一标识(比如时间戳/UUID),每次消费时用该id判断是否已消费过

4.主流幂等性操作
  • 唯一Id+指纹码机制,利用数据库主键去重

    指纹码:规则或时间戳加别的服务给到的唯一信息码(优点:实现简单;缺点:在高并发时,单数据库有写入性能瓶颈)

  • 利用redis原子性实现(优先这个)

    利用redis执行setnx命令,具有天然幂等性

2.优先级队列

1.应用场景
  • 在订单催收时,可以设置哪个订单优先接受
2.优先级队列长度
  • 0~255,越大越先执行
3.设置优先级
  • 在创建队列之前,设置长度

    map.put("x-max-priority",10);
    channel.queueDeclare("hello",true,false,false,map);	填入参数
    
  • 在发送消息之前设置消息优先级

    AMQP.BasicProperties properties = new AMQP.BasicProperties().builder()
    .priority(5).build();
    channel.basicPublish("","hello",properties,msg.getBytes()); //发送消息
    

    注意:队列和消息都要设置优先级,消费者要等消息已发送到队列才能消费,这样才有机会对消息进行排序

3.惰性队列

  • 消息保存在内存中还是磁盘上

    正常情况:消息保存在内存;惰性队列:保存在磁盘

  • 应用场景:消费者下线,宕机或因维护而关闭

  • 设置方法(声明队列时)

    map.put("x-queue-mode","lazy") //设置为惰性模式
    channel.queueDeclare("hello",true,false,false,map);	填入参数
    
  • 优点:当有大量消息存储时,占用的内存远小于普通队列

    缺点:读取速度慢,要先从磁盘读取消息到内存(根据索引)

4.RabbitMQ集群

  • 将单个RabbitMQ集群在一起
1.搭建步骤(3台主机)
  • 修改3台主机名称: vim /etc/hostname

  • 配置各个节点的host文件:vim /etc/hosts(ip地址 主机名)

    192.168.172.1 node1

    192.168.172.2 node2

    192.168.172.3 node3

  • 使各个节点cookie文件使用同一值(在第一台机器)

    scp/var/lib/rabbitmq/.erlang.cookie root@node2:/var/lib/rabbitmq/.erlang.cookie

    scp/var/lib/rabbitmq/.erlang.cookie root@node3:/var/lib/rabbitmq/.erlang.cookie

  • 重启服务:rabbitmq-server -detached

  • 在节点2和3各执行

    rabbitmqctl stop_app (只关闭RabbitMQ服务)

    rabbitmqctl reset

    rabbitmqctl join_cluster rabbit@node1(节点3则为:node2)

    rabbitmqctl start_app

  • 集群状态:rabbitmqctl cluster_status

  • 重新创建用户并设置角色与权限

  • 解除节点(在节点1执行)

    rabbitmqctl stop_app 
    rabbitmqctl reset
    rabbitmqctl start_app
    rabbitmqctl cluster_status
    rabbitmqctl forget_cluster_node rabbit@node2(要删除的节点)
    

5.镜像队列

  • 作用:避免集群中一个节点宕机造成消息丢失
  • 在RabbitMQ管理界面的Admin->Policies设置

6.Haproxy实现负载均衡

  • 通过Haproxy和keepalive解决生产者只发消息给一个RabbitMQ节点的问题
  • 步骤
    • 普通情况下,生产者走主机,由主机转发到RabbitMQ集群
    • 若主机宕机了,keepalive将ip漂移到备机,再由备机转发
    • 并且每隔一段时间会查询主机状态,看是否漂移回去

7.FederationExchange

  • 作用:解决两个broker相距太远而导致消息延迟

  • 实现:安装对应插件

    rabbitmq-plugins enable rabbitmq_federation

    rabbitmq-plugins enable rabbitmq_federation_management

  • 在下游节点要先准备交换机

  • 在下游节点配置上游节点

  • 配置策略

8.FederationQueue

  • 和Exchange配置类似

9.shovel

  • Shovel可靠,持续地从一个broker中队列(源端)拉取数据并转发到另一个Broker

  • 开启插件:rabbitmq-plugins enable rabbitmq_shovel

    rabbitmq-plugins enable rabbitmq_shovel_management
    root@node3:/var/lib/rabbitmq/.erlang.cookie

  • 重启服务:rabbitmq-server -detached

  • 在节点2和3各执行

    rabbitmqctl stop_app (只关闭RabbitMQ服务)

    rabbitmqctl reset

    rabbitmqctl join_cluster rabbit@node1(节点3则为:node2)

    rabbitmqctl start_app

  • 集群状态:rabbitmqctl cluster_status

  • 重新创建用户并设置角色与权限

  • 解除节点(在节点1执行)

    rabbitmqctl stop_app 
    rabbitmqctl reset
    rabbitmqctl start_app
    rabbitmqctl cluster_status
    rabbitmqctl forget_cluster_node rabbit@node2(要删除的节点)
    

5.镜像队列

  • 作用:避免集群中一个节点宕机造成消息丢失
  • 在RabbitMQ管理界面的Admin->Policies设置

6.Haproxy实现负载均衡

  • 通过Haproxy和keepalive解决生产者只发消息给一个RabbitMQ节点的问题
  • 步骤
    • 普通情况下,生产者走主机,由主机转发到RabbitMQ集群
    • 若主机宕机了,keepalive将ip漂移到备机,再由备机转发
    • 并且每隔一段时间会查询主机状态,看是否漂移回去

7.FederationExchange

  • 作用:解决两个broker相距太远而导致消息延迟

  • 实现:安装对应插件

    rabbitmq-plugins enable rabbitmq_federation

    rabbitmq-plugins enable rabbitmq_federation_management

  • 在下游节点要先准备交换机

  • 在下游节点配置上游节点

  • 配置策略

8.FederationQueue

  • 和Exchange配置类似

9.shovel

  • Shovel可靠,持续地从一个broker中队列(源端)拉取数据并转发到另一个Broker

  • 开启插件:rabbitmq-plugins enable rabbitmq_shovel

    rabbitmq-plugins enable rabbitmq_shovel_management

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值