RabbitMQ笔记

视频原地址:
最适合小白入门的RabbitMQ教程
笔记原地址

一、消息队列介绍

消息队列需求场景

在基于微服务开发的电商项目中,商品的查询和商品的修改是通过两个服务实现的,如果修改了商品的价格,如何保证商品查询服务查询出来的商品价格同步更新呢?

服务与服务之间的通信方式有两种:同步调用 和 异步消息调用

  • 同步调用:远程过程调用
    • REST:ribbon、fefign
    • RPC:Dubbo
  • 异步消息调用:消息队列
    在这里插入图片描述

1、为了保证数据的一致性,当“商户商品修改服务”在完成对A库中商品信息的修改之后,需要调用“商户商品查询服务”及“自媒体商品查询服务”同步完成B库及C库中商品信息的修改;

2、如果“商户商品修改服务”使用Ribbon或者Feign同步调用“商户商品查询服务”及“自媒体商品查询服务”虽然能够实现数据的同步修改,但是大大增加了“商户商品修改服务”对用户的响应时间

3、为了缩短“商户商品修改服务”对用户的响应时间,我们可以在“商户商品修改服务”完成对A库修改之后,通过异步消息队列通知“商户商品查询服务”及“自媒体商品查询服务”。
在这里插入图片描述
在这里插入图片描述

消息队列概念

  • MQ全称为Message Queue,消息队列(MQ)是一种应用程序对应用程序的通信方法。应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们。
  • 消息传递指的是程序之间通过在消息中发送数据进行通信,而不是通过直接调用彼此来通信,直接调用通常是用于诸如远程过程调用的技术。排队指的是应用程序通过队列来通信。队列的使用除去了接收和发送应用程序同时执行的要求。

常见消息队列中间件

RabbitMQ、ActiveMQ、RocketMQ、Kafka
在这里插入图片描述

  • RabbitMQ 稳定可靠,数据一致,支持多协议,有消息确认,基于erlang语言;

  • Kafka 高吞吐,高性能,快速持久化,无消息确认,无消息遗漏,可能会有有重复消息,依赖于zookeeper,成本高;

  • ActiveMQ 不够灵活轻巧,对队列较多情况支持不好;

  • RocketMQ 性能好,高吞吐,高可用性,支持大规模分布式,协议支持单一。

RabbitMQ介绍
  • RabbitMQ是一个在AMQP基础上完成的,可复用的企业消息系统。他遵循Mozilla Public License开源协议。
  • AMQP,即Advanced Message Queuing Protocol, 一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。Erlang中的实现有 RabbitMQ等。
  • 主要特性:
    • 保证可靠性:使用一些机制来保证可靠性,如持久化、传输确认、发布确认
    • 灵活的路由功能
    • 可伸缩性:支持消息集群,多台RabbitMQ服务器可以组成一个集群
    • 高可用性:RabbitMQ集群中的某个节点出现问题时队列任然可用
    • 支持多种协议
    • 支持多语言客户端
    • 提供良好的管理界面
    • 提供跟踪机制:如果消息出现异常,可以通过跟踪机制分析异常原因
    • 提供插件机制:可通过插件进行多方面扩展

RabbitMQ安装及配置

https://blog.csdn.net/weixin_43758633/article/details/112690146

RabbitMQ逻辑结构

在这里插入图片描述

用户级别

  1. 用户级别
  • 超级管理员administrator,可以登录控制台,查看所有信息,可以对用户和策略进行操作
  • 监控者monitoring,可以登录控制台,可以查看节点的相关信息,比如进程数,内存磁盘使用情况
  • 策略制定者policymaker ,可以登录控制台,制定策略,但是无法查看节点信息
  • 普通管理员 management 仅能登录控制台
  • 其他, 无法登录控制台,一般指的是提供者和消费者
  1. 添加用户(命令模式)
  • 添加/配置用户
# 插件目录
./rabbitmqctl add_user ytao admin123
  • 设置用户权限
#设置admin为administrator级别
./rabbitmqctl set_user_tags ytao administrator

3.添加用户(web方式)

消息队列的模式

参考文档:http://www.rabbitmq.com/getstarted.html

  1. 简单模式
    简单模式就是我们的生产者将消息发到队列,消费者从队列中取消息,一条消息对应一个消费者
    在这里插入图片描述
  2. 工作模式
    Work模式就是一条消息可以被多个消费者尝试接收,但是最终只能有一个消费者能获取

