RabbitMQ入门:从安装到高级消息模式

news2025/3/15 10:43:54

文章目录

  • 一. RabbitMQ概述
    • 1.1 同步/异步
      • 1.1.1 同步调用
      • 1.1.2 异步调用
    • 1.2 消息中间件
      • 1.2.1 概念
      • 1.2.2 作用
      • 1.2.3 常见的消息中间件
      • 1.2.4 其他中间件
    • 1.3 RabbitMQ
      • 1.3.1 简介
      • 1.3.2 特点
      • 1.3.3 方式
      • 1.3.4 架构
      • 1.3.5 运行流程
  • 二. 安装
    • 2.1 Docker 安装 RabbitMQ
  • 三. 简单队列(Simple Queue)
    • 3.1 消息模式
    • 3.2 简单队列(Simple Queue)
      • 3.2.1 面板操作
      • 3.2.2 代码操作
  • 四. 工作队列(Work Queue)
    • 4.1 消息模式
    • 4.2 工作队列(Work Queue)
      • 4.2.1 轮询模式
      • 4.2.2 公平模式
    • 4.3 总结
  • 五. 发布订阅(Pub/Sub)
    • 5.1 理解
      • 5.1.1 发布订阅模式具体实现
        • 生产者
        • 消费者
  • 六. 路由模式(Routing)
    • 6.1 图形化界面理解
      • 6.1.1 创建direct交换机
      • 6.1.2 交换机与队列绑定
      • 6.1.3 模拟生产者发送消息
      • 6.1.4 查看队列消息
    • 6.2 发布订阅模式具体实现
      • 6.2.1 生产者
      • 6.1.2 消费者
  • 七. 主题模式(Topic)
    • 7.1 图像界面理解
    • 7.2 主题模式具体实现
      • 7.2.1 生产者
      • 7.2.2 消费者
  • 八. SpringBoot集成RabbitMQ

一. RabbitMQ概述

1.1 同步/异步

1.1.1 同步调用

同步调用是指客户端调用远程服务时,需要等待服务端返回结果后才能继续执行,典型的场景如远程调用 HTTP 服务。
在这里插入图片描述
同步调用的优势:时效性强,等到结果后才返回–需要查询结果的通常用同步调用
同步调用的问题:

  • 拓展性差–增加功能要改代码
  • 性能下降–调用链路长,每次都是阻塞等待上一个服务
  • 级联失败问题–一个服务挂掉,整个链路上的服务都出问题

1.1.2 异步调用

异步调用是指客户端调用远程服务时,不需要等待服务端返回结果,而是直接返回一个代表请求的消息,典型的场景如消息队列。当服务端处理完请求后,通过消息队列通知客户端。
在这里插入图片描述
异步调用的优势:提升性能–不需要等待结果,可以继续执行–适合处理耗时长的请求

1.2 消息中间件

1.2.1 概念

消息中间件(Message Queue,MQ)是一种应用程序之间的数据交换方式,它是一种分布式系统架构模式,用于在不同的应用程序之间传递消息。消息中间件的主要功能是实现应用间的松耦合,让信息的发送方和接收方不需直连,而是通过消息中间件进行交互。

  • 消息: 简单的说就是软件之间通讯时传递的数据,它可以是很简单的数字、字母,也可以是很复杂的嵌套对象数据。
  • 中间件:最简单的理解是第三者,本来软件A和软件B间的通讯两者直接传递消息就可以了,但是,此时中间件作为第三者,非要先让软件A通讯的消息先发给它,再由它发给软件B(感觉就是中间商一样),下面通过图来更好的理解它们。
  • 消息队列:是消息中间件的一种实现方式。

总结:消息中间件则是将软件与软件之间的交互方式进行存储和管理的一种技术,也可以看做是一种容器
在这里插入图片描述

1.2.2 作用

  • 异步通信:消息队列可以实现应用间的异步通信,应用只需要将消息放入队列,不用等待回复,就可以继续执行。

比如我们最常见的短信验证码功能,当我们在界面点击“获取验证码”后,我们还可以同时进行其他的操作,如输入更新的密码等,此时,我们不需要一直等到手机收到短信了才进行下一步的操作,这就是异步处理,提高了用户体验。
在这里插入图片描述

  • 解耦合:通过消息队列解耦合应用,应用之间不再需要直连,而是通过消息队列进行通信。

比如常见的订单系统,当有订单下单时,我们需要减去库存,但如果订单、库存的逻辑都放在一个系统中,不止处理事件需要很长,系统的耦合性比较高,此时,使用消息中间件,可以实现将订单业务和库存业务抽出来做不同的系统,每次下单的时候可以将下单信息放入消息中间间中,然后库存系统去订阅它,只有有订单数据就进行减去库存操作,这样就将应用解耦了
在这里插入图片描述

  • 削峰填谷:通过消息队列可以有效地削峰填谷,避免应用因消息处理过慢而出现性能问题。

