RabbitMQ学习笔记

本文详细介绍了RabbitMQ的安装过程,包括启动服务、启用web管理插件以及创建用户。接着,展示了如何创建生产者和消费者,包括消息的发送与接收、手动应答、不公平分发、预取值分发、发布确认等机制。此外,还讲解了交换机类型(fanout、direct、topic)、死信队列以及SpringBoot整合RabbitMQ的实践。最后,提到了延时队列的实现及优化,并提供了发布确认的高级用法。
摘要由CSDN通过智能技术生成

RabbitMQ

准备

安装

rpm -ivh erlang-22.3.4.21-1.el7.x86_64.rpm 
yum install socat -y
rpm -ivh rabbitmq-server-3.8.11-1.el7.noarch.rpm

启动

systemctl start rabbitmq-server.service

开启web管理插件

rabbitmq-plugins enable rabbitmq_management

创建新的用户

# 创建账户
rabbitmqctl add_user admin 123456
# 设置用户角色
rabbitmqctl set_user_tags admin administrator
# 设置用户权限
set_permissions [-p <vhostpath>] <user> <conf> <write> <read>
rabbitmqctl set_permissions -p "/" admin ".*" ".*" ".*"
# 查看用户列表
rabbitmqctl list_users

消费流程

生产消费

依赖:

<dependencies>
    <!--rabbitmq依赖客户端-->
    <dependency>
        <groupId>com.rabbitmq</groupId>
        <artifactId>amqp-client</artifactId>
        <version>5.9.0</version>
    </dependency>
    <!--操作文件流的一个依赖-->
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.6</version>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-clean-plugin</artifactId>
            <version>3.1.0</version>
        </plugin>
    </plugins>
</build>

生产者:

public class Producer {
    //队列名称
    public static final String QUEUE_NAME = "hello";

    //发消息
    public static void main(String[] args) throws IOException, TimeoutException {
        //创建连接工厂
        ConnectionFactory factory=new ConnectionFactory();
        //工厂ip 连接RabbitMQ的队列
        factory.setHost("192.168.168.130");
        factory.setUsername("admin");
        factory.setPassword("123456");
        //创建连接
        Connection connection=factory.newConnection();
        //获取信道
        Channel channel = connection.createChannel();
        /**
         * 声明一个队列
         * 1.队列名称
         * 2.队列里面的消息是否持久化(磁盘),默认存储在内存
         * 3.该队列是否供只供个消费者消费,是否不进行消息共享,默认为false
         * 4.是否自动删除,最后一个消费者断开连接,该队列是否删除
         * 5.其他参数,map对象,可以设置队列最大长度,或者死信等等,详见死信
         */
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        //发消息
        String message = "hello world";
        /**
         * 发送一个消息
         * 1.发送到哪个交换机,""表示无名交换机
         * 2.路由的key值,本次是队列名称
         * 3.其他参数信息,ps:MessageProperties.PERSISTENT_TEXT_PLAIN(消息持久化)
         * 4.发送消息的消息体
         */
        channel.basicPublish("",QUEUE_NAME,null,message.getBytes(StandardCharsets.UTF_8));
        System.out.println("消息发送完毕");
    }
}

消费者:

public class Consumer {
    //队列的名称
    public static final String QUEUE_NAME = "hello";
    //接受消息
    public static void main(String[] args) throws IOException, TimeoutException {
        //创建连接工厂
        ConnectionFactory factory=new ConnectionFactory();
        factory.setHost("192.168.168.130");
        factory.setUsername("admin");
        factory.setPassword("123456");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        
        /**
         * 消费者消费消息
         * 1.消费哪个队列
         * 2.消费成功后是否自动应答
         * 3.消费者未成功消费的回调
         * 4.消费者取消消费的回调
         */
        channel.basicConsume(AUTO_QUEUE_NAME,false,(consumerTag,message)->{
            SleepUtils.sleep(1);
            System.out.println("接收到消息:"+new String(message.getBody(), StandardCharsets.UTF_8));

        },consumerTag->{
            System.out.println(consumerTag+"消费者取消消费接口回调逻辑");
        });
    }
}

