SpringCloud----RabbitMQ高级消息队列

概述

RabbitMQ是一种消息中间件,用于处理来自客户端的异步消息。服务端将要发送的消息放入到队列池中。接收端可以根据RabbitMQ配置的转发机制接收服务端发来的消息。

RabbitMQ依据指定的转发规则进行消息的转发、缓冲和持久化操作,主要用在多服务器间或单服务器的子系统间进行通信,是分布式系统标准的配置。

使用场景

虚拟机配置

image-20211213152010280

  1. 解压出来
  2. 通过VMware打开
  3. 内容
    1. yun 安装源, 扩展源使用了阿里服务器
    2. 安装了Python, pip, ansible
    3. 添加了两个脚本文件, 方便配置IP地址
      1. IP-static- 配置固定IP
      2. IP-dhcp: 自动获取IP
  4. 使用./ip-dhcp命令自动获取IP
  5. 关机克隆设置名字为 Docker-base 用来安装docker
  6. ifconfig查看IP地址
  7. 使用MobaXterm连接虚拟机

Docker

下载docker离线包

https://download.docker.com/linux/static/stable/x86_64/docker-20.10.6.tgz

这个地址可以选择自己需要的版本进行下载:
https://download.docker.com/linux/static/stable/

离线安装工具

https://github.com/Jrohy/docker-install/

image-20211213204324420

安装

  1. 将前面下载的以下文件放入服务器的 /root/docker-install 文件夹下:

    - [docker-install]
    	- docker-20.10.6.tgz
    	- install.sh
    	- docker.bash
    
  2. 执行安装:

    # 进入 docker-install 文件夹
    cd docker-install
    
    # 为 docker-install 添加执行权限
    chmod +x install.sh
    
    # 安装
    ./install.sh -f docker-20.10.6.tgz
    

镜像加速

由于国内网络问题,需要配置加速器来加速。
修改配置文件 /etc/docker/daemon.json

下面命令直接生成文件 daemon.json

cat <<EOF > /etc/docker/daemon.json
{
  "registry-mirrors": [
    "https://docker.mirrors.ustc.edu.cn",
    "http://hub-mirror.c.163.com"
  ],
  "max-concurrent-downloads": 10,
  "log-driver": "json-file",
  "log-level": "warn",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
    },
  "data-root": "/var/lib/docker"
}
EOF

​ 重启服务

# 重新加载docker配置
sudo systemctl daemon-reload

#重启docker服务
sudo systemctl restart docker

测试

docker info

docker run hello-world

Docker运行RabbitMQ

  1. 克隆Docker虚拟机: RabbitMQ
  2. 设置IP地址
  3. MobaX连接虚拟机

RabbitMQ配置

  1. Rabbit镜像拖入root目录

image-20211213204334418

  1. 导入镜像docker load < rabbit-image.gz

  2. 关闭防火墙

    systemctl stop firewalld
    systemctl disable firewalld
     
    # 重启 docker 系统服务
    systemctl restart docker
    
  3. 配置管理员账号密码

    mkdir /etc/rabbitmq
    vim /etc/rabbitmq/rabbitmq.conf
    
    # 添加两行配置:
    default_user = admin
    default_pass = admin
    
  4. 启动Rabbitmq

    docker run -d --name rabbit \
    -p 5672:5672 \
    -p 15672:15672 \
    -v /etc/rabbitmq/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf \
    -e RABBITMQ_CONFIG_FILE=/etc/rabbitmq/rabbitmq.conf \
    --restart=always \
    rabbitmq:management
    
    
  5. 访问测试

访问管理控制台 http://192.168.64.4:15672
用户名密码是 admin

image-20211213204341508

rabbitmq六种工作模式

简单模式

准备工作

image-20211213204349127

  1. 新建空项目

  2. 添加依赖

        <dependencies>
            <dependency>
                <groupId>com.rabbitmq</groupId>
                <artifactId>amqp-client</artifactId>
                <version>5.4.3</version>
            </dependency>
        </dependencies>
    

生产者发送信息

public class Producer {
    public static void main(String[] args) throws IOException, TimeoutException {
        /*连接*/
        ConnectionFactory f = new ConnectionFactory();
        f.setHost("192.168.64.4");
        f.setPort(5672);
        f.setUsername("admin");
        f.setPassword("admin");

        Connection con = f.newConnection();   // 创建连接
        Channel c = con.createChannel();  // 通信的通道

        /*在服务器上创建 helloworld 队列
        * 队列如果存在, 不会重复创建*/
        c.queueDeclare("helloworld",false,false,false,null); //队列定义/声明

        /*向队列发送消息*/
        c.basicPublish("","helloworld",null,"Hello World".getBytes());  // 2. 队列名

    }
}

  • setHost : 为 Rabbitmq服务虚拟机IP
  • setPort : Rabbitmq端口号
  • setUsername/setPassword : Rabbitmq配置的用户名密码