如常见的秒杀系统,如果有5万个商品可以秒杀,没有消息中间件的话,所有的请求都一次性到后台,此时系统很容易卡死,引入消息中间件如消息队列,此时可以在队列中设置好可以存储数据的数量,这样每次用户请求会先但消息队列中,消息队列就减去1,当消息队列中存储长度为0时,直接返回秒杀失败,这样就避免了所有用户请求可能在同一时间到达系统后台,达到流量削峰的作用
在这里插入图片描述

  • 广播消费:消息队列可以实现广播消费,一个消息可以被多个消费者消费

1.2.3 常见的消息中间件

  • ActiveMQ:Apache 出品,主要用于企业级的消息中间件,支持多种协议,包括 AMQP、MQTT、STOMP 等。
  • RabbitMQ:RabbitMQ 是一款开源的消息队列软件,由 Erlang 语言编写,基于 AMQP 协议。
  • Kafka:Apache 出品,主要用于大数据实时处理,支持多种协议,包括 Apache Kafka、Apache Pulsar 等。
  • RocketMQ:阿里巴巴开源的消息队列软件,主要用于微服务架构。

1.2.4 其他中间件

  • 分布式消息中间件:RocketMQ、Kafka、ActiveMQ 等。
  • 负载均衡中间件:Nginx、LVS、HAProxy 等。
  • 缓存中间件:Redis、Memcached 等。
  • 数据库中间件:MySQL、MongoDB 等。
  • 搜索引擎中间件:ElasticSearch、Solr 等。
  • 日志中间件:Logstash、Flume 等。
  • 容器中间件:Docker、Kubernetes 等。

1.3 RabbitMQ

RabbitMQ官网

1.3.1 简介

RabbitMQ 是一款开源的消息队列软件,由 Erlang 语言编写,基于 AMQP 协议。RabbitMQ 最初起源于金融系统,用于在分布式系统中存储和转发消息。RabbitMQ 是一个在分布式系统中用于存储和转发消息的消息队列,它可以实现可靠的消息传递,支持多种消息队列协议,包括 AMQP、STOMP、MQTT 等。RabbitMQ 是一个非常灵活的消息队列,可以支持多种应用场景,如任务队列、事件驱动、数据流、消息分发等。

1.3.2 特点

  • 高可用性:RabbitMQ 集群保证消息的高可用性,即使部分节点发生故障,也能保证消息的传递。
  • 灵活的路由机制:RabbitMQ 支持多种路由机制,包括点对点、发布/订阅、主题等。
  • 多种协议支持:RabbitMQ 支持多种消息队列协议,包括 AMQP、STOMP、MQTT 等。
  • 多种语言客户端:RabbitMQ 提供多种语言的客户端,如 Java、.NET、Python、Ruby 等。
  • 管理界面:RabbitMQ 提供了一个易用的管理界面,可以直观地查看消息队列的状态。
  • 多种插件支持:RabbitMQ 提供了许多插件,可以实现各种功能,如消息持久化、消息确认、消息集群、Web 管理界面等。

1.3.3 方式

  • 点对点(P2P):点对点通信,一个生产者发送消息到一个队列,一个消费者从同一个队列中接收消息。
  • 发布/订阅(Pub/Sub):发布/订阅通信,一个生产者发送消息到一个交换机,多个消费者从同一个交换机订阅同一个主题的消息。
  • 主题(Topic):主题通信,一个生产者发送消息到一个主题交换机,多个消费者从同一个主题交换机订阅同一个主题的消息。

1.3.4 架构

在这里插入图片描述

  • Server:又称Broker, RabbitMQ 服务器,用于存储、转发消息。
  • 连接器(Connector):用于客户端和 RabbitMQ 服务器之间的网络连接。
  • 生产者(Producer):消息的发送方,向 RabbitMQ 队列中发送消息。
  • 交换机(Exchange):消息交换机,用于接收生产者的消息并将其路由到队列。
  • 队列(Queue):消息队列,存储消息直到消费者取出并处理。
  • 绑定(Binding):绑定,用于将交换机和队列进行关联。
  • 路由键(Routing Key):路由键,用于指定消息的路由规则。
  • 消费者(Consumer):消息的接收方,从 RabbitMQ 队列中接收消息并处理。
  • 虚拟主机(Virtual Host):虚拟主机,用于隔离不同用户的权限。
  • 信道(Channel):信道,用于连接到 RabbitMQ 服务器,并进行消息的传输。

1.3.5 运行流程

在这里插入图片描述

  • 1.生产者将消息发送到交换机。
  • 2.交换机根据路由规则将消息路由到队列。
  • 3.队列将消息存储在内存中。
  • 4.消费者从队列中获取消息并处理。
  • 5.消费者确认消息已被处理。
  • 6.RabbitMQ 服务器将消息发送给消费者。

