RabbitMQ基本使用

一、安装

1.Ubuntu18.04在线安装
sudo apt update

# 安装RabbitMQ服务器软件包
sudo apt install rabbitmq-server

# 创建管理员用户
sudo rabbitmqctl add_user admin 123456

# 授予管理员用户的访问权限
sudo rabbitmqctl set_user_tags admin administrator

# 启用RabbitMQ管理插件
sudo rabbitmq-plugins enable rabbitmq_management

# 重新启动RabbitMQ服务以使更改生效
sudo systemctl restart rabbitmq-server

# 设置RabbitMQ开机自启
sudo systemctl enable rabbitmq-server
2.Windows安装
erlang语言包下载地址: https://erlang.org/download/otp_versions_tree.html
rabbitmq下载地址: 版本对照地址: https://www.rabbitmq.com/which-erlang.html
3.Docker安装
# 拉取并启动
# --hostname 指定主机名(RabbitMQ 的一个重要注意事项是它根据所谓的节点名称存储数据,默认为主机名)
sudo docker run -d -p 15672:15672 -p 5672:5672 -e RABBITMQ_DEFAULT_VHOST=test -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=123456 -e RABBITMQ_ERLANG_COOKIE='rabbitcookie' -v /var/lib/rabbitmq:/var/lib/rabbitmq --privileged=true --name rabbitmq rabbitmq

# 设置开机自启
sudo docker update rabbitmq --restart=always

# 启动界面管理
sudo docker exec -it rabbitmq rabbitmq-plugins enable rabbitmq_management
4.Docker-compose安装
version: "3.8"
services:
  rabbitmq:
    image: daocloud.io/library/rabbitmq:3.8.7
    container_name: rabbitmq
    restart: always
    environment:
      RABBITMQ_DEFAULT_USER: "admin"
      RABBITMQ_DEFAULT_PASS: "123456"
    volumes:
      - ./data/:/var/lib/rabbitmq/
    ports:
      - 5672:5672
      - 15672:15672

networks:
  mynet:
    driver: bridge
5.集群搭建
  • 修改主机名称
# 修改etc/hosts下的文件
# 在三个节点下分别配置如下内容:
192.168.80.161	node1
192.168.80.162	node2
192.168.80.163	node3

# 使用命令修改(如果配置文件配置节点名称修改不过来可以使用这种方式): 
sudo hostnamectl set-hostname newhostname

# 使修改生效
sudo systemctl restart systemd-hostnamed


  • 同步cookie
# 确保节点间使用同一个cookie,需要将/var/lib/rabbitmq/.erlang.cookie文件从主节点同步到各个从节点
scp /var/lib/rabbitmq/.erlang.cookie admin@192.168.80.163:~/
  
# 切回根目录,这里是因为权限不够所以将文件发送到了根目录
cd
  
# 将文件移动至/var/lib/rabbitmq/.erlang.cookie
sudo mv .erlang.cookie /var/lib/rabbitmq/.erlang.cookie
  
# 修改文件所属用户及用户组
sudo chown rabbitmq:rabbitmq /var/lib/rabbitmq/.erlang.cookie
  • 加入集群
# 从节点停止服务,rabbitmqctl stop会将erlang虚拟机关闭,而这种方式只关闭rabbitmq服务
sudo rabbitmqctl stop_app

# 从节点重置(当前node1为主节点)
sudo rabbitmqctl reset

# 加入主节点
sudo rabbitmqctl join_cluster rabbit@node1

# 启用应用服务
sudo rabbitmqctl start_app

# 查看集群状态
sudo rabbitmqctl cluster_stauts

# 不知道啥原因,登录不上去了,重新添加个用户并授权
sudo rabbitmqctl add_user admin 123456
sudo rabbitmqctl set_user_tags admin administrator