手动应答

//第二个false表示不自动应答,手动应答
channel.basicConsume(AUTO_QUEUE_NAME,false,(consumerTag,message)->{
    
            //手动应答,应答后才说明消息利用成功,否则消息还会回到队列,继续被应用,false表示不批量应答
            channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
            //拒绝这条消息,false表示不再放回队列
            channel.basicReject(message.getEnvelope().getDeliveryTag(),false);
    
        },consumerTag->{
            System.out.println(consumerTag+"消费者取消消费接口回调逻辑");
        });

不公平分发

//设置为不公平分发,由消费者设置,设置为1就表示不公平分发
channel.basicQos(1);

预取值分发

//设置prefetch的值
int prefetchCount=3;
channel.basicQos(prefetchCount);

发布确认

  1. 单个确认
//开启发布确认
channel.confirmSelect();
channel.basicPublish("",QUEUE_NAME,MessageProperties.PERSISTENT_TEXT_PLAIN,
                     message.getBytes(StandardCharsets.UTF_8));
//true表示确认发布成功
int flag=channel.waitForConfirms();
  1. 批量确认
//开启发布确认
channel.confirmSelect();
//批量确认消息大小
int batchSize=100;
for(int i=0;i<1000;i++){
    String message=i+"";
    channel.basicPublish("",QUEUE_NAME,MessageProperties.PERSISTENT_TEXT_PLAIN,
                     message.getBytes(StandardCharsets.UTF_8));
    if((i+1)%batchSize==0){
        channel.waitForConfirms();
    }
}
  1. 异步批量确认

channel.addConfirmListener((deliveryTag,message)->{
            //发布成功
    		
        },(deliveryTag,multiple)->{
            //发布失败
            
        });

for (int i = 0; i < 1000; i++) {
    String message="消息"+i;
    channel.basicPublish("",queueName,MessageProperties.PERSISTENT_TEXT_PLAIN,
                         message.getBytes(StandardCharsets.UTF_8));
};

交换机

fanout交换机

// 消费者
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
channel.queueDeclare(queueName,true,false,false,null);
channel.queueBind(queueName,EXCHANGE_NAME,"");
channel.queueBind(queueName,EXCHANGE_NAME,"");
channel.basicConsume(queueName,true,()->{},()->{});
// 生产者
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
channel.queueDeclare(queueName,true,false,false,null);
channel.basicPublish(EXCHANGE_NAME,"",null,message);

direct交换机

// 消费者
channel.exchangeDeclare(EXCHANGE_NAME,"direct");
channel.queueDeclare(queueName,true,false,false,null);
channel.queueBind(queueName,EXCHANGE_NAME,"aaa");
channel.queueBind(queueName,EXCHANGE_NAME,"bbb");
channel.basicConsume(queueName,true,()->{},()->{});

// 生产者
channel.exchangeDeclare(EXCHANGE_NAME,"direct");
channel.queueDeclare(queueName,true,false,false,null);
channel.basicPublish(EXCHANGE_NAME,"aaa",null,message);

topic交换机

// 消费者
channel.exchangeDeclare(EXCHANGE_NAME,"topic");
channel.queueDeclare(queueName,true,false,false,null);
channel.queueBind(queueName,EXCHANGE_NAME,"www.*.com");
channel.queueBind(queueName,EXCHANGE_NAME,"www.#");
channel.basicConsume(queueName,true,()->{},()->{});

// 生产者
channel.exchangeDeclare(EXCHANGE_NAME,"topic");
channel.queueDeclare(queueName,true,false,false,null);
channel.basicPublish(EXCHANGE_NAME,"www.liucheng.com",null,message);

死信

来源:

  1. 消息TTL过期
  2. 队列达到最大长度
  3. 消息被拒绝(basicReject||basicNack||requeue=false)
// 消费者

Map<String,Object> map=new HashMap<>();
map.put("x-message-ttl",1000);	//过期时间ms,一般是发送的时候指定,大多不写在这
map.put("x-dead-letter-exchange",DEAD_EXCHANGE);	//设置死信交换机
map.put("x-dead-letter-routing-key","lisi");	//设置死信routingkey
map.put("x-max-length",6);	//设置队列的最大长度
channel.exchangeDeclare(NORMAL_EXCHANGE,"direct");
channel.exchangeDeclare(DEAD_EXCHANGE,"direct");