二. 安装

2.1 Docker 安装 RabbitMQ

1.拉取镜像或加载本地镜像

docker pull rabbitmq:management

docker load -i rabbitmq.tar

2.创建数据卷

docker volume create mq-plugins

3.运行容器

docker run \
 -e RABBITMQ_DEFAULT_USER=admin \
 -e RABBITMQ_DEFAULT_PASS=123456 \
 -v mq-plugins:/plugins \
 --name mq \
 --hostname mq1 \
 -p 15672:15672 \
 -p 5672:5672 \
 -d \
 rabbitmq:3.8-management

相关信息

  • -e RABBITMQ_DEFAULT_USER=admin:设置默认用户名为 admin
  • -e RABBITMQ_DEFAULT_PASS=123456:设置默认密码为 123456
  • -v mq-plugins:/plugins:挂载数据卷,用于存储插件
  • –name mq:设置容器名称为 mq
  • –hostname mq1:设置主机名为 mq1
  • -p 15672:15672:将容器的 15672 端口映射到主机的 15672 端口
  • -p 5672:5672:将容器的 5672 端口映射到主机的 5672 端口
  • -d:后台运行容器
  • rabbitmq:3.8-management:指定镜像版本为 3.8-management

4.验证安装
访问 http://服务器IP:15672
输入用户名和密码,默认用户名为 admin,密码为 123456
在这里插入图片描述

三. 简单队列(Simple Queue)

3.1 消息模式

参考官网

3.2 简单队列(Simple Queue)

一个生产者对应一个消费者,消息直接发送到队列。
在这里插入图片描述

官方的HelloWorld是基于最基础的消息队列模型来实现的,只包括三个角色:

  • publisher:消息发布者,将消息发送到队列queue
  • queue:消息队列,负责接受并缓存消息
  • consumer:订阅队列,处理队列中的消息

3.2.1 面板操作

1.创建一个队列
在这里插入图片描述

2.在默认交换处模拟生产者发送消息 因为该队列绑定的是默认交换机,所以消息会直接发送到队列中。
在这里插入图片描述
在这里插入图片描述
:Routing Key 写队列名
3.队列处查看消息
在这里插入图片描述
4.模拟消费者接收消息(查看消息内容)
在这里插入图片描述

AckMode : 应答模式

  • Nack: 不应答,只查看,消息不会移除队列
  • Ack: 应答模式,查看并移除队列

3.2.2 代码操作

1.导入依赖

  1. java原生依赖
<dependency>
    <groupId>com.rabbitmq</groupId>
    <artifactId>amqp-client</artifactId>
    <version>5.10.0</version>
</dependency>
  1. Spring依赖
<dependency>
     <groupId>org.springframework.amqp</groupId>
     <artifactId>spring-amqp</artifactId>
     <version>2.2.5.RELEASE</version>
 </dependency>

 <dependency>
     <groupId>org.springframework.amqp</groupId>
     <artifactId>spring-rabbit</artifactId>
     <version>2.2.5.RELEASE</version>
 </dependency>
  1. Spring Boot 依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

根据自己的项目环境进行选择即可

2.定义生产者

package com.syh.simple;

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

import java.io.IOException;

/**
 * @author shan
 * @date 2024/5/16 14:39
 */