在这里插入图片描述
在这里插入图片描述

  1. 订阅模式
    一条消息可以被多个消费者同时获取,生产者将消息发送到交换机,消费者将自己对应的队列注册到交换机,当发送消息后所有注册的队列的消费者都可以收到消息
    在这里插入图片描述
    在这里插入图片描述

4.路由模式
生产者将消息发送到了type为direct模式的交换机,消费者的队列在将自己绑定到路由的时候会给自己绑定一个key,只有消费者发送对应key格式的消息时候队列才会收到消息
在这里插入图片描述
在这里插入图片描述

5.Topic模式
在这里插入图片描述
6.RPC模式
在这里插入图片描述
自定义示意图
在这里插入图片描述

SpringBoot整合

简单使用

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

module1 producer

在这里插入图片描述
application.yml

server:
  port: 9001
spring:
  application:
    name: producer
  rabbitmq:
    host: 47.115.2.92
    port: 5672
    virtual-host: host1
    username: ems
    password: 123

controller

package com.zzy.producer.controller;

import com.zzy.producer.service.TestService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * @author ZhaoZhiyue
 * @DATE 2021/1/25 - 20:50
 */
@RestController
public class TestController {

    @Resource
    private TestService service;

    @RequestMapping("test")
    public String test(String msg) {
        service.sendMsg(msg);
        return "success";
    }
}

service

package com.zzy.producer.service;

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @author ZhaoZhiyue
 * @DATE 2021/1/25 - 20:51
 */
@Service
public class TestService {

    @Resource
    private AmqpTemplate amqpTemplate;

    public void sendMsg(String msg) {

        if (msg.startsWith("q_")) {
            //1.发送消息到队列
            amqpTemplate.convertAndSend("q1", msg);
//            System.out.println(msg+"发送成功!");
        } else if (msg.startsWith("f_")) {
            //2.发送消息到交换机(订阅交换机 fanout) 第一个参数为交换机名字,第二个为空
            amqpTemplate.convertAndSend("ex1", "", msg);
        } else if (msg.startsWith("r")) {
            if (msg.startsWith("r_a")) {
                //3.发送消息到交换机(路由交换机) 第一个参数为交换机名字,第二个为key
                amqpTemplate.convertAndSend("ex2", "a", msg);
            } else if (msg.startsWith("r_b")) {

                amqpTemplate.convertAndSend("ex2", "b", msg);
            }
        }
    }
}

module2 consumer

application.yml

server:
  port: 9002
spring:
  application:
    name: producer
  rabbitmq:
    host: 47.115.2.92
    port: 5672
    virtual-host: host1
    username: ems
    password: 123

service

package com.zzy.producer.service;

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @author ZhaoZhiyue
 * @DATE 2021/1/25 - 20:51
 */
@Service
package com.zzy.consumer.servcier;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

/**
 * @author ZhaoZhiyue
 * @DATE 2021/1/25 - 21:30
 */
@Service
//@RabbitListener(queues = {"q1","q2"})
@RabbitListener(queues = {"q5"})
public class ReceiveMsgServcie {

    @RabbitHandler
    public void receiveMsg(String msg){
        System.out.println("接受:"+msg);
        System.out.println(msg+"接受成功!");
    }

    @RabbitHandler
    public void receiveMsg(byte[] bs){
    }

}

二、使用RabbitMQ传输对象

Rabbitmq是消息队列,发送和接受的都是字符串/字节数组类型的消息。

新建项目

在这里插入图片描述
Template下新建

<!DOCTYPE html>
<html lang="en">
<head>
<!--    <base href="/">-->
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h3>商品添加</h3>
<form action="/goods/add" method="post">
    <p>商品ID: <input type="text" name="goodsID"></p>
    <p>商品名称: <input type="text" name="goodsName"></p>
    <p>商品价格: <input type="text" name="goodsPrice"></p>
    <p>商品描述: <input type="text" name="goodsDesc"></p>
    <p><input type="submit" value="提交"></p>
</form>

</body>
</html>

producer

server:
  port: 9100
spring:
  application:
    name: producer
  rabbitmq:
    host: 47.115.2.92
    port: 5672
    virtual-host: host1
    username: ems
    password: 123

Goods

package com.zzy.mq.beans;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;