channel.queueDeclare(normal_queue,true,false,false,map);
channel.queueDeclare(dead_queue,true,false,false,null);

channel.queueBind(normal_queue,NORMAL_EXCHANGE,"zhangsan");
channel.queueBind(dead_queue,DEAD_EXCHANGE,"lisi");
channel.basicConsume(NORMAL_QUEUE,true,()->{},()->{});

// 生产者

//过期时间ms
AMQP.BasicProperties properties=new AMQP.BasicProperties().builder().expiration("10000").build();
channel.basicPublish(NORMAL_EXCHANGE,"zhangsan",properties,message);

SpringBoot整合

依赖:

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

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>2.0.1</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.22</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.9.2</version>
</dependency>
<dependency>
    <groupId>org.springframework.amqp</groupId>
    <artifactId>spring-rabbit-test</artifactId>
    <scope>test</scope>
</dependency>

交换机与队列声明绑定

@Configuration
public class TTLQueueConfig {
    public static final String NORMAL_EXCHANGE = "X";
    public static final String DEAD_EXCHANGE = "Y";
    public static final String NORMAL_QUEUE_A = "QA";
    public static final String DEAD_QUEUE = "QD";

    @Bean("normalExchange")
    public DirectExchange normalExchange(){
        return new DirectExchange(NORMAL_EXCHANGE);
    }
    @Bean("deadExchange")
    public DirectExchange deadExchange(){
        return new DirectExchange(DEAD_EXCHANGE);
    }
    
    
    @Bean("normalQueueA")
    public Queue normalQueueA(){
        Map<String,Object> map=new HashMap<>();
        map.put("x-dead-letter-exchange",DEAD_EXCHANGE);
        map.put("x-dead-letter-routing-key","YD");
        map.put("x-message-ttl",10000);
        return QueueBuilder.durable(NORMAL_QUEUE_A).withArguments(map).build();
    }

    @Bean("deadQueue")
    public Queue deadQueue(){
        return QueueBuilder.durable(DEAD_QUEUE).build();
    }

    
    @Bean
    public Binding ABindingNormal(@Qualifier("normalQueueA") Queue normalQueueA,
                                  @Qualifier("normalExchange") DirectExchange normalExchange){
        return BindingBuilder.bind(normalQueueA).to(normalExchange).with("XA");
    }
    @Bean
    public Binding DBindingDead(@Qualifier("deadQueue") Queue deadQueue,
                                  @Qualifier("deadExchange") DirectExchange deadExchange){
        return BindingBuilder.bind(deadQueue).to(deadExchange).with("YD");
    }

}

生产者

@Slf4j
@RestController
@RequestMapping("/ttl")
public class SendMsgController {
    private final RabbitTemplate rabbitTemplate;
    @Autowired
    public SendMsgController(RabbitTemplate rabbitTemplate) {
        this.rabbitTemplate = rabbitTemplate;
    }

    @GetMapping("/sendMsg/{message}")
    public void sendMsg(@PathVariable String message){
        log.info("当前时间:{},发送一条信息给两个TTL队列:{}",new Date().toString(),message);
        rabbitTemplate.convertAndSend("X","XA","消息来自ttl为10s的队列:"+message);
        rabbitTemplate.convertAndSend("X","XB","消息来自ttl为40s的队列:"+message);
    }
    @GetMapping("/sendMsg/{message}/{ttlTime}")
    public void sendMsg(@PathVariable String message,@PathVariable String ttlTime){
        log.info("当前时间:{},发送一条信息给一个{}毫秒TTL队列:{}",new Date().toString(),ttlTime,message);
        rabbitTemplate.convertAndSend("X","XC","消息来自自定义时间的队列"+message,(msg)->{
            msg.getMessageProperties().setExpiration(ttlTime);
            return msg;
        });
    }
}

消费者