public class Producer {
    public static void main(String[] args) {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //   设置RabbitMQ地址
        factory.setHost("47.120.37.156");
        factory.setPort(5672);
        factory.setVirtualHost("/");
        factory.setUsername("guest");
        factory.setPassword("guest");

        Connection connection = null;
        Channel channel = null;
        try {
            // 创建连接
            connection = factory.newConnection();
            // 创建通道
            channel = connection.createChannel();
            // 5: 申明队列queue存储消息
            /*
             *  如果队列不存在,则会创建
             *  Rabbitmq不允许创建两个相同的队列名称,否则会报错。
             *
             *  @params1: queue 队列的名称
             *  @params2: durable 队列是否持久化
             *  @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
             *  @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
             *  @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
             * */
            channel.queueDeclare("hello", false, false, false, null);
            // 6: 发送消息
            String message = "Hello World!";
            // 7: 发送消息给中间件rabbitmq-server
            // @params1: 交换机exchange,会有一个默认交换机
            // @params2: 队列名称/routing
            // @params3: 属性配置
            // @params4: 发送消息的内容
            channel.basicPublish("", "hello", null, message.getBytes("UTF-8"));
            System.out.println(" [x] Sent '" + message + "'");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println(" [x] Unexpected exception: " + e.getMessage());
        } finally {
            // 关闭连接和通道
            if (connection!= null && channel.isOpen()) {
                try {
                    connection.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

执行发送,这个时候可以在web控制台查看到这个队列queue的信息。

3.定义消费者

package com.syh.simple;

import com.rabbitmq.client.*;

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

/**
 * @author shan
 * @date 2024/5/16 14:48
 */
public class Consumer {
    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        // 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
        factory.setHost("47.120.37.156");
        factory.setPort(5672);
        factory.setVirtualHost("/");
        factory.setUsername("guest");
        factory.setPassword("guest");
        // 1.2.建立连接
        Connection connection = factory.newConnection();

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

        // 3.创建队列
        String queueName = "hello";
        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("等待接收消息。。。。");
    }
}

执行消费者,这个时候可以看到控制台输出接收到的消息。

四. 工作队列(Work Queue)

4.1 消息模式

参考官网

4.2 工作队列(Work Queue)

在这里插入图片描述
工作队列(Work Queue)模式是一种消息模式,它将任务分派给多个消费者,每个消费者都可以独立地处理任务。

当有多个消费者时,我们的消息会被哪个消费者消费呢,我们又该如何均衡消费者消费信息的多少呢? RabbitMQ提供了两种工作队列模式:

  • 轮询模式(Round-robin):每个消费者都轮流接收消息,平均分配消息;
  • 公平模式(Fair dispatch):每个消费者都有相同的权重,按比例分配消息;

4.2.1 轮询模式

  • 轮询模式是最简单的工作队列模式,每个消费者都接收到相同数量的消息,但消息的顺序是不确定的。
  • 轮询模式适用于消费者数量固定的情况,消费者的数量越多,平均分配消息的数量越少。
  • 轮询模式的优点是简单,缺点是消息的顺序不确定。

轮询模式的实现

  • 我们需要创建一个队列,并将消息发送到队列中。
  • 创建多个消费者,并将它们绑定到同一个队列上。

1.生产者

public class Producer {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("47.104.141.27");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("生产者");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 6: 准备发送消息的内容
            //===============================end topic模式==================================
            for (int i = 1; i <= 20; i++) {
                //消息的内容
                String msg = "学相伴:" + i;
                // 7: 发送消息给中间件rabbitmq-server
                // @params1: 交换机exchange
                // @params2: 队列名称/routingkey
                // @params3: 属性配置
                // @params4: 发送消息的内容
                channel.basicPublish("", "queue1", null, msg.getBytes());
            }
            System.out.println("消息发送成功!");
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

2.消费者1

public class Work1 {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("47.104.141.27");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("消费者-Work1");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 5: 申明队列queue存储消息
            /*
             *  如果队列不存在,则会创建
             *  Rabbitmq不允许创建两个相同的队列名称,否则会报错。
             *
             *  @params1: queue 队列的名称
             *  @params2: durable 队列是否持久化
             *  @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
             *  @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
             *  @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
             * */
            // 这里如果queue已经被创建过一次了,可以不需要定义
//            channel.queueDeclare("queue1", false, false, false, null);
            // 同一时刻,服务器只会推送一条消息给消费者
            // 6: 定义接受消息的回调
            Channel finalChannel = channel;
            finalChannel.basicQos(1);
            finalChannel.basicConsume("queue1", true, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    try{
                        System.out.println("Work1-收到消息是:" + new String(delivery.getBody(), "UTF-8"));
                        Thread.sleep(2000);
                    }catch(Exception ex){
                        ex.printStackTrace();
                    }
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                }
            });
            System.out.println("Work1-开始接受消息");
            System.in.read();
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

3.消费者2

public class Work2 {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("47.104.141.27");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("消费者-Work2");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 5: 申明队列queue存储消息
            /*
             *  如果队列不存在,则会创建
             *  Rabbitmq不允许创建两个相同的队列名称,否则会报错。
             *
             *  @params1: queue 队列的名称
             *  @params2: durable 队列是否持久化
             *  @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
             *  @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
             *  @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
             * */
            // 这里如果queue已经被创建过一次了,可以不需要定义
            //channel.queueDeclare("queue1", false, true, false, null);
            // 同一时刻,服务器只会推送一条消息给消费者
            //channel.basicQos(1);
            // 6: 定义接受消息的回调
            Channel finalChannel = channel;
            finalChannel.basicQos(1);
            finalChannel.basicConsume("queue1", true, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    try{
                        System.out.println("Work2-收到消息是:" + new String(delivery.getBody(), "UTF-8"));
                        Thread.sleep(200);
                    }catch(Exception ex){
                        ex.printStackTrace();
                    }
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                }
            });
            System.out.println("Work2-开始接受消息");
            System.in.read();
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

4.运行结果

Work1-开始接受消息
Work2-开始接受消息
Work1-收到消息是:学相伴:1
Work2-收到消息是:学相伴:1
Work1-收到消息是:学相伴:2
Work2-收到消息是:学相伴:2
Work1-收到消息是:学相伴:3

4.2.2 公平模式

  • 公平模式是一种更复杂的工作队列模式,每个消费者都有相同的权重,按比例分配消息。
  • 公平模式适用于消费者数量不固定的情况,消费者的数量越多,平均分配消息的数量越多。
  • 公平模式的优点是消息的顺序是确定的,缺点是分配消息的数量不确定。

公平模式的实现

  • 我们需要创建一个队列,并将消息发送到队列中。
  • 创建多个消费者,并将它们绑定到同一个队列上。

1.生产者

public class Producer {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("47.104.141.27");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("生产者");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 6: 准备发送消息的内容
            //===============================end topic模式==================================
            for (int i = 1; i <= 20; i++) {
                //消息的内容
                String msg = "学相伴:" + i;
                // 7: 发送消息给中间件rabbitmq-server
                // @params1: 交换机exchange
                // @params2: 队列名称/routingkey
                // @params3: 属性配置
                // @params4: 发送消息的内容
                channel.basicPublish("", "queue1", null, msg.getBytes());
            }
            System.out.println("消息发送成功!");
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

2.消费者1

public class Work1 {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("47.104.141.27");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("消费者-Work1");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 5: 申明队列queue存储消息
            /*
             *  如果队列不存在,则会创建
             *  Rabbitmq不允许创建两个相同的队列名称,否则会报错。
             *
             *  @params1: queue 队列的名称
             *  @params2: durable 队列是否持久化
             *  @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
             *  @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
             *  @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
             * */
            // 这里如果queue已经被创建过一次了,可以不需要定义
//            channel.queueDeclare("queue1", false, false, false, null);
            // 同一时刻,服务器只会推送一条消息给消费者
            // 6: 定义接受消息的回调
            Channel finalChannel = channel;
            finalChannel.basicQos(1);
            finalChannel.basicConsume("queue1", false, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    try{
                        System.out.println("Work1-收到消息是:" + new String(delivery.getBody(), "UTF-8"));
                        Thread.sleep(2000);
                        finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
                    }catch(Exception ex){
                        ex.printStackTrace();
                    }
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                }
            });
            System.out.println("Work1-开始接受消息");
            System.in.read();
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

3.消费者2

public class Work2 {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("47.104.141.27");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("消费者-Work2");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 5: 申明队列queue存储消息
            /*
             *  如果队列不存在,则会创建
             *  Rabbitmq不允许创建两个相同的队列名称,否则会报错。
             *
             *  @params1: queue 队列的名称
             *  @params2: durable 队列是否持久化
             *  @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
             *  @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
             *  @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
             * */
            // 这里如果queue已经被创建过一次了,可以不需要定义
            //channel.queueDeclare("queue1", false, true, false, null);
            // 同一时刻,服务器只会推送一条消息给消费者
            //channel.basicQos(1);
            // 6: 定义接受消息的回调
            Channel finalChannel = channel;
            finalChannel.basicQos(1);
            finalChannel.basicConsume("queue1", false, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    try{
                        System.out.println("Work2-收到消息是:" + new String(delivery.getBody(), "UTF-8"));
                        Thread.sleep(200);
                        finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
                    }catch(Exception ex){
                        ex.printStackTrace();
                    }
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                }
            });
            System.out.println("Work2-开始接受消息");
            System.in.read();
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

4.运行结果

4.3 总结

工作模式下的轮询: - 应答模式为自动应答 finalChannel.basicConsume(“queue1”, true, …) 工作模式下的公平: - finalChannel.basicQos(1); // 设置一次只接收一条消息 - 应答模式为手动应对 finalChannel.basicConsume(“queue1”, false, …) - finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);// 确认消息,手动应答

五. 发布订阅(Pub/Sub)

在这里插入图片描述

5.1 理解

角色:

p : Product 生产者 发布消息

X : 交换机

Q : 多个队列

C: Consumer 消费者 订阅消息

1,创建fanout类型的交换机
在这里插入图片描述

2.创建多个队列
在这里插入图片描述

3.绑定队列和交换机
在这里插入图片描述

或者队列处也可以绑定
在这里插入图片描述

4.在交换机处模拟生产者发送消息
在这里插入图片描述

5.查看队列中的消息数量
在这里插入图片描述
消息数量增加了

5.1.1 发布订阅模式具体实现

  • 类型:fanout
  • 特点:Fanout—发布与订阅模式,是一种广播机制,它是没有路由key的模式。
生产者
package com.syh.fanout;

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

/**
 * @author shan
 * @date 2024/5/16 20:49
 */
public class Producer {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("47.120.37.156");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");

        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("生产者");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 6: 准备发送消息的内容
            String message = "你好,fanout-exchange";
            String  exchangeName = "fanout-exchange";
            String routingKey = "";
            // 7: 发送消息给中间件rabbitmq-server
            // @params1: 交换机exchange
            // @params2: 队列名称/routingkey
            // @params3: 属性配置
            // @params4: 发送消息的内容
            channel.basicPublish(exchangeName, routingKey, null, message.getBytes());
            System.out.println("消息发送成功!");
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}
消费者
package com.syh.fanout;

import com.rabbitmq.client.*;

import java.io.IOException;

/**
 * @author shan
 * @date 2024/5/16 20:52
 */
public class Consumer {
    private static Runnable runnable = () -> {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("47.120.37.156");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        //获取队列的名称
        final String queueName = Thread.currentThread().getName();
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("生产者");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 5: 申明队列queue存储消息
            /*
             *  如果队列不存在,则会创建
             *  Rabbitmq不允许创建两个相同的队列名称,否则会报错。
             *
             *  @params1: queue 队列的名称
             *  @params2: durable 队列是否持久化
             *  @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
             *  @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
             *  @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
             * */
            // 这里如果queue已经被创建过一次了,可以不需要定义
            //channel.queueDeclare("queue1", false, false, false, null);
            // 6: 定义接受消息的回调
            Channel finalChannel = channel;
            finalChannel.basicConsume(queueName, true, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    System.out.println(queueName + ":收到消息是:" + new String(delivery.getBody(), "UTF-8"));
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                }
            });
            System.out.println(queueName + ":开始接受消息");
            System.in.read();
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    };
    public static void main(String[] args) {
        // 启动三个线程去执行
        new Thread(runnable, "queue1").start();
        new Thread(runnable, "queue2").start();
//        new Thread(runnable, "queue-3").start();
    }
}