/**
 * @author ZhaoZhiyue
 * @DATE 2021/1/25 - 23:12
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Goods implements Serializable {
    private String goodsID;
    private String goodsName;
    private double goodsPrice;
    private String goodsDesc;
}

PageController

package com.zzy.mq.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @author ZhaoZhiyue
 * @DATE 2021/1/25 - 23:06
 */
@Controller
public class PageController {

    @RequestMapping("/goods-add.html")
    public String goodsAdd() {
        return "goods-add";
    }
}

GoodController

package com.zzy.mq.controller;

import com.zzy.mq.beans.Goods;
import com.zzy.mq.servcie.MQService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.annotation.Resource;

/**
 * @author ZhaoZhiyue
 * @DATE 2021/1/25 - 23:10
 */
@Controller
@RequestMapping("/goods")
public class GoodController {

    @Resource
    private MQService mqService;

    @RequestMapping("/add")
    public String add(Goods goods) {
        //完成了商品的添加操作 ---servcie

        //将goods对象通过消息队列传递给consumer
        mqService.sendGoodsToMq(goods);

        return "goods-add";

    }
}

MQService

package com.zzy.mq.servcie;

import com.zzy.mq.beans.Goods;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;

/**
 * @author ZhaoZhiyue
 * @DATE 2021/1/25 - 23:16
 */
@Service
public class MQService {
    @Resource
    private AmqpTemplate amqpTemplate;

    public void sendGoodsToMq(Goods goods) {
        //消息队列可以发送 字符串 字节数组 序列化对象
        amqpTemplate.convertAndSend("","q1",goods);
    }
}

接受者

package com.zzy.mq.service;

import com.zzy.mq.beans.Goods;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

/**
 * @author ZhaoZhiyue
 * @DATE 2021/1/25 - 23:23
 */
@Component
@RabbitListener(queues = "q1")
public class ReceiveService {

    @RabbitHandler
    public void receiveMsg(String msg){
        System.out.println("接受消息:+"+msg);
    }

    @RabbitHandler
    public void receiveMsg(Goods goods){
        System.out.println("接受消息:+"+goods);
    }

    @RabbitHandler
    public void receiveMsg(byte[] bs){
        System.out.println("接受消息,byte[]:+"+new String(bs));
    }


}

第一种方法:使用序列化对象(使用较多,比较方便)

要求:

  • 对象实现Serializable 接口
  • 传递的对象包名、类名和属性名必须一致
package com.zzy.mq.beans;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * @author ZhaoZhiyue
 * @DATE 2021/1/25 - 23:12
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Goods implements Serializable {
    private String goodsID;
    private String goodsName;
    private double goodsPrice;
    private String goodsDesc;
}

发送

package com.zzy.mq.servcie;

import com.zzy.mq.beans.Goods;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @author ZhaoZhiyue
 * @DATE 2021/1/25 - 23:16
 */
@Service
public class MQService {

    @Resource
    private AmqpTemplate amqpTemplate;

    public void sendGoodsToMq(Goods goods) {
        //消息队列可以发送 字符串 字节数组 序列化对象
        amqpTemplate.convertAndSend("","q1",goods);
    }
}

接受

    @RabbitHandler
    public void receiveMsg(Goods goods){
        System.out.println("接受消:+"+goods);
    }
第二种方法:使用序列化字节数组

对象也要实现Serializable 接口

要求:

  • 对象实现Serializable 接口
  • 传递的对象包名、类名和属性名必须一致
  1. 发送
package com.zzy.mq.servcie;
import com.zzy.mq.beans.Goods;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.utils.SerializationUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @author ZhaoZhiyue
 * @DATE 2021/1/25 - 23:16
 */
@Service
public class MQService {

    @Resource
    private AmqpTemplate amqpTemplate;

    public void sendGoodsToMq(Goods goods) {
        //消息队列可以发送 字符串 字节数组 序列化对象
//        amqpTemplate.convertAndSend("","q1",goods);

        byte[] bytes = SerializationUtils.serialize(goods);
        amqpTemplate.convertAndSend("","q1",bytes);
    }
}

  1. 接受:
	@RabbitHandler
    public void receiveMsg(byte[] bs){
        Goods goods = (Goods) SerializationUtils.deserialize(bs);
        System.out.println("接受消息,byte[]:+"goods);
    }
第三种方法:使用JSON字符串

