RabbitMQ 使用

核心概念

Message 消息,消息是不具名的,它由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成, 这些属性包括routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可 能需要持久性存储)等。 Publisher 消息的生产者,也是一个向交换器发布消息的客户端应用程序。

Exchange 交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列。

Exchange有4种类型:皆可绑定多个队列

​ direct(默认)【点对点直连,和队列绑定时配置的键 和 消息头的键一样时,发送消息到指定队列】

​ fanout【扇形,不用管配置的键 或者 消息的键 是什么,直接把消息发給所有绑定的队列】

​ topic【主题,配置的键可以加 # 或者 * 前者表示匹配0~很多值 后者匹配一个 若消息的键符合配置键的规则,可以发送给队列

​ headers【低效,和direct差不多,不用】

Queue 消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直 在队列里面,等待消费者连接到这个队列将其取走。

Binding 绑定,用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交 换器理解成一个由绑定构成的路由表。 Exchange 和Queue的绑定可以是多对多的关系。

Connection 网络连接,比如一个TCP连接。 【一个客户端只会建立一条连接!!!】

Channel 信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的TCP连接内的虚拟连接,AMQP 命令都是通过信道 发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。因为对于操作系统来说建立和销毁 TCP 都 是非常昂贵的开销,所以引入了信道的概念,以复用一条 TCP 连接。

Consumer 消息的消费者,表示一个从消息队列中取得消息的客户端应用程序。

Virtual Host 虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个 vhost 本质上就是一个 mini 版的 RabbitMQ 服务器,拥有自己的队列、交换器、绑定和权限机制。vhost 是 AMQP 概念的基础,必须在连接时指定,RabbitMQ 默认的 vhost 是 /

Broker 表示消息队列服务器实体

4369, 25672 (Erlang发现&集群端口)5672, 5671 (AMQP端口)15672 (web管理后台端口)61613, 61614 (STOMP协议端口)1883, 8883 (MQTT协议端口)

https://www.rabbitmq.com/networking.html

访问地址:http://192.168.190.150:15672/

安装

直接运行下面 第一次启动会下载镜像
docker run -d --name rabbitmq -p 5671:5671 -p 5672:5672 -p 4369:4369 -p 25672:25672 -p 15671:15671 -p 15672:15672 rabbitmq:management docker update rabbitmq --restart=always

配置

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


配置
spring.rabbitmq.host=192.168.190.150
spring.rabbitmq.port=5672
spring.rabbitmq.virtual-host=/


启动类加注解
@EnableRabbit


配置类【创建消息时,不使用jdk默认序列化器,使用json转换器】
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyRabbitConfig {

    @Bean
    public MessageConverter messageConverter(){
        return new Jackson2JsonMessageConverter();
    }

}

使用

自动注入

// 给rabbitmq里面加交换机,队列,绑定,,,
@Autowired
AmqpAdmin amqpAdmin;

// 给rabbitmq发送接收消息
@Autowired
RabbitTemplate rabbitTemplate;

创建 交换机

void creatChange() {
    // 对象:创建 direct类型交换机
    // public DirectExchange(String name, boolean durable, boolean autoDelete)
    // 参数含义:交换机名,是否持久化(关闭后打开rabbitmq有值),是否自动删除
    DirectExchange directExchange = new DirectExchange("hello.javaexchange", true, false);
    amqpAdmin.declareExchange(directExchange);
}

创建队列

void creatQueue(){
    // public Queue(String name, boolean durable, boolean exclusive, boolean autoDelete, @Nullable Map<String, Object> arguments)
    // 参数含义:队列名,是否持久化,是否排他(只能同时连一个队列),是否自动删除,指定一些参数(暂时不用)
    Queue queue = new Queue("hello.javaqueue",true,false,false);
    amqpAdmin.declareQueue(queue);
}

创建绑定

