RabbitMQ 快速入门

RabbitMQ Tutorials — RabbitMQ
AMQP 0-9-1 介绍
示例源码

RabbitMQ 是最广泛部署使用的开源的消息代理中间件。

本文讲解几个入门示例:

  • Hello World!
  • 工作队列
  • 发布订阅
  • 路由模式
  • Topics 模式
  • 发布确认

Hello World!

Hello World! 示例实现了:

发布者发布 “Hello World!” 到队列,消费者从队列接受到 “Hello World!”

发布者实现

  1. 获取 RabbitMQ 连接
  • 需指 RabbitMQ Server 地址、端口号、账号密码
private static final ConnectionFactory connectionFactory = new ConnectionFactory();
static {
    connectionFactory.setHost(Constants.RABBITMQ_HOST);
    connectionFactory.setVirtualHost(Constants.RABBITMQ_VIRTUAL_HOST);
    // 不设置用户, 默认使用 guest 用户
}
public static Connection newConnection() throws IOException, TimeoutException {
    return connectionFactory.newConnection();
}
  • Connection 是 TCP 长连接,AMQP 0-9-1 协议使用 Channel 复用这个长连接,所以我们要 connection.createChannel() 获取通信通道来与 RabbitMQ 通信
  1. 声明队列
// 队列名, 是否持久化, 是否当前连接独占, 是否不使用后自动删除, 其他参数
channel.queueDeclare(Constants.QUEUE_HELLO_WORLD, true, false, false, null);
  1. 往队列发布消息
String message = "Hello World!";
// exchange,队列名称,其他参数,消息
channel.basicPublish("", Constants.QUEUE_HELLO_WORLD, null, message.getBytes());

Exchange 传了空字符串,是不用指定 Exchange ?
AMQP 0-9-1 规定了默认的 Exchange 的名称是空字符串,类型是 Direct,使用队列名作为 RoutingKey

消费者实现

  1. 获取 RabbitMQ 连接
  2. 声明队列
  3. 监听消息
  • 取消接收消息的回调在消息队列被删除时触发,basicCancel 触发的是com.rabbitmq.client.Consumer#handleCancelOk
channel.basicConsume(Constants.QUEUE_HELLO_WORLD, false
    // 接收消息回调
    , (consumerTag, deliver) -> {
        log.info("[{}] receive: {}", consumerTag, new String(deliver.getBody()));
        // false - 只应答当前消息
        channel.basicAck(deliver.getEnvelope().getDeliveryTag(), false);
    }
    // 取消接受消息回调	
    , consumerTag -> {
        log.info("[{}] ConsumerA cancel receive", consumerTag);
	});

发布者、消费者都需要声明队列?
若只在一方声明队列,则必须保证声明方先启动。
AMQP 0-9-1 允许重复声明队列,发布者、消费者都声明可避免队列不存在。不过声明队列时指定的属性和前一次不一致时会报错。

执行结果

  1. 启动发布者
  2. 启动消费者

消费者控制台:
image.png

工作队列


发布者发布任务,消费者处理任务。

发布者实现

  1. 获取 RabbitMQ 连接
  2. 声明队列
  3. 往队列发布任务(消息)

消费者实现

实现两个消费者 C1、C2:

  1. 获取 RabbitMQ 连接
  2. 声明队列
  3. 接收任务(消息)