要求:

  • 传递的对象属性名必须一致
    发送
 public void sendGoodsToMq2(Goods goods) throws JsonProcessingException {
        //消息队列可以发送 字符串 字节数组 序列化对象
        ObjectMapper objectMapper = new ObjectMapper();
        String msg = objectMapper.writeValueAsString(goods);
        amqpTemplate.convertAndSend("","q1",msg);
    }

接受

    @RabbitHandler
    public void receiveMsg2(String msg) throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();
        Goods goods = objectMapper.readValue(msg, Goods.class);
        System.out.println("接受消息:+"+msg);
    }

三、基于Java的交换机与队列创建

使用消息队列,消息队列和交换机可以通过管理系统完成创建,也可以在应用程序中通过java代码来完成创建。

java普通项目

1、使用java新建队列

channel.queueDeclare("q7",false,false,false,null);
channel.basicPublish("","q7",null,msg.getBytes());

2、使用java新建交换机

//定义一个订阅交换机
channel.exchangeDeclare("ex3",BuiltinExchangetype.FANOUT);
//定义一个路由交换机
channel.exchangeDeclare("ex4",BuiltinExchangetype.DIRECT);

//绑定队列
//参数1:队列名称
//参数2:目标交换机
//参数3:如果绑定交换机则参数为"",如果绑定路由交换机则表示队列的key
channel.queueBind("q7","ex4","k1");
channel.queueBind("q7","ex4","k2");
SpringBoot

新建配置类RabbitMQConfiguration

package com.zzy.mq.config;

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author ZhaoZhiyue
 * @DATE 2021/1/26 - 23:08
 */
@Configuration
public class RabbitMQConfiguration {
	//声明队列
    @Bean("q9")
    public Queue newQueue9() {
        return new Queue("q9");
    }

    @Bean
    public Queue newQueue10() {
        Queue q10 = new Queue("q10");
        //设置队列属性
        q10.isDurable();
        return q10;
    }

    //声明订阅交换机
    @Bean
    public FanoutExchange newFanoutExchange() {
        return new FanoutExchange("ex5");
    }

    //声明订阅交换机
    @Bean
    public DirectExchange newDirectExchange() {
        return new DirectExchange("6");
    }

    //绑定队列,参数可以为方法名,自动注入
    @Bean
    public Binding bindingq9(Queue q9,Queue newQueue10,DirectExchange newDirectExchange){
        return BindingBuilder.bind(q9).to(newDirectExchange).with("k1");
    }

    @Bean
    public Binding bindingq10(Queue newQueue10,DirectExchange newDirectExchange){
        return BindingBuilder.bind(newQueue10).to(newDirectExchange).with("k2");
    }

}

四、消息的可靠性

4.1RabbitMQ事务

当消息发送过程中添加了事务,处理效率将降低几十倍甚至上百倍!所以基本不用

在这里插入图片描述

4.2 普通maven项目中消息确认机制和return机制

在这里插入图片描述

  • 消息确认机制:确认消息提供者是否成功发送消息到交换机
  • return机制:确认消息是否成功地从交换机分发到队列

4.2.1 消息确认机制
1、普通confirm方式

//1.发送消息之前开启消息确认
channel.ConfirmSelect();

channel.basicPublish("ex4","k1",null,msg.getBytes());

//2.接收消息确认
boolean b = channel.waitForConfirms();

2、批量confirm

//1.发送消息之前开启消息确认
channel.ConfirmSelect();

//批量发送消息
for(int i=0;i<10;i++){
	channel.basicPublish("ex4","k1",null,msg.getBytes());
}
//2.接收批量消息确认:发送的所有消息中,如果有一条是失败的,则所有的消息发送直接失败,抛出IO异常
//加入发送消息需要10s,waitForConfirms会进入阻塞状态
boolean b = channel.waitForConfirms();

3、异步confirm

//1.发送消息之前开启消息确认
channel.ConfirmSelect();

//批量发送消息
for(int i=0;i<10;i++){
	channel.basicPublish("ex4","k1",null,msg.getBytes());
}
//2.使用监听器异步confirm
//使用监听器异步confirm:监听器相当于启动了另一个线程专门用于等待消息确认(ack)
channel.addConfirmListener(new ConfirmLisetener(){
	//参数1:long l 返回消息的标识
	//参数2:是否为批量 true:多条消息
	public void handleAck(long l ,boolean b) throws IOExcetion{
		System.out.println("消息成功发送到交换机!");
	}
	
	public void handleNack(long l ,boolean b) throws IOExcetion{
		System.out.println("消息发送到交换机失败!");
	}
});
//不要关闭channel,否则可能还没确认就关闭了