@Slf4j
@Component
public class DeadLetterQueueConsumer {
    @RabbitListener(queues = "QD")
    public void receiveD(Message message, Channel channel){
        String s = new String(message.getBody());
        log.info("当前时间:{},收到死信队列的消息:{}",new Date().toString(),s);
    }
}

延时队列问题

  1. Community Plugins — RabbitMQ下载插件
  2. 进入RabbitMQ安装目录下的plgins目录/usr/lib/rabbitmq/lib/rabbitmq_server-3.8.11/plugins
  3. 把插件移到上述目录下
  4. 输入命令rabbitmq-plugins enable rabbitmq_delayed_message_exchange
优化后的延时队列
public static final String DELAYED_QUEUE_NAME="delayed.queue";
public static final String DELAYED_EXCHANGE_NAME="delayed.exchange";
public static final String DELAYED_ROUTING_KEY="routing.key";
@Bean
public CustomExchange delayedExchange(){
    Map<String,Object> map=new HashMap<>();
    map.put("x-delayed-type","direct");
    return new CustomExchange(DELAYED_EXCHANGE_NAME,"x-delayed-message",true,false,map);
}
@Bean
public Queue delayedQueue(){
    return QueueBuilder.durable(DELAYED_QUEUE_NAME).build();
}

@Bean
public Binding delayedQueueBindingExchange(@Qualifier("delayedQueue") Queue delayedQueue,
                                           @Qualifier("delayedExchange") Exchange delayedExchange){
    return BindingBuilder.bind(delayedQueue).to(delayedExchange).with(DELAYED_ROUTING_KEY).noargs();
}


//生产者
@GetMapping("/sendDelayedMsg/{message}/{ttlTime}")
public void sendDelayedMsg(@PathVariable String message,@PathVariable Integer ttlTime){
    log.info("当前时间:{},发送一条信息给一个{}毫秒延时TTL队列:{}",new Date().toString(),ttlTime,message);
    rabbitTemplate.convertAndSend("delayed.exchange","routing.key","消息来自延时队列"+message,(msg)->{
        msg.getMessageProperties().setDelay(ttlTime);
        return msg;
    });
}
//消费者
@Slf4j
@Component
public class DeadLetterQueueConsumer {
    @RabbitListener(queues = "QD")
    public void receiveD(Message message, Channel channel){
        String s = new String(message.getBody());
        log.info("当前时间:{},收到死信队列的消息:{}",new Date().toString(),s);
    }
}

发布确认高级

<!--springboot配置文件中加入,开启异步确认消息-->
spring.rabbitmq.publisher-confirm-type=correlated
@Component
@Slf4j
public class MyCallBack implements RabbitTemplate.ConfirmCallback {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostConstruct
    public void init(){
        rabbitTemplate.setConfirmCallback(this);
    }
    /**
     * 回调函数
     * @param correlationData   保存回调信息的ID和其他信息,由发送消息的时候指定的对象
     * @param ack   是否成功收到消息
     * @param cause 失败的原因
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        String id=correlationData!=null?correlationData.getId():"";
        if(ack){
            log.info("交换机已经收到了id为{}的消息",id);
        }else{
            log.info("交换机没有收到了id为{}的消息,原因是{}",id,cause);
        }
    }
}
队列回退消息
  1. Mandatory参数

在交换机收到消息后,会直接给消息生产者发送确认消息,如果发现该消息不可路由,那么这条消息会直接丢弃,此时生产者是不知道这条消息被丢弃的。

所以我们需要交换机在这个时候通知生产者一声,所以我们可以通过设置mandatory参数可以在当消息传递过程中不可达目的地是讲消息返回给生产者。

<!--springboot配置文件中加入-->
spring.rabbitmq.publisher-returns=true
//同上,实现RabbitTemplate.ReturnCallback接口并实现方法
@Override
public void returnedMessage(Message message, int replyCode, 
                            String replyText, String exchange, String routingKey) {

}

备份交换机

//把主交换机连接备份交换机
ExchangeBuilder.directExchange("exchange_name").durable(true).
    withArgument("alternate-exchange","backup_exchange_name").build();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值