Fork me on GitHub

Rabbitmq

Rabbitmq

官网:
https://rabbitmq.com/install-homebrew.html

安装

brew install rabbitmq
#查看版本信息,及安装位置
brew info rabbitmq
#配置环境变量
open -e ~/.zshrc
#添加如下内容
export PATH=/opt/homebrew/Cellar/rabbitmq/3.12.13/sbin:$PATH

image

rabbitmq命令

# 启动服务
sudo rabbitmq-server

# rabbitmq 后台启动命令
sudo rabbitmq-server -detached

# 关闭服务
sudo rabbitmqctl stop

# 查询rabbitmq的状态
sudo rabbitmqctl status

# 启动插件
sudo rabbitmq-plugins enable rabbitmq_management

# 查找rabbitmq在哪
find / -name 'rabbitmq' -type d

# rabbitmq内存限制
# 系数计算,假设机器内存32g,以下命令限制的最高内存为,32*0.06=1.92g
rabbitmqctl set_vm_memory_high_watermark 0.06

# rabbitmq添加消息日志记录
rabbitmq-plugins enable rabbitmq_tracing

# 查询rabbitmq的进程
ps -ef | grep rabbitmq

# 杀掉rabbitmq进程
ps -ef | grep rabbitmq | grep -v grep | awk '{print $2}' | xargs kill -9

登陆rabbitmq

成功启动服务后,使用以下链接访问主页,则会进入到登陆页面
访问rabbitmq主页
http://localhost:15672/
guest/guest
image

插件

rabbitmq_tracing

在管理界面中,查找 "Tracing" 部分,您可以在那里创建新的追踪并配置它们来追踪特定的交换器和队列的消息。

sudo rabbitmq-plugins enable rabbitmq_tracing

注意,持续的追踪可能会对性能产生影响,特别是在高吞吐量的环境中。因此,建议仅在调试或审计需求时启用追踪,并在不需要时将其禁用。

代码

maven坐标

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

生产者

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

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;

public class Producer {
    private final static String QUEUE_NAME = "queue_name";
    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setPort(5672);
        factory.setUsername("guest");
        factory.setPassword("guest");

        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);

            String message = "这是一条来自生产者的消息。";
            //发布消息basicPublish(交换机,路由关键字,其他参数,消息主体)
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
            System.out.println("生产者发送: '" + message + "'");
        }
    }
}

image
登陆查看rabbitmq数量
image
Virtual host: 虚拟主机,消息队列通常将不同的应用程序隔离到不同的虚拟主机中,这有助于安全性和管理。
Name: 名称,这列可能是虚拟主机中的队列名称。
Type: 类型,这是队列的类型,通常有“classic”、“direct”等。
Features: 功能,可能是队列的一些特性或配置。
State: 状态,显示队列的当前状态,可能有“idle”、“active”等。
Ready: 准备就绪,表示当前队列中可立即被消费的消息数量。
Unacked: 未应答,表示当前已经被消费者获取但还未确认的消息数量。
Total: 总数,表示队列中所有消息的总数量。
Incoming: 进入消息率,表示每秒进入队列的消息数量。
Deliver / Get: 交付/获取,表示每秒交付给消费者或获取的消息数量。
Ack: 确认,表示每秒消费者确认收到的消息数量。

消费者

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

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

public class Consumer {
    private final static String QUEUE_NAME = "queue_name";
    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setPort(5672);
        factory.setUsername("guest");
        factory.setPassword("guest");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        //必须设置队列名称
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println("等待接收消息");

        //RabbitMQ将会通过异步的方式推送消息,因此需要提供了一个回调
        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
            System.out.println(" 消费者 接收到消息: '" + message + "'");
        };
        //basicConsume用于接收消息参数(队列名,autoAck,回调函数,CancelCallback)
        channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
    }
}

image

springboot整合rabbitmq

更换Spring initializr的源为阿里云https://start.aliyun.com/
image
更阿里源换后可选择java8版本
image

maven坐标

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

配置文件类

一般需要创建一个公共项目common,共享一些配置,比如队列主题,交换机名称,路由匹配键名称等等。

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * rabbitmq配置类
 **/
@Configuration
public class DirectExchangeConfig {

    //队列1
    public static final String DIRECT_QUEUE = "directQueue";
    //队列2
    public static final String DIRECT_QUEUE2 = "directQueue2";
    //交换机,用来将路由和队列绑定,一个路由可以绑定多个队列,这样每次发送路由就会向多个队列发送消息
	//一个队列也可以绑定多个消费者,但是只有一个消费者能消费
    public static final String DIRECT_EXCHANGE = "directExchange";
    //路由,多个路由时
    public static final String DIRECT_ROUTING_KEY = "direct";