六. 路由模式(Routing)

在这里插入图片描述

6.1 图形化界面理解

6.1.1 创建direct交换机

在这里插入图片描述

6.1.2 交换机与队列绑定

:要指定routingkey
在这里插入图片描述
在这里插入图片描述

6.1.3 模拟生产者发送消息

在这里插入图片描述

6.1.4 查看队列消息

在这里插入图片描述

6.2 发布订阅模式具体实现

  • 类型:direct
  • 特点:Direct模式是fanout模式上的一种叠加,增加了路由RoutingKey的模式。

6.2.1 生产者

package com.syh.direct;

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

/**
 * @author shan
 * @date 2024/5/16 21:10
 */
public class Product {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("47.120.37.156");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");

        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("生产者");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 6: 准备发送消息的内容
            String message = "你好,学相伴!!!";
            String exchangeName = "dicrect-exchange";
            String routingKey1 = "email";
            String routingKey2 = "sms";
            // 7: 发送消息给中间件rabbitmq-server
            // @params1: 交换机exchange
            // @params2: 队列名称/routingkey
            // @params3: 属性配置
            // @params4: 发送消息的内容
            channel.basicPublish(exchangeName, routingKey1, null, message.getBytes());
            channel.basicPublish(exchangeName, routingKey2, null, message.getBytes());
            System.out.println("消息发送成功!");
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

6.1.2 消费者

package com.syh.direct;

import com.rabbitmq.client.*;

import java.io.IOException;

/**
 * @author shan
 * @date 2024/5/16 21:14
 */
public class Consumer {
    private static Runnable runnable = () -> {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("47.120.37.156");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        //获取队列的名称
        final String queueName = Thread.currentThread().getName();
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("生产者");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 5: 申明队列queue存储消息
            /*
             *  如果队列不存在,则会创建
             *  Rabbitmq不允许创建两个相同的队列名称,否则会报错。
             *
             *  @params1: queue 队列的名称
             *  @params2: durable 队列是否持久化
             *  @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
             *  @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
             *  @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
             * */
            // 这里如果queue已经被创建过一次了,可以不需要定义
            //channel.queueDeclare("queue1", false, false, false, null);
            // 6: 定义接受消息的回调
            Channel finalChannel = channel;
            finalChannel.basicConsume(queueName, true, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    System.out.println(queueName + ":收到消息是:" + new String(delivery.getBody(), "UTF-8"));
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                }
            });
            System.out.println(queueName + ":开始接受消息");
            System.in.read();
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    };
    public static void main(String[] args) {
        // 启动三个线程去执行
        new Thread(runnable, "queue1").start();
        new Thread(runnable, "queue2").start();
        new Thread(runnable, "queue3").start();
    }
}