6.镜像队列
6.1.精确模式
# rabbitmqctl set_policy 固定写法
# ha-test 自定义的策略名称
# ^test\. 匹配队列的正则表达式
# ha-mode: 策略模式
# ha-params: 如果是使用了精确模式,需要指定备份的数量(这个案例中是2,也就是指主机1份,备份节点1份)
# ha-sync-mode: 三种情况
    # automatic: 镜像节点与主节点之间的同步是自动的。镜像节点会自动追随主节点的变化
    # manual: 镜像节点与主节点之间的同步是手动的。需要手动触发同步操作
    # explicit:镜像节点与主节点之间的同步需要显式命令触发

# 命令如下:
rabbitmqctl set_policy ha-exactly "^test\." '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'
6.2.全部备份
rabbitmqctl set_policy ha-all "^all\." '{"ha-mode":"all"}'
6.3.节点模式
# ha-params: 如果策略模式为nodes,这里需要明确指定副本所在节点名称

rabbitmqctl set_policy ha-nodes "^simple\." '{"ha-mode":"nodes","ha-params":["rabbit@node1", "rabbit@node2"]}'

二、模式(官方介绍

1.简单模式
# 消费者
@RabbitListener(queuesToDeclare = @Queue(name = "simple.queue", arguments =         
@Argument(name = "x-queue-mode", value = "lazy")))
public void simple(String message) {
    System.out.println(message);
}


# 生产者
@Resource
private RabbitTemplate rabbitTemplate;

@Test
public void simple() {
    rabbitTemplate.convertAndSend("simple.queue", "简单队列");
}
2.工作模式

多个消费者消费同一个队列,默认情况下轮询从通道中获取消息

# 配置文件配置
spring.rabbitmq.listener.simple.prefetch=1

# 消费者
@RabbitListener(queuesToDeclare = @Queue(name = "work.queue", arguments = @Argument(name = "x-queue-mode", value = "lazy")))
public void work1(String msg) {
    System.out.println(msg);
}

@RabbitListener(queues = "work.queue")
public void work2(String content) {
    System.err.println(content);
}

# 生产者
@Test
public void test() {
    for (int i = 0; i < 10; i++) {
        rabbitTemplate.convertAndSend("work.queue", "第" + i + "条消息");
    }
}
3.发布与订阅
# 消费者
@RabbitListener(bindings = @QueueBinding(
    value = @Queue(name = "fanout1", arguments = @Argument(name = "x-queue-mode",value = "lazy")),
    exchange = @Exchange(name = "my.fanout", type = ExchangeTypes.FANOUT)))
public void fanout1(String message) {
    System.out.println(message);
}

RabbitListener(bindings = @QueueBinding(
    value = @Queue(name = "fanout2", arguments = @Argument(name = "x-queue-mode", value = "lazy")),
    exchange = @Exchange(name = "my.fanout", type = ExchangeTypes.FANOUT)
    ))
public void fanout2(String message) {
    System.err.println(message);
}

# 生产者
@Test
public void test() {
    rabbitTemplate.convertAndSend("my.fanout", "", "测试发布与订阅模式");
}
4.路由模式
# 消费者
@RabbitListener(bindings = @QueueBinding(
        value = @Queue(name = "routing1.queue"),
        exchange = @Exchange(name = "routing.exchange", type = ExchangeTypes.DIRECT),
        key = {"info"}
))
public void routing1(String message) {
    System.out.println("info接收到的消息为: " + message);
}

@RabbitListener(bindings = @QueueBinding(
        value = @Queue(name = "routing2.queue"),
        exchange = @Exchange(name = "routing.exchange"),
        key = {"err", "info"}
))
public void routing2(String message) {
    System.err.println("err接收到的消息为: " + message);
}


# 生产者
@Resource
private RabbitTemplate rabbitTemplate;

@Test
public void test1() {
    rabbitTemplate.convertAndSend("routing.exchange", "info", "测试路由模式");
}

@Test
public void test2() {
    rabbitTemplate.convertAndSend("routing.exchange", "err", "测试路由模式");
}
5.主题模式

#:表示匹配0个或多个单词

*::表示匹配1个单词

# 消费者
@RabbitListener(bindings = @QueueBinding(
        value = @Queue(name = "topic.queue1", durable = "true"),
        exchange = @Exchange(name = "topic.exchange", type = ExchangeTypes.TOPIC),
        key = {"topic.#"}
))
public void topic1(String message) {
    System.out.println("消费者1接收到的消息为: " + message);
}

@RabbitListener(bindings = @QueueBinding(
        value = @Queue(name = "topic.queue3", durable = "true"),
        exchange = @Exchange(name = "topic.exchange", type = ExchangeTypes.TOPIC),
        key = {"topic.*.black"}
))
public void topic3(String message) {
    System.out.println("消费者3接收到的消息为: " + message);
}


# 生产者
@Test
public void test() {
    rabbitTemplate.convertAndSend("topic.exchange", "topic.test.black", "测试主题模式1");
}

@Test
public void test2() {
    rabbitTemplate.convertAndSend("topic.exchange", "topic.test", "测试主题模式2");
}
6.发布确认

配置文件中配置如下: 

spring.rabbitmq.publisher-confirm-type: correlated

注意: 非必要不要开启。非常影响性能

# 配置类配置
@Configuration
@Slf4j
public class RabbitMQConfig implements ApplicationContextAware {

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class);

    rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
        if (ack) {
            log.debug("id为{}的消息处理成功", correlationData);
        } else {
            log.error("消息处理失败,失败原因为: {}", cause);
        }
    });
}