参数说明

image-20211213204356740

  1. 队列名

  2. 布尔值(是否是持久队列)持久: 重启服务器队列存在 (在磁盘储存队列信息)

  3. 是否是排他队列 (独占队列) (是否可以共享队列)

  4. 队列能不能被服务器自动删除, (没有消费者时服务器是否自动删除)

  5. 队列的其他参数属性

  6. 交换机,空串是默认交换机

  7. 队列名

  8. 消息的其他参数属性 (用键值对设置参数属性, 如果没有就用 null )

测试
image-20211213152059618

消费者接受消息

public class Consumer {
    public static void main(String[] args) throws IOException, TimeoutException {
        /*建立连接*/
        ConnectionFactory f = new ConnectionFactory();
        f.setHost("192.168.64.4");
        f.setPort(5672);
        f.setUsername("admin");
        f.setPassword("admin");

        Connection con = f.newConnection();   // 创建连接
        Channel c = con.createChannel();  // 通信的通道

        /*创建队列*/
        c.queueDeclare("helloworld",false,false,false,null); //队列定义/声明

        /*创建回调对象*/
        DeliverCallback deliverCallback = new DeliverCallback() {
            public void handle(String s, Delivery delivery) throws IOException {
                byte[] a = delivery.getBody();
                String string = new String(a);
                System.out.println("收到:"+ string);
            }
        };  // 处理消息的回调
        CancelCallback cancelCallback = new CancelCallback() {
            public void handle(String s) throws IOException {

            }
        };  // 取消消息的回调

        /*从队列接收消息, 把消息传递到回调对象处理*/
//        c.basicConsume("helloworld", true, 处理消息的回调对象, 取消消息的回调对象);
        c.basicConsume("helloworld", true, deliverCallback, cancelCallback);
    }
}
参数说明

image-20211213152129190

  1. 确认方式 ACK – Acknowledgment

    • true 自动确认
    • false 手动确认

工作模式

多个消费者接收消息

生产者发送消息

public class Producer {
    public static void main(String[] args) throws IOException, TimeoutException {
        /*创建连接*/
        ConnectionFactory f = new ConnectionFactory();
        f.setHost("192.168.64.4");
        f.setPort(5672);
        f.setUsername("admin");
        f.setPassword("admin");

        Connection con = f.newConnection();   // 创建连接
        Channel c = con.createChannel();  // 通信的通道

        /*创建队列*/
        c.queueDeclare("helloworld",false,false,false,null); //队列定义/声明

        /*循环输入消息发送*/
        while (true) {
            System.out.println("请输入消息:");
            String s = new Scanner(System.in).nextLine();
            c.basicPublish("","helloworld",null,s.getBytes());
        }

    }
}

消费者接收消息

  • 遍历每一个数据
  • 当遇到" . " 时就暂停1秒
public class Consumer {
    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory f = new ConnectionFactory();
        f.setHost("192.168.64.4");
        f.setPort(5672);
        f.setUsername("admin");
        f.setPassword("admin");

        Connection con = f.newConnection();   // 创建连接
        Channel c = con.createChannel();  // 通信的通道

        /*创建队列*/
        c.queueDeclare("helloworld",false,false,false,null); //队列定义/声明

        /* 回调对象*/
        DeliverCallback deliverCallback = (consumerTag, message) -> {
            String s = new String(message.getBody());
            System.out.println(s);
            // 遍历所有字符,遇到 '.' 暂停1秒
            for (int i = 0; i < s.length(); i++) {
                if (s.charAt(i) == '.') {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                    }
                }
            }
            System.out.println("-------------------------------消息处理完成");
        };
        CancelCallback cancelCallback = consumerTag -> {};

        // 接收消息
        c.basicConsume("helloworld",true,deliverCallback,cancelCallback);
    }
}

测试一

  1. Producer发送消息

    image-20211213152139544

  2. Consumer接收消息

    image-20211213152146236

  3. 查看服务器

    image-20211213152153093

image-20211213152204310

测试二

  • 启动多个消费者

    image-20211213152221646
  1. Producer 发送消息

    image-20211213152233501
  2. 查看两个Consumer接收

image-20211213152244947 image-20211213152255042
  1. 通过" . " 测试, 先发送一堆 " . "
image-20211213152307027
  1. Consumer接收到 “…” 接着发送 1-9测试