    //队列一
    @Bean
    public Queue directQueue() {
        return new Queue(DIRECT_QUEUE, true);
    }
    //队列二
    @Bean
    public Queue directQueue2() {
        return new Queue(DIRECT_QUEUE2, true);
    }
    //交换机
    @Bean
    public DirectExchange directExchange() {
        return new DirectExchange(DIRECT_EXCHANGE, true, false);
    }
    //将队列1和交换机绑定, 并设置路由
    @Bean
    public Binding bindingDirectExchange(Queue directQueue, DirectExchange directExchange) {
        return BindingBuilder.bind(directQueue).to(directExchange).with(DIRECT_ROUTING_KEY);
    }

    //将队列2和交换机绑定, 并设置路由
    @Bean
    public Binding bindingDirectExchange2(Queue directQueue2, DirectExchange directExchange) {
        return BindingBuilder.bind(directQueue2).to(directExchange).with(DIRECT_ROUTING_KEY);
    }

}

application.yml配置

spring:
    rabbitmq:
        host: 127.0.0.1
        port: 5672
        username: guest
        password: guest

监听器配置类(消费者)

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

/**
 * @className: DirectQueueListener
 * @description: 直连交换机的监听器
 */
@Component
public class DirectQueueListener {

    /**
     * 尽管队列1设置了两个消费者,但是只有一个能够消费成功
     * 多次发送则轮训消费:
     * DirectReceiver消费者收到消息1  : 发送一条测试消息:direct
     * DirectReceiver消费者收到消息3  : 发送一条测试消息:direct
     * DirectReceiver消费者收到消息2  : 发送一条测试消息:direct
     * DirectReceiver消费者收到消息3  : 发送一条测试消息:direct
     *
     * 一个交换机可以绑定多个队列。如果通过路由key可以匹配到多个队列,消费的时候也只能有一个进行消费
     * @param testMessage
     */
    @RabbitHandler
    @RabbitListener(queues = DirectExchangeConfig.DIRECT_QUEUE)
    public void process(String testMessage) {
        System.out.println("DirectReceiver消费者收到消息1  : " + testMessage);
    }

    @RabbitHandler
    @RabbitListener(queues = DirectExchangeConfig.DIRECT_QUEUE)
    public void process2(String testMessage) {
        System.out.println("DirectReceiver消费者收到消息2  : " + testMessage);
    }