# 消费者
@RabbitListener(queuesToDeclare = @Queue(name = "confirm.queue"))
public void confirm(String message) {
    System.out.println(message);
}


# 生产者
@Test
public void test() {
    CorrelationData correlationData = new CorrelationData("unique-id-" + System.currentTimeMillis());
    rabbitTemplate.convertAndSend("confirm.queue1", "", "测试confirm模式", correlationData);
}

三、队列

1.惰性队列
  • 接收到消息后直接存入磁盘而非内存

  • 消费者要消费消息时才会从磁盘中读取并加载到内存

  • 支持数百万条的消息存储

# 注解方式定义惰性队列
@RabbitListener(queuesToDeclare = @Queue(name = "simple.queue", arguments = @Argument(name = "x-queue-mode", value = "lazy")))
public void simple(String message) {
        System.out.println(message);
}


# Bean方式定义惰性队列
@Bean
public Queue errorQueue() {
    return QueueBuilder.durable("error.queue").lazy().build();
}
2.延迟队列

插件下载地址

1.将插件添加到plugins目录下

2.添加延迟队列插件

sudo docker exec -it rabbitmq rabbitmq-plugins enable rabbitmq_delayed_message_exchange

注意: 3.8版本之前没有提供插件模式,需要使用死信交换机实现延时队列。具体示例参考死信队列

# 消费者
@RabbitListener(bindings = @QueueBinding(
        value = @Queue(name = "delay.queue"),
        exchange = @Exchange(name = "delay.direct", delayed = "true"),
        key = "delay"
))
public void delay(String message) {
    System.out.println(message);
}


# 生产者
@Test
public void test() {
    rabbitTemplate.convertAndSend("delay.direct", "delay", "测试延迟队列", new MessagePostProcessor() {
        @Override
        public Message postProcessMessage(Message message) throws AmqpException {
            message.getMessageProperties().setDelay(5000);
            return message;
        }
    });
}
3.死信队列

成为死信的三种情况:

  • 消费者使用basic.reject或basic.nack声明消费失败,并且消息的requeue参数设置为false
  • 消息是一个过期消息,超时无人消费
  • 要投递的队列消息堆积满了,最早的消息可能成为死信