image-20211213152320614

  • 接收到"…"的Consumer-2 进入等待

    image-20211213152328759

  • 另一个 Consumer 打印出一部分信息

    image-20211213152336251

  • Consumer-2 等待结束后, 打印出剩下的消息

    image-20211213152350559

测试三(手动确认回执)

修改成手动确认回执

  1. 修改Consumer 手动确认

image-20211213152358163

  1. 添加回执

    image-20211213152405668

  2. 添加Qos

    image-20211213152412220

测试
  1. produce 发送 “…” 和 1-9

    image-20211213152421860
  2. Consumer2接收后进入等待

  3. Consumer2接收后进入等待

    image-20211213152432297
  4. Consumer接收到所有信息

image-20211213152444964

队列持久化

  1. 将Producer 持久化改成 true, 队列名改成"task_queue"

image-20211213152454325

  1. basicPublish其他参数属性改为 持久化属性

    image-20211213152502966

  2. 修改Consumer 创建队列参数

  3. 修改Consumer 接收信息队列名 (task_queue)

  4. 添加队列信息测试, 打开RabbitMQ 服务器查看队列信息, 会发现有helloworld 和 task_queue 两个队列

  5. 重启 RabbitMQ 服务, 再打开RabbitMQ 服务器查看, 没有持久化的helloworld就没有了, 做了持久化的task_queue 还存在

发布订阅模式

生产者

package m3;

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConnectionFactory;

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

/**
 * 生产商
 *
 * @author 刘杰
 * @date 2021/11/22 10:48:59
 */
public class Producer {
    public static void main(String[] args) throws IOException, TimeoutException {
        // 连接
        ConnectionFactory f = new ConnectionFactory();
        f.setHost("192.168.64.4"); // wht6.cn
        f.setPort(5672);
        f.setUsername("admin");
        f.setPassword("admin");
        Channel c = f.newConnection().createChannel();
        // 创建 Fanout 类型的交换机: logs
        // c.exchangeDeclare("logs", "fanout");
        c.exchangeDeclare("logs", BuiltinExchangeType.FANOUT);
        // 向 logs 交换机发送消息
        while (true) {
            System.out.print("输入消息:");
            String s = new Scanner(System.in).nextLine();
            // 第二个参数,对于fanout类型交换机无效
            c.basicPublish("logs", "", null, s.getBytes());
        }
    }
}

消费者

package m3;

import com.rabbitmq.client.*;

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

/**
 * 消费者
 *
 * @author 刘杰
 * @date 2021/11/22 10:08:32
 */
public class Consumer {
    public static void main(String[] args) throws IOException, TimeoutException {
        // 连接
        ConnectionFactory f = new ConnectionFactory();
        f.setHost("192.168.64.4"); // wht6.cn
        f.setPort(5672);
        f.setUsername("admin");
        f.setPassword("admin");
        Channel c = f.newConnection().createChannel();


        // 1.创建随机队列 2.创建交换机 3.绑定  手动命名
        String queue = UUID.randomUUID().toString();
        c.queueDeclare(queue,false,true,true,null);  //手动提供参数
        c.exchangeDeclare("logs", BuiltinExchangeType.FANOUT);
        // 第三个参数对 fanout 交换机无效
        c.queueBind(queue, "logs", "");

        // 正常的从随机队列接收处理消息
        DeliverCallback deliverCallback = (consumerTag, message) -> {
            String s = new String(message.getBody());
            System.out.println("收到:"+s);
        };
        CancelCallback cancelCallback = consumerTag -> {};
        c.basicConsume(queue,true,deliverCallback,cancelCallback);
    }
}

测试

主题模式

生产者

public class Producer {
    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory f = new ConnectionFactory();
        f.setHost("192.168.64.4"); // wht6.cn
        f.setPort(5672);
        f.setUsername("admin");
        f.setPassword("admin");
        f.setVirtualHost("/");  //默认空间
        Channel c = f.newConnection().createChannel();

        c.exchangeDeclare("direct_logs", BuiltinExchangeType.DIRECT);

        while (true) {
            System.out.println("输入消息: ");
            String s = new Scanner(System.in).nextLine();
            System.out.println("输入路由键: ");
            String k = new Scanner(System.in).nextLine();
            /*2: 路由键关键词
            * 如果用默认交换机, 路由键就是队列名*/
            c.basicPublish("direct_logs", k,null, s.getBytes(StandardCharsets.UTF_8));

        }
    }
}

消费者

public class Consumer {
    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory f = new ConnectionFactory();
        f.setHost("192.168.64.4"); // wht6.cn
        f.setPort(5672);
        f.setUsername("admin");
        f.setPassword("admin");
        Channel c = f.newConnection().createChannel();