void creadBinding(){
    // public Binding(String destination, Binding.DestinationType destinationType, String exchange, String routingKey, @Nullable Map<String, Object> arguments)
    // 参数含义:目的地,目的地类型[绑定交换机 或者 队列],交换机名,路由键,自定义参数
    Binding binding = new Binding("hello.javaqueue", Binding.DestinationType.QUEUE,
            "hello.javaexchange","hello.java",null);
    amqpAdmin.declareBinding(binding);
}

发送消息

void sendMessage(){
    // 交换机名,路由键,要发送的对象(要实现Serializable接口)
    rabbitTemplate.convertAndSend("hello.javaexchange","hello.java","嘿嘿嘿,喜欢小可爱");
}

接收消息

/**

*** 监听消息:当监听的队列有消息时,会自动启动这个方法执行!!!【视频中的这个方法放在了service层,可以自动执行】**


*** 方法可以写的参数:**

*** 1.Message message 获取到完整的消息 头+体**

*** 2.发送消息时在消息中放入的类型 content 获取到发送消息的对象**

*** 3.Channel channel:当时传输数据的通道**


*** 注:**

*** 1.多个客户端监听同一个消息,只能有一个客户端获取到**

*** 2.只有一个消息处理完,才会接收另一个消息**

*** 3.接收消息可以在 类上配@RabbitListener,方法上配@RabbitHandler**

*** 或者 直接在方法上配@RabbitListener**

*** 【推荐第一种,因为队列里面的消息可能放有多种类型,每个方法使用 @RabbitHandler区分(只有一种类型不用加),方法使用不同的参数,方便获取】**

*/

方法一:  【程序会报错,但是不影响使用】
接口:【不用标什么注解】
public interface ELService {

    boolean deleteProduct(Message message,
                          List<String> productIds,
                          Channel channel);
}

实现类:
@Service
@RabbitListener(queues = {"maimai.product.esqueue"})
public class ELServiceImpl implements ELService {
   
// 直接标 @RabbitHandler 就行,它是根据 方法参数的 @Payload List<String> productIds 来判断由哪个方法处理
    @RabbitHandler
    @Override
public boolean deleteProduct(Message message,
                             @Payload List<String> productIds,   // 消息中的对象
                             Channel channel) {


    System.out.println ("list: "+productIds.toString ());


    // 收货,确定收到消息(参数含义:每个消息的标签(按顺序递增,其实就是客户端接收到的第几个消息),是否批量确定)
    try {
        channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
    } catch (IOException e) {
        e.printStackTrace ();
    }
           
    // 省略后面代码
}
}



方法二:
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;

@RabbitListener(queues = {"hello.javaqueue"})
public void recieveMessage(Message message,
                           String content,
                           Channel channel){
    // 获得请求体
    byte[] body = message.getBody();
    // 获得消息头属性
    MessageProperties messageProperties = message.getMessageProperties();
    System.out.println("接收到的消息:"+message);
    System.out.println("接收到的对象:"+content);
    System.out.println("传输数据的通道:"+channel);
}

/**

*** 定制RabbitTemplate**

*** 1、服务收到消息就会回调**

*** 1、spring.rabbitmq.publisher-confirms: true**

*** 2、设置确认回调**

*** 2、消息正确抵达队列就会进行回调**

*** 1、spring.rabbitmq.publisher-returns: true**

*** spring.rabbitmq.template.mandatory: true**

*** 2、设置确认回调ReturnCallback**


*** 3、消费端确认(保证每个消息都被正确消费,此时才可以broker删除这个消息)**

*** 1、默认是自动确认的,只要消息接收到,客户端会自动确认,服务端就会移除这个消息**

*** 问题:**

*** 我们收到很多消息,自动回复给服务器ck,只有一个消息处理成功,宕机了。发生消息丢失:**

*** 手动确认模式。只要我们设有明确告诉Mg,货物被签收。没有Ack,消息就一直是unackee状态。**

*** 即使Consumer宕机,消息不会消失,会重新变为ready,下一次有新的consumer进来就发給它**

*** 2、如何签收:**

*** channel,basicAck(deliveryTag,false);签收;业务成功完成就应该签收**

*** channel.basicNack(deliveryTag,false,true);拒签;业务失败,拒签**

*/