下例是以死信交换机实现的延迟队列的案例,通过超时时间使消息成为死信并监听死信队列进行消费

  • 定义队列

   /**
     * 1. 分别定义业务队列&业务交换机 和 死信队列&死信交换机
     * 2. 业务队列通过x-dead-letter-routing-key参数指定routing-key,通过x-dead-letter-exchange参数指定绑定的交换机
     */
    @Bean
    public Queue initQueue() {
        return QueueBuilder.durable("my.queue")
                .withArgument("x-dead-letter-exchange", "dlx.exchange") // 指定死信交换机
                .withArgument("x-dead-letter-routing-key", "dlx.err") // 指定死信交换机routingKey
                .build();
    }

    @Bean
    public DirectExchange initExchange() {
        return ExchangeBuilder.directExchange("my.exchange").build();
    }

    @Bean
    public Binding initBinding() {
        return BindingBuilder.bind(initQueue()).to(initExchange()).with("key");
    }

    @Bean("dlxQueue")
    public Queue dlxQueue() {
        return QueueBuilder.durable("dlx.queue").lazy().build();
    }

    @Bean("dlxExchange")
    public DirectExchange dlxExchange() {
        return ExchangeBuilder.directExchange("dlx.exchange").build();
    }

    @Bean
    public Binding dlxBinding(@Qualifier("dlxQueue") Queue queue, @Qualifier("dlxExchange") Exchange exchange) {
//        return BindingBuilder.bind(dlxQueue()).to(dlxExchange()).with("dlx.err");
        return BindingBuilder.bind(queue).to(exchange).with("dlx.err").noargs();
    }
  • 定义消费者
    @RabbitListener(queues = "dlx.queue")
    public void delay1(String message) {
        System.out.println(message);
    }
  • 定义提供者
    @Test
    public void test() {
        rabbitTemplate.convertAndSend("my.exchange", "key", "测试延迟队列", message -> {
            message.getMessageProperties().setExpiration("10000");
            return message;
        });
    }

四、消费者消息可靠性

1.手动签收
# auto: 自动确认,根据异常类型进行判断是否签收
# manual: 需要在代码中手动签收
spring:
  rabbitmq:
    listener:
      simple:
        acknowledge-mode: auto



# 手动签收示例
@RabbitListener(queuesToDeclare = @Queue(name = "simple.ack", arguments = @Argument(name = "x-queue-mode", value = "lazy")))
public void ack(String content, Channel channel, Message message) throws IOException {
    try {
        // 手动签收
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    } catch (Exception e) {
        // 第一个boolean是否允许多条处理,第二个boolean为是否重回队列
        channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
    }
}
2.消费失败重试(自动补偿)
spring:
  rabbitmq:
    listener:
      simple:
        retry:
          # 开启消费者失败重试
          enabled: on
          # 初始失败等待时长
          initialInterval: 1000ms
          # 下次等待时长为 multiplier * last-initial
          multiplier: 1
          # 最大重试次数
          maxAttempts: 5
          # false为无装填,如果包含事务改为false
          stateless: false
3.重回队列
    # 消费者
    @Bean
    public Queue errorQueue() {
        return QueueBuilder.durable("error.queue").lazy().build();
    }

    @Bean
    public DirectExchange errorExchange() {
        return new DirectExchange("error.direct");
    }

    @Bean
    public Binding errorBinding() {
        return BindingBuilder.bind(errorQueue()).to(errorExchange()).with("error");
    }

    // 指定重新回到哪个队列
    @Bean
    public MessageRecoverer messageRecoverer(RabbitTemplate rabbitTemplate) {
        return new RepublishMessageRecoverer(rabbitTemplate, errorExchange().getName(), "error");
    }

    # 生产者需要携带messageId
    @Bean
    public MessageConverter messageConverter() {
        Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter();
        converter.setCreateMessageIds(true);
        return converter;
    }

五、其他

1.消息幂等性

解决幂等性的方案比较多。主要思想就是通过一个唯一标识来分辨消息是否已经消费过

令牌机制

在进入页面时生成一个唯一标识(例如token),操作完成后删除这个唯一标识。每次进行标识的校验。如果存在则方形,不存在则表示消息已经处理过了

全局唯一ID

例如使用MQ中的messageId来判断消息是否消费过