    /**
     * 处理消息后发送确认消息(确认后消息会从mq中移除)
     * @param message
     * @param channel
     */
    @RabbitHandler
    @RabbitListener(queues = DirectExchangeConfig.DIRECT_QUEUE2)
    public void process3(Message message, Channel channel) {
        try {
            String messageBody = new String(message.getBody(), "UTF-8");
            System.out.println("DirectReceiver消费者3收到消息  : " + messageBody);
            // 发送确认
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

}

使用rabbitmq(生产者)

@Controller
public class RabbitMqController {


    @Autowired
    private RabbitTemplate rabbitTemplate;
    // http://127.0.0.1:8080/hellorabbitmq?msg=向rabbitmq发送的消息
    @RequestMapping("/hellorabbitmq")
    @ResponseBody
    public String hello(String msg) {
		//交换机,路由,消息体,然后加入到对应路由下面的队列
        rabbitTemplate.convertAndSend(DirectExchangeConfig.DIRECT_EXCHANGE, DirectExchangeConfig.DIRECT_ROUTING_KEY, "发送一条测试消息:direct");
        return "客户端发送的消息:  " + msg;
    }

}

image

消息队列

消息指的是两个应用间传递的数据。数据的类型有很多种形式,可能只包含文本字符串,也可能包含嵌入对象。

“消息队列(Message Queue)”是在消息的传输过程中保存消息的容器。在消息队列中,通常有生产者和消费者两个角色。生产者只负责发送数据到消息队列,谁从消息队列中取出数据处理,他不管。消费者只负责从消息队列中取出数据处理,他不管这是谁发送的数据。
image

消息队列作用

解耦。假设有系统B、C、D都需要系统A的数据,于是系统A调用三个方法发送数据到B、C、D。这时,系统D不需要了,那就需要在系统A把相关的代码删掉。假设这时有个新的系统E需要数据,这时系统A又要增加调用系统E的代码。为了降低这种强耦合,就可以使用MQ,系统A只需要把数据发送到MQ,其他系统如果需要数据,则从MQ中获取即可。
image

异步。一个客户端请求发送进来,系统A会调用系统B、C、D三个系统,同步请求的话,响应时间就是系统A、B、C、D的总和,也就是800ms。如果使用MQ,系统A发送数据到MQ,然后就可以返回响应给客户端,不需要再等待系统B、C、D的响应,可以大大地提高性能。对于一些非必要的业务,比如发送短信,发送邮件等等,就可以采用MQ。
image

削峰。这其实是MQ一个很重要的应用。假设系统A在某一段时间请求数暴增,有5000个请求发送过来,系统A这时就会发送5000条SQL进入MySQL进行执行,MySQL对于如此庞大的请求当然处理不过来,MySQL就会崩溃,导致系统瘫痪。如果使用MQ,系统A不再是直接发送SQL到数据库,而是把数据发送到MQ,MQ短时间积压数据是可以接受的,然后由消费者每次拉取2000条进行处理,防止在请求峰值时期大量的请求直接发送到MySQL导致系统崩溃。
image

RabbitMQ的特点

RabbitMQ是一款使用Erlang语言开发的,实现AMQP(高级消息队列协议)的开源消息中间件。首先要知道一些RabbitMQ的特点,官网可查:

可靠性。支持持久化,传输确认,发布确认等保证了MQ的可靠性。
灵活的分发消息策略。这应该是RabbitMQ的一大特点。在消息进入MQ前由Exchange(交换机)进行路由消息。分发消息策略有:简单模式、工作队列模式、发布订阅模式、路由模式、通配符模式。
支持集群。多台RabbitMQ服务器可以组成一个集群,形成一个逻辑Broker。
多种协议。RabbitMQ支持多种消息队列协议,比如 STOMP、MQTT 等等。
支持多种语言客户端。RabbitMQ几乎支持所有常用编程语言,包括 Java、.NET、Ruby 等等。
可视化管理界面。RabbitMQ提供了一个易用的用户界面,使得用户可以监控和管理消息 Broker。
插件机制。RabbitMQ提供了许多插件,可以通过插件进行扩展,也可以编写自己的插件。

RabbitMQ中的组成部分

Broker:消息队列服务进程。此进程包括两个部分:Exchange和Queue。
Exchange:消息队列交换机。按一定的规则将消息路由转发到某个队列。
Queue:消息队列,存储消息的队列。
Producer:消息生产者。生产方客户端将消息同交换机路由发送到队列中。
Consumer:消息消费者。消费队列中存储的消息。
image

消息生产者连接到RabbitMQ Broker,创建connection,开启channel。
生产者声明交换机类型、名称、是否持久化等。
生产者发送消息,并指定消息是否持久化等属性和routing key。
exchange收到消息之后,根据routing key路由到跟当前交换机绑定的相匹配的队列里面。
消费者监听接收到消息之后开始业务处理。

死信队列

RabbitMQ 中,消息可能会进入死信队列(DLQ,Dead Letter Queue)的情况主要包括以下几种:

  1. 消息被拒绝(Nack):当消费者通过 basic.reject 或 basic.nack 显式拒绝一条消息,并且拒绝时没有设置重入队列(requeue)标志为 true 时,消息就会被发送到死信队列,前提是该队列配置了死信交换器(DLX)。

  2. 消息过期:如果一条消息在队列中停留的时间超过了它的 TTL(Time-To-Live,存活时间),这条消息就会过期并被移动到死信队列,同样,这需要队列被配置了 DLX。

  3. 队列长度限制:如果一个队列被设置了最大长度(或最大占用空间),当新消息到达导致队列超出这个限制时,队列头部的消息(即最早的那些消息)会被移动到死信队列。

要使用死信队列,你需要为你的队列配置死信交换器(DLX)和可选的死信路由键(DLK)。这可以在队列声明时通过 arguments 参数来设置。例如,在 RabbitMQ 的管理界面或通过 AMQP 客户端代码声明队列时,可以这样配置:

    @Bean
    public Queue directQueue() {
        /**
         * 绑定死信交换机及路由key
         */
        Map<String, Object> args = new HashMap<>();
        // x-dead-letter-exchange:这里声明当前业务队列绑定的死信交换机
        //消息被拒绝、消息过期,或者队列达到其最大长度。消息会变成死信
        args.put("x-dead-letter-exchange", DEAD_TCP_DATA_DIRECT_EXCHANGE);
        // x-dead-letter-routing-key:这里声明当前业务队列的死信路由 key
        args.put("x-dead-letter-routing-key", DEAD_TCP_DATA_DIRECT_ROUTING);
        return QueueBuilder.durable(DIRECT_QUEUE).withArguments(args).build();
    }
posted @ 2024-02-20 22:04  秋夜雨巷  阅读(11)  评论(0编辑  收藏  举报