        String queue = c.queueDeclare().getQueue();//服务器自动提供参数: 随机名,false,true,true  .getQueue(): 得到随机名(因为后面要用)
        c.exchangeDeclare("direct_logs", BuiltinExchangeType.DIRECT);
        System.out.println("输入绑定键, 用空格隔开: ");   // "aa  bb  cc"
        String s = new Scanner(System.in).nextLine();
        String[] a = s.split("\\s+");// \s是空白字符, +表示一到多个 (一个或多个都可以)
        for (String k : a) {
            c.queueBind(queue, "direct_logs", k);
        }
        DeliverCallback deliverCallback = (consumerTag, message) -> {
            java.lang.String ss = new java.lang.String(message.getBody());
            System.out.println("收到:" + ss);
        };
        CancelCallback cancelCallback = consumerTag -> {};
        c.basicConsume(queue,true, deliverCallback, cancelCallback);

    }
}

测试一

  • 消费者输入绑定键

    image-20211213152516247
  • 生产者输入消息 + 路由键

    image-20211213152528362

  • 消费者收到消息

    image-20211213152535631

测试二

  • 生产者随便输入

    image-20211213152542597

  • 消费者不会受到

    image-20211213152549932

virtual host 设置空间

在RabbitMQ中叫做虚拟消息服务器VirtualHost,每个VirtualHost相当于一个相对独立的RabbitMQ服务器,每个VirtualHost之间是相互隔离的。exchange、queue、message不能互通

image-20211213152602598
  • 默认空间名为: " / "

查看结果

image-20211213152614778

使用:

image-20211213152625613

Sleuth + Zipkin 链路跟踪

随着系统规模越来越大,微服务之间调用关系变得错综复杂,一条调用链路中可能调用多个微服务,任何一个微服务不可用都可能造整个调用过程失败

spring cloud sleuth 可以跟踪调用链路,分析链路中每个节点的执行情况

添加依赖

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-sleuth</artifactId>
        </dependency>

Zipkin

https://github.com/openzipkin/zipkin

下载

image-20211213152636075

启动服务器

在根目录打开终端控制器

java -jar zipkin-server-2.23.5-exec.jar --zipkin.collector.rabbitmq.uri=amqp://admin:admin@192.168.64.4:5672

如用自己的空间

java -jar zipkin-server-2.12.9-exec.jar --zipkin.collector.rabbitmq.uri=amqp://admin:admin@192.168.64.4:5672 / 空间名

Zipkin访问地址

http://localhost:9411/zipkin

添加依赖

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

配置yml

添加06网关, 三个消费服务

rabbitmq:
    port: 5672
    host: 192.168.64.4
    username: admin
    password: admin
    virtual-host: /
  zipkin:
    sender:
      type: rabbit   # zipkin 向 Rabbit 转发日志

导入商城项目

  1. 课前资料/elasticsearch/pd-商城项目案例.zip 里面的 pd-web 文件夹,解压到 rabbitmq 工程目录

  2. pom文件 SpringBoot版本改为2.3.2.RELEASE

  3. 配置连接数据库

  4. 执行启动类后配置启动类

  5. 在 working directory 中配置 pd-web 模块目录路径 然后重新启动

    image-20211213152721576

  6. 访问 http://localhost

订单发送到 RabbitMQ

添加依赖

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-amqp</artifactId>
		</dependency>

配置yml

  rabbitmq:
    host: 192.168.64.4
    port: 5672
    username: amdin
    password: admin
    virtual-host: /

配置启动类

  1. 新建 spring 的 Queue 实例
  2. 封装队列的参数
  3. rabbitmq的自动配置类会自动发现这个Queue 实例
  4. 根据其中的参数自动在服务器上创建队列
	@Bean
	public Queue orderQueue() {
		return new Queue("orderQueue", true, false, false);
	}

队列参数解析

  1. 持久队列
  2. 独占队列
  3. 不自动删除

修改 orderServiceImpl

  1. 注释 64-89行

    image-20211213152745426

  2. 添加 代码

    • 注入对象: AmqpTemplate (用来封装发送消息代码的工具)

    image-20211213152804733

订单消费者

  1. 复制 pd-web 项目起名为 pd-web-consumer
  2. 修改pom文件

新建消费者类: OrderConsumer

  1. 添加注解

    • @RabbitListener 通过注解配置就可以接受消息
    • queues = “orderQueue” 队列名
  2. 创建方法

    image-20211213204604428

用注解配置接收消息
收到的订单通过原来的业务方法代码, 存储到数据库

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

偶尔也吹晚风

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

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

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

打赏作者

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

抵扣说明:

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

余额充值