乐观锁方式

2.消息追踪
2.1.Filehose方式

在rabbitmq中会有一个内置交换机amq.rabbitmq.trace,它用于消息的追踪和审计。

实现方式:

        1.开启firehose

                sudo systemctl enable rabbitmq-server

        2.定义一个队列接收消息并且与trace交换机绑定

弊端:

  1. 开启消息追踪可能会导致性能开销,特别是在高流量环境中。因为每个消息都要被发送到 amq.rabbitmq.trace 交换机,这增加了系统的负担。

  2. 将所有消息发送到 amq.rabbitmq.trace 交换机可能导致消息的泄漏和安全风险。因为消息被发送到一个预定义的队列,如果你的系统中有敏感信息,确保在使用这个功能时做好相应的安全措施。

  3. 如果系统中的消息量非常大,且对每条消息都需要进行追踪,使用 amq.rabbitmq.trace 交换机可能不是最佳选择。考虑使用其他监控和追踪工具来处理高频率的消息追踪需求。

2.2.插件方式

1.查看已启用的插件(非必须) rabbitmq-plugins list

2.启用插件 rabbitmq-plugins enable rabbitmq_tracing

3.在控制台会Admin界面下的右侧会多出一个Tracing

4.添加一个trace

3.队列参数
3.1.消息过期时间
    @Bean
    public Queue backupQueue() {
        return QueueBuilder.durable("backup").withArgument("x-message-ttl", 6000).build();
    }
3.2.队列最大长度(条)
    # 队列中最多存入100条消息
    @Bean
    public Queue backupQueue() {
        return QueueBuilder.durable("backup").withArgument("x-max-length", 100).build();
    }
3.3.队列最大长度(字节)
    # 队列中的消息总字节量为102400
    @Bean
    public Queue backupQueue() {
        return QueueBuilder.durable("backup").withArgument("x-max-length-bytes", 102400).build();
    }
3.4.队列消息溢出
    /**
     *  x-overflow可选值:
     *      drop-head: 当队列满时,丢弃队列头部消息
     *      reject-publish: 当队列满时,拒绝发布新消息
     *      reject-publish-dlx: 当队列满时,拒绝发布新消息,并将拒绝的消息发布到死信队列
     */
    @Bean
    public Queue backupQueue() {
        return QueueBuilder.durable("backup").withArgument("x-overflow", "drop-head").build();
    }
3.5.指定死信队列
    @Bean
    public Queue initQueue() {
        return QueueBuilder.durable("my.queue")
                .withArgument("x-dead-letter-exchange", "dlx.exchange") // 指定死信交换机
                .build();
    }
3.6.指定死信队列路由键
    @Bean
    public Queue initQueue() {
        return QueueBuilder.durable("my.queue")
            .withArgument("x-dead-letter-routing-key", "dlx.err") // 指定死信交换机路由键
            .build();
    }
3.7.单例模式

用于确保在队列上只有一个活跃的(正在消费消息的)消费者。当设置了这个属性后,一旦有一个消费者开始处理消息,其他消费者将被暂停,直到这个消费者完成或者发生了重新分发(requeue)操作

这个属性对于确保消息的顺序处理和避免重复处理非常有用。当队列的消息需要按照严格的顺序处理时,可以通过设置 single active consumer 来确保一次只有一个消费者在处理消息。这在一些特定的业务场景中是非常有用的

    @Bean
    public Queue backupQueue() {
        return QueueBuilder.durable("backup").withArgument("x-single-active-consumer", true).build();
    }
3.8.队列优先级
    /**
     *  取值范围0-255,数值越大越先执行
     */
    @Bean
    public Queue backupQueue() {
        return QueueBuilder.durable("backup").withArgument("x-max-priority", 10).build();
    }
3.9.备份交换机
    @Bean("simpleQueue")
    public Queue initQueue() {
        return QueueBuilder.durable("simple").withArgument("alternate-exchange", "backup-exchange").build();
    }
  • 23
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值