开启confirmCallback,returnsCallback,ack

他们都需要在配置文件中配置,前两个要在配置类中配置。ack需要客户端手动发送

配置文件

# 开启发送端到rabbit的确认回调
spring.rabbitmq.publisher-confirm-type=correlated
# 旧的配置,已弃用【是否启用发送端的确认模式,队列接收到消息时的回调】
#spring.rabbitmq.publisher-confirm=true


# 开启rabbit到队列的确认回调
spring.rabbitmq.publisher-returns=true


# 设置客户端接收到消息的回调【默认是自动ack,这样会发生客户端待机而队列以为客户端已全部接收删除消息的情况】
# 设置为手动确认,当客户端没有回复rabbit,消息不会被删除
spring.rabbitmq.listener.simple.acknowledge-mode=manual

配置类

import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class MyRabbitConfig {

    @Autowired
    RabbitTemplate rabbitTemplate;

    @Bean
    public MessageConverter messageConverter(){
        return new Jackson2JsonMessageConverter();
    }

    /**
     * 定制RabbitTemplate
    /
    public void initRabbitTemplate(){
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {

            /**
             * 设置发送端到rabbit的确认回调
             *      消息到了rabbit触发
             * @param correlationData 消息的id
             * @param b 是否成功收到消息
             * @param s 失败的原因
             */
            @Override
            public void confirm(CorrelationData correlationData, boolean b, String s) {
                System.out.println("confirm...correlationData["+correlationData+"]==>ack:["+b+"]==>cause:["+s+"]");
            }
        });


        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            /**
             * 设置rabbit到队列的确认回调
             *      消息没到队列触发!!!
             * @param message 投递失败的消息
             * @param i 回复的状态码
             * @param s 回复的文本内容
             * @param s1 这个消息发給哪个交换机
             * @param s2 这个消息用哪个路由键
             */
            @Override
            public void returnedMessage(Message message, int i, String s, String s1, String s2) {
                System.out.println("Fail Message["+message+"]==>replyCode["+i+"]" +
                        "==>replyText["+s+"]==>exchange["+s1+"]==>routingKey["+s2+"]");
            }
        });
    }
}

客户端

  • // 收货,确定收到消息(参数含义:每个消息的标签(按顺序递增,其实就是客户端接收到的第几个消息),是否批量确定)

channel.basicAck(deliveryTag,false);

  • // 退货,拒绝接收消息
  • // 参数含义:每个消息的标签,是否批量处理,是否重新把消息给rabbit(如果为false,丢弃消息)(如果为true,消息入队后还会继续传给客户端,就是传给这个方法)
  • channel.basicNack(deliveryTag,false,true);
  • // 和上一个的区别是没有批量的选项
  • channel.basicReject(deliveryTag,true);
@RabbitListener(queues = {"hello.javaqueue"})
public void recieveMessage(Message message,
                           String content,
                           Channel channel){
    // 获得请求体
    byte[] body = message.getBody();
    // 获得消息头属性
    MessageProperties messageProperties = message.getMessageProperties();
    System.out.println("接收到的消息:"+message);
    System.out.println("接收到的对象:"+content);
    System.out.println("传输数据的通道:"+channel);


    try {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        // 收货,确定收到消息(参数含义:每个消息的标签(按顺序递增,其实就是客户端接收到的第几个消息),是否批量确定)
        channel.basicAck(deliveryTag,false);
        // 退货,拒绝接收消息
        // 参数含义:每个消息的标签,是否批量处理,是否重新把消息给rabbit(如果为false,丢弃消息)(如果为true,消息入队后还会继续传给客户端,就是传给这个方法)
        channel.basicNack(deliveryTag,false,true);
        // 和上一个的区别是没有批量的选项
        channel.basicReject(deliveryTag,true);
    } catch (IOException e) {
        e.printStackTrace();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

沛权

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

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

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

打赏作者

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

抵扣说明:

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

余额充值