高可靠性的消息队列 —— RabbitMQ

  • 原生Java客户端

Channel ch = null;

// …

ch.txSelect();

for (int i = 0; i < MSG_COUNT; ++i) {

try {

ch.basicPublish(“”, QUEUE_NAME,

MessageProperties.PERSISTENT_BASIC,

“nop”.getBytes());

ch.txCommit();

} catch (Exception exception) {

ch.txRollback();

}

}

  • SpringBoot AMQP

首先提供一个事务管理器供SpringBoot使用。

@Bean

RabbitTransactionManager transactionManager(ConnectionFactory connectionFactory) {

return new RabbitTransactionManager(connectionFactory);

}

接下来,在消息生产者上面做两件事:添加@Transactional并设置通信信道为事务模式:

@Service

public class MsgService {

@Autowired

RabbitTemplate rabbitTemplate;

@Transactional

public void send() {

rabbitTemplate.setChannelTransacted(true);

// rabbitTemplate.convertAndSend(…);

// int i = 1 / 0; 没有爆发异常,由spring提交事务,否则回滚(也就是不发生消息)

}

}

但是使用事务有两个问题。

首先channel长时间处于阻塞:发布者必须依次等待broker处理每条消息。

不过有时候发布者只要知道broker宕机时哪些消息尚未处理就足够了。

其次是事务实现的繁重性:每次提交都需要一个 fsync(),这需要很多时间才能完成。

发布 10000 条消息需要 4 多分钟(具体参数机器性能决定,总之确实非常慢)

发送方确认机制


一旦通道进入确认模式,代理将在处理消息时确认消息。

由于这是异步完成的,生产者可以流式发布而不用等待代理,代理也可以有效地批量写入磁盘

  • 原生Java客户端

// 消息追踪记录(如果需要线程安全并且有序,可以使用 ConcurrentSkipListMap )

HashMap<Long, String> map = new HashMap<>();

// 必须显式开启

channel.confirmSelect();

// 监听被退回的消息(如消息路由到队列失败)

channel.addReturnListener(returnMessage -> {

System.out.println("return : " + System.currentTimeMillis());

try {

Thread.sleep(100);

} catch (InterruptedException e) {

e.printStackTrace();

}

try {

TimeUnit.SECONDS.sleep(10);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.err.println(new String(returnMessage.getBody()) + " publish fail!");

});

// 监听被到达或未到达交换机(exchange)的消息

channel.addConfirmListener(new ConfirmListener() {

@Override

public void handleAck(long deliveryTag, boolean multiple) throws IOException {

map.remove(deliveryTag); // 发送成功,缓存清除掉

}

@Override

public void handleNack(long deliveryTag, boolean multiple) throws IOException {

System.err.println(map.get(deliveryTag) + “not ack!”);

// 下面可以进行重新发送等逻辑

}

});

try {

Random random = new Random();

int idx = 0;

while (idx < 1000) {

String message = “from server…” + (++idx);

// 追踪记录

map.put(channel.getNextPublishSeqNo(), message);

// 发送消息

channel.basicPublish(“”, queue, MessageProperties.PERSISTENT_BASIC, message.getBytes(StandardCharsets.UTF_8));

TimeUnit.MILLISECONDS.sleep(random.nextInt(300) + 200);

}

} finally {

System.out.println(“以下为未成功发送的消息”);

// 可以进行重试逻辑

map.values().forEach(s -> System.out.println("not ack, may need publish again : " + s));

}

  • SpringBoot AMQP

首先在配置文件中配置中开启消息发送方确认机制

spring:

rabbitmq:

publisher-returns: true

publisher-confirm-type: correlated

publisher-confirm-type有三种属性:

  1. none:表示禁用发布确认模式,默认即此。

  2. correlated:使用相关消息确认,回调中触发。

  3. simple:使用 waitForConfirms()waitForConfirmsOrDie() 方法的进行确认。

然后配置回调的监听器

@Configuration

public class PublisherConfirmConfig implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnsCallback {

RabbitTemplate rabbitTemplate;

public PublisherConfirmConfig(RabbitTemplate rabbitTemplate) {

this.rabbitTemplate = rabbitTemplate;

}

//@Bean 此处无需注入

RabbitTransactionManager transactionManager(ConnectionFactory connectionFactory) {

return new RabbitTransactionManager(connectionFactory);

}

@Override

public void confirm(CorrelationData correlationData, boolean ack, String cause) {

System.out.println("correlationData : "+correlationData);

if (ack) {

System.out.println(“success”);

} else {

System.err.println("cause : "+cause);

}

}

@Override

public void returnedMessage(ReturnedMessage returned) {

System.err.println(returned);

}

@PostConstruct

public void initRabbitTemplate() {

rabbitTemplate.setConfirmCallback(this);

rabbitTemplate.setReturnsCallback(this);

}

}

发送消息:

// 注意必须传入 CorrelationData,否则没有根据去跟踪(原生client使用deliveryTag跟踪)

rabbitTemplate.convertAndSend(“q1”, (Object) s, new CorrelationData("correlation id = " + count));

MQ服务器存储消息不丢失

============================================================================

消费者消费消息不丢失

==========================================================================

关于ACK


RabbitMQ的Client

RabbitMQ中channel在消费消息(basicConsume(String queue, boolean autoAck, Consumer callback))的时候,指定的ack的含义如下:

  • autoAck = true

当broker在消息发送后(写入TCP套接字后)此条消息就立即ack了,此条消息RabbitMQ服务器也不再保存了,

而丝毫不管收到消息的客户端是否处理。如果消费者在收到大量消息但没有处理的时候突然宕机了,那么那些未处理消息也就随着本地缓冲区的消失而消失了(服务器上也没有了)。

这种ack方式谨慎使用。

  • autoAck = false

这种ack方式必须要求用户自己主动ack消息(channel.basicAck),常常和prefetchCount配合使用(后面会介绍到)。

Spring AMQP

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

作为过来人,小编是整理了很多进阶架构视频资料、面试文档以及PDF的学习资料,针对上面一套系统大纲小编也有对应的相关进阶架构视频资料


《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!
经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!**

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

作为过来人,小编是整理了很多进阶架构视频资料、面试文档以及PDF的学习资料,针对上面一套系统大纲小编也有对应的相关进阶架构视频资料

[外链图片转存中…(img-SXbLZYsZ-1712537241522)]
[外链图片转存中…(img-suaXpcxL-1712537241522)]

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!

  • 17
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值