七. 主题模式(Topic)

在这里插入图片描述

7.1 图像界面理解

# 表示0个或多个值,并且是多级

* 表示一级任意值,必须有

7.2 主题模式具体实现

  • 类型:topic
  • 特点:Topic模式是direct模式上的一种叠加,增加了模糊路由RoutingKey的模式。

7.2.1 生产者

public class Producer {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("47.104.141.27");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("生产者");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 6: 准备发送消息的内容
            String message = "你好,学相伴!!!";
            String  exchangeName = "topic-exchange";
            String routingKey1 = "com.course.order";//都可以收到 queue-1 queue-2
            String routingKey2 = "com.order.user";//都可以收到 queue-1 queue-3
            // 7: 发送消息给中间件rabbitmq-server
            // @params1: 交换机exchange
            // @params2: 队列名称/routingkey
            // @params3: 属性配置
            // @params4: 发送消息的内容
            channel.basicPublish(exchangeName, routingKey1, null, message.getBytes());
            System.out.println("消息发送成功!");
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

7.2.2 消费者

public class Consumer {
    private static Runnable runnable = () -> {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("47.104.141.27");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        //获取队列的名称
        final String queueName = Thread.currentThread().getName();
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("生产者");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 5: 申明队列queue存储消息
            /*
             *  如果队列不存在,则会创建
             *  Rabbitmq不允许创建两个相同的队列名称,否则会报错。
             *
             *  @params1: queue 队列的名称
             *  @params2: durable 队列是否持久化
             *  @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
             *  @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
             *  @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
             * */
            // 这里如果queue已经被创建过一次了,可以不需要定义
            //channel.queueDeclare("queue1", false, false, false, null);
            // 6: 定义接受消息的回调
            Channel finalChannel = channel;
            finalChannel.basicConsume(queueName, true, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    System.out.println(queueName + ":收到消息是:" + new String(delivery.getBody(), "UTF-8"));
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                }
            });
            System.out.println(queueName + ":开始接受消息");
            System.in.read();
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    };
    public static void main(String[] args) {
        // 启动三个线程去执行
        new Thread(runnable, "queue-1").start();
        new Thread(runnable, "queue-2").start();
        new Thread(runnable, "queue-3").start();
    }
}