4.2.2 return机制

  • 添加return监听器
  • 发送消息是指定第三个参数为true
  • 由于监听器是异步操作,所以在消息发送之后不能关闭channel

return机制:监控交换机是否将消息分发到队列

在这里插入图片描述

4.3 SpringBoot项目中消息确认机制和return机制

4.3.1 配置applicaton.yml开启消息确认和return监听

server:
  port: 9100
spring:
  application:
    name: producer
  rabbitmq:
    host: 47.115.2.92
    port: 5672
    virtual-host: host2
    username: ems
    password: 123
   
    publisher-confirm-type: simple#  开启消息确认模式
    publisher-returns: true #使用return监听机制

创建confirm和return监听

package com.zzy.mq.servcie;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.ReturnedMessage;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

/**
 * @author ZhaoZhiyue
 * @DATE 2021/1/27 - 20:55
 */
@Component
public class MsgConfigrmAndReturn implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnsCallback {

    Logger logger = (Logger) LoggerFactory.getLogger(MsgConfigrmAndReturn.class);


    @Resource
    private RabbitTemplate rabbitTemplate;

    @PostConstruct
    public void init() {
        //设置回调
        rabbitTemplate.setConfirmCallback(this);
        rabbitTemplate.setReturnsCallback(this);

    }


    @Override
    public void confirm(CorrelationData correlationData, boolean b, String s) {
        //此方法用于监听消息确认结果(消息是否发送到交换机)
        if(b){
            logger.info("----消息成功发送到交换机");
        }else {
        	//当路由模式,发送不存在的key时会失败
            logger.warn("----消息发送到交换机失败");
        }

    }

    @Override
    public void returnedMessage(ReturnedMessage returnedMessage) {
        //此方法用于return监听(当消息分发到队列失败时执行)
        logger.warn("消息分发到队列失败...");


    }
}

五、延迟机制

死信队列:有存活时间,且在存活时间内未被消费!

5.1 延迟队列

  • 消息进入队列之后,延迟指定的时间才能被消费者消费
  • RabbitMQ是不支持延迟队列的,如何实现呢?
  • AMQP协议和RabbitMQ本身是不支持延迟队列功能的,但是可以通过TTL(Time to Live)特性模拟延迟队列的功能
  • TTL就是消息的存活时间,RabbitMQ可以分别对队列和消息设置存活时间;
    • 在创建队列时可以设置队列的存活时间,当消息进入到队列并且在存活时间内没有被消费者消费,则次消息就从当前队列移除;
    • 创建消息队列没有设置TTL,但是消息设置了TTL,则当消息存活时间结束,也会被移除;
    • 当TTL结束之后,
      在这里插入图片描述
      因为队列时严格的先进先出,消息的存活时间应该避免前大后小
      在这里插入图片描述
      解决方式:
      使用两个队列
      在这里插入图片描述

5.2使用延迟队列实现订单支付监控

5.2.1实现流程图

在这里插入图片描述

5.2.2 创建交换机和队列

1.创建路由交换机
在这里插入图片描述
2.创建消息队列
在这里插入图片描述
3.创建死信队列
在这里插入图片描述
4.绑定队列

在这里插入图片描述
然后发送到delay_queue1,在delay_queue2接受即可。

六、消息队列作用/使用场景总结

6.1 解耦

场景说明:用户下单后,订单系统要通知库存系统

  • 传统做法:
    订单系统直接调用库存系统提供的借口,如果系统出现故障会导致订单系统失败

在这里插入图片描述

  • 使用消息队列
    在这里插入图片描述

6.2 异步

场景说明:用户注册成功之后,需要发送注册邮件及注册短信提醒

  • 传统方式:
    在这里插入图片描述
  • 使用消息队列:
    在这里插入图片描述

6.3 消息通信

场景说明:应用系统之间的通信,例如聊天室
在这里插入图片描述

6.4 流量削峰

场景说明:秒杀业务

大量的请求不会主动请求秒杀业务,而是存放在消息队列(缓存)
可以指定队列长度,队列长度100,100后的消息被丢弃
在这里插入图片描述

6.5 日志处理

场景说明:系统中大量的日志处理
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值