channel.basicConsume(Constants.QUEUE_HELLO_WORLD, false
    // 接收消息回调
    , (consumerTag, deliver) -> {
        String task = new String(deliver.getBody());
        log.info("[{}] ConsumerA receive: {}", consumerTag, task);
        try {
            // C1 睡 10 ms
            // Thread.sleep(10);
            // C2 睡 5 ms
            Thread.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("done task: {}", task);
        channel.basicAck(deliver.getEnvelope().getDeliveryTag(), false);
    }
    // 取消接受消息回调	
    , consumerTag -> {
        log.info("[{}] ConsumerA cancel receive", consumerTag);
	});

假设消费者处理任务时宕机了,如何保证消费者确实成功执行了任务,任务没有丢失呢?
通过消息接收确认机制,消费者可以在成功处理完任务后再调用basicAck方法应答,Broker 在收到应答再移除任务。
若处理任务时发生错误,可调用 basicNack 表示拒绝消息,拒绝的消息可指定直接废弃,还是重新入队。

执行结果

  1. 启动消费者C1、C2
  2. 启动发布者

发布者发布 10 个任务

for (int i = 0; i < 10; i++) {
    String message = String.format("task [%d]", i);
    channel.basicPublish("", Constants.QUEUE_WORK_QUEUES, null, message.getBytes());
}

C1 控制台:
image.png
C2 控制台:
image.png

C1、C2 处理任务效率不一样,确分到相同数量的任务,为什么呢?
因为 Broker 默认不会等消费者处理完一个任务再发送下一个任务,直接持续轮询分配任务。
可以通过 basicQos(n)方法指定消费者处理完 n 个任务后再分配下 n 个任务。

channel.basicQos(1)指定处理完一个任务再分配下一个任务,执行结果如下:
C1 控制台:
image.png
C2 控制台:
image.png

发布订阅

  • Echange 将消息路由给所有队列


发布者 P 发布消息 M,消费者 C1、C2 都会收到消息 M。

发布者实现

  1. 获取 RabbitMQ 连接
  2. 声明 Exchange
// 交易所名称, 交易所类型
channel.exchangeDeclare(Constants.EXCHANGE_FANOUT, BuiltinExchangeType.FANOUT);
  1. 往 Exchange 发布消息

订阅者实现

  1. 获取 RabbitMQ 连接
  2. 声明队列
  3. 将队列绑定到 Exchange
channel.queueBind(Constants.QUEUE_FANOUT_A, Constants.EXCHANGE_FANOUT, "");
  1. 监听队列消息

路由模式

  • Echange 根据 RoutingKey 将消息路由给目标队列


发布者 P 发布消息 M1 ,M1 的 RoutingKey = error,C1、C2 都会收到消息 M1;
发布者 P 发布消息 M2,M2 的 RoutingKey = error,只有 C2 收到消息 M2。

发布者实现

  1. 获取 RabbitMQ 连接
  2. 声明 Exchange
// 交易所名称, 交易所类型
channel.exchangeDeclare(Constants.EXCHANGE_DIRECT, BuiltinExchangeType.DIRECT);
  1. 往 Exchange 发布消息

消费者实现

实现两个消费者 C1、C2:

  1. 获取 RabbitMQ 连接
  2. 声明队列
  3. 将队列绑定到 Exchange
// C1
channel.queueBind(Constants.QUEUE_DIRECT_A, Constants.EXCHANGE_DIRECT, "info");
channel.queueBind(Constants.QUEUE_DIRECT_A, Constants.EXCHANGE_DIRECT, "warn");
channel.queueBind(Constants.QUEUE_DIRECT_A, Constants.EXCHANGE_DIRECT, "error");

// C2
channel.queueBind(Constants.QUEUE_DIRECT_B, Constants.EXCHANGE_DIRECT, "error");
  1. 监听队列消息

执行结果

  1. 启动发布者
  2. 启动消费者 C1、C2

发布者:
image.png
消费者 C1:
image.png
消费者 C2:
image.png

Topics 模式

  • 队列绑定 Exchange 时指定通配符
  • Exchange 根据消息的 RoutingKey 与 队列的通配符匹配,匹配上则将消息路由给目标队列


通配符规则:

  • RoutingKey 由多个词组成,词之间通过 . 分割,格式与域名类似
  • * 匹配一个词
  • # 匹配多个词

示例代码

发布确认

看了【工作队列 】示例,我们知道 AMQP 保证消费消息的可靠是通过接收消息确认机制,那如何保证发布消息的可靠性呢?
发布确认机制,在 Broker 成功接收消息后,给发布者回复确认,从而保证发布消息的可靠性。

  • 同步等待确认:
// 等待 1000 ms
channel.waitForConfirms(1000);
  • 异步监听确认:
channel.addConfirmListener((deliveryTag, multiple) -> {
    // Broker 正常接收
    log.info("[async] message {} confirmed", deliveryTag);
}, (deliveryTag, multiple) -> {
    // Broker 接收不到消息
    log.info("[async] message {} un confirmed", deliveryTag);
});

RabbitMQ 还支持返回发布者没消费者订阅的消息,即发送的消息 Exchange 无法路由给任何队列。
channel.addReturnListener(returnMessage -> {
log.info(“{} is returned, because {}”
, new String (returnMessage.getBody()), returnMessage.getReplyText());
});

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值