八. SpringBoot集成RabbitMQ

在本人这篇博客(点击超链接)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2315400.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux应用:进程的回收

进程的诞生和消亡 程的诞生通常是通过系统调用&#xff08;如fork、exec等&#xff09;来创建新进程。当一个进程完成其任务或者出现错误时&#xff0c;它会进入消亡阶段。进程可以通过exit函数主动结束自身&#xff0c;也可能由于操作系统的调度策略&#xff08;如资源耗尽、…

如何利用 AI 技术快速定位和修复生产环境问题

网罗开发 &#xff08;小红书、快手、视频号同名&#xff09; 大家好&#xff0c;我是 展菲&#xff0c;目前在上市企业从事人工智能项目研发管理工作&#xff0c;平时热衷于分享各种编程领域的软硬技能知识以及前沿技术&#xff0c;包括iOS、前端、Harmony OS、Java、Python等…

(链表)206. 反转链表

给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1]示例 2&#xff1a; 输入&#xff1a;head [1,2] 输出&#xff1a;[2,1]示例 3&#xff1a; 输入&am…

农业建设项目管理系统评测:8款推荐工具优缺点分析

本文主要介绍了以下8款农业建设项目管理系统&#xff1a;1.PingCode&#xff1b; 2. Worktile &#xff1b;3. 建米农业工程项目管理系统&#xff1b;4. 开创云数字农业管理平台&#xff1b; 5. Trimble Ag Software&#xff1b;6.Conservis&#xff1b; 7. Agworld &#xff1…

linux 命令 tail

tail 是 Linux 中用于查看文件末尾内容的命令&#xff0c;常用于日志监控和大文件快速浏览。以下是其核心用法及常见选项&#xff1a; 基本语法 tail [选项] 文件名 常用选项 显示末尾行数 -n <行数> 或 --lines<行数> 指定显示文件的最后若干行&#xff08;…

实验8 搜索技术

实验8 搜索技术 一、实验目的 &#xff08;1&#xff09;掌握搜索技术的相关理论&#xff0c;能根据实际情况选取合适的搜索方法&#xff1b; &#xff08;2&#xff09;进一步熟悉盲目搜索技术&#xff0c;掌握其在搜索过程中的优缺点&#xff1b; &#xff08;3&#xff09;…

VSTO(C#)Excel开发9:处理格式和字体

初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github&#xff1a;codetoys&#xff0c;所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的&#xff0c;可以在任何平台上使用。 源码指引&#xff1a;github源…

LinkedList底层结构和源码分析(JDK1.8)

参考视频&#xff1a;韩顺平Java集合 特点 LinkedList 底层实现了 双向链表 和 双端队列 的特点。可以添加任意元素&#xff08;元素可以重复&#xff09;&#xff0c;包括 null。线程不安全&#xff0c;没有实现同步。 LinkedList 底层结构 LinkedList 底层维护了一个双向链…

数字内容体验的技术支柱是什么?

数据分析引擎构建基础 数字内容体验的技术底座始于对海量用户行为数据的深度解析。作为技术体系的根基&#xff0c;数据分析引擎通过实时采集、清洗与结构化处理&#xff0c;将分散的点击轨迹、停留时长及交互偏好转化为可操作的洞察。其核心能力体现在三方面&#xff1a;一是…

C# 使用Markdown2Pdf把md文件转换为pdf文件

NuGet安装Markdown2Pdf库&#xff0c;可以把格式简单markdown文件转换为pdf。但该库用了Puppeteer Sharp&#xff0c;因此会在运行过程中提示指定Chrome浏览器路径或自动下载Chrome浏览器。 代码如下&#xff1a; using Markdown2Pdf;var converter new Markdown2PdfConverte…

专家系统如何运用谓词逻辑进行更复杂的推理

前文&#xff0c;我们讲解了命题逻辑和谓词逻辑的基本概念、推理规则、应用以及一些简单的示例。具体内容可以先看我的文章&#xff1a;人工智能的数学基础之命题逻辑与谓词逻辑&#xff08;含示例&#xff09;-CSDN博客 那么形如专家系统这类复杂系统&#xff0c;是如何通过谓…

html css网页制作成品——糖果屋网页设计(4页)附源码

目录 一、&#x1f468;‍&#x1f393;网站题目 二、✍️网站描述 三、&#x1f4da;网站介绍 四、&#x1f310;网站效果 五、&#x1fa93; 代码实现 &#x1f9f1;HTML 六、&#x1f947; 如何让学习不再盲目 七、&#x1f381;更多干货 一、&#x1f468;‍&#x1f…

落雪音乐Pro 8.8.6 | 内置8条音源,无需手动导入,纯净无广告

洛雪音乐Pro版内置多组稳定音源接口&#xff0c;省去手动导入的繁琐操作&#xff0c;安装即可畅听海量音乐。延续原版无广告的纯净体验&#xff0c;支持歌单推荐与音源切换&#xff0c;满足个性化听歌需求。此版本仅支持在线播放&#xff0c;无法下载音乐&#xff0c;且与原版不…

什么是全栈?

&#x1f91f;致敬读者 &#x1f7e9;感谢阅读&#x1f7e6;笑口常开&#x1f7ea;生日快乐⬛早点下班 &#x1f4d8;博主相关 &#x1f7e7;博主信息&#x1f7e8;博客首页&#x1f7eb;专栏推荐&#x1f7e5;活动信息 &#x1f4c3;文章前言 &#x1f537;文章均为学习工…

XML文件格式的简介及如何用Python3处理XML格式对象

诸神缄默不语-个人技术博文与视频目录 文章目录 1. XML格式简介2. 格式化XML文件的工具3. Python处理XML&#xff1a;xml库1. xml.etree.\(c\)ElementTree2. xml.dom.minidom 4. 本文撰写过程中参考的其他网络资料 1. XML格式简介 可扩展标记语言 (Extensible Markup Language…

通过qemu仿真树莓派系统调试IoT固件和程序

通过qemu仿真树莓派系统调试IoT固件和程序 本文将介绍如何使用 QEMU 模拟器在 x86 架构的主机上运行 Raspberry Pi OS&#xff08;树莓派操作系统&#xff09;。我们将从下载镜像、提取内核和设备树文件&#xff0c;到启动模拟环境&#xff0c;并进行一些常见的操作&#xff0…

Oracle底层原理解析

Oracle 解析 1、union \ union all \ Intersect \ Minus内部处理机制&#xff08;优化&#xff09; 当查询语句中的where子句中使用到or时&#xff0c;可以用union all来代替。因为使用or查询语句的时候&#xff0c;引起全表扫描&#xff0c;并走索引查询 特别&#xff1a;当…

深度解读DeepSeek部署使用安全(48页PPT)(文末有下载方式)

深度解读DeepSeek&#xff1a;部署、使用与安全 详细资料请看本解读文章的最后内容。 引言 DeepSeek作为一款先进的人工智能模型&#xff0c;其部署、使用与安全性是用户最为关注的三大核心问题。本文将从本地化部署、使用方法与技巧、以及安全性三个方面&#xff0c;对Deep…

【前端三剑客】万字总结JavaScript

一、初识JavaScript 1.1 JavaScript 的作用 表单动态校验&#xff08;密码强度检测&#xff09; &#xff08; JS 产生最初的目的 &#xff09;网页特效服务端开发(Node.js)桌面程序(Electron)App(Cordova)控制硬件-物联网(Ruff)游戏开发(cocos2d-js) 1.2 HTML/CSS/JS 的关系…

【哈希表与字符串的算法之路:思路与实现】—— LeetCode

文章目录 两数之和面试题01.02.判定是否为字符重排存在重复元素存在重复元素||字母异位词分组最长公共前缀和最长回文子串二进制求和字符串相乘 两数之和 这题的思路很简单&#xff0c;在读完题目之后&#xff0c;便可以想到暴力枚举&#xff0c;直接遍历整个数组两遍即可&…