MQ高可用相关设置,2024年最新驱动核心源码详解和Binder超系统学习资源

一、生产者

开启RabbitMQ事务
生产者发送数据之前开启 RabbitMQ 事务channel.txSelect,然后发送消息,如果消息没有成功被 RabbitMQ 接收到,那么生产者会收到异常报错,此时就可以回滚事务channel.txRollback,然后重试发送消息;如果收到了消息,那么可以提交事务channel.txCommit。

// 开启事务
channel.txSelect
try {
// 这里发送消息
} catch (Exception e) {
channel.txRollback

// 这里再次重发这条消息

}

// 提交事务
channel.txCommit

设置Confirm模式:

同步确认:

//开启发布确认
channel.confirmSelect();
String message = i + “”;
channel.basicPublish(“”,queueName,null,message.getBytes());
//服务端返回 false 或超时时间内未返回,生产者可以消息重发
boolean flag = channel.waitForConfirms();
if(flag){
System.out.println(“消息发送成功”);
}

异步确认 :

服务端 :

消息持久化,必须满足以下三个条件,缺一不可。

  • Exchange 设置持久化
  • Queue 设置持久化
  • Message持久化发送:发送消息设置发送模式deliveryMode=2,代表持久化消息

发送消息时设置delivery_mode属性为2,使消息被持久化保存到磁盘,即使RabbitMQ服务器宕机也能保证消息不丢失。同时,创建队列时设置durable属性为True,以确保队列也被持久化保存。

// 声明队列,并将队列设置为持久化
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
String message = “Hello, RabbitMQ!”;
// 发送消息时将消息设置为持久化
channel.basicPublish(“”, QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());

AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
.deliveryMode(2)
.build();
channel.basicPublish(“”, “myQueue”, properties, “Hello, RabbitMQ”.getBytes());

设置备份交换机:

Map<String, Object> arguments = new HashMap<>();
arguments.put(“alternate-exchange”, “myAlternateExchange”);
channel.exchangeDeclare(“myExchange”, BuiltinExchangeType.DIRECT, true, false, arguments);
channel.exchangeDeclare(“myAlternateExchange”, BuiltinExchangeType.FANOUT, true, false, null);

二 :服务端设置集群镜像模式

  • 单节点模式: 最简单的情况,非集群模式,节点挂了,消息就不能用了。业务可能瘫痪,只能等待。
  • 普通模式: 消息只会存在与当前节点中,并不会同步到其他节点,当前节点宕机,有影响的业务会瘫痪,只能等待节点恢复重启可用(必须持久化消息情况下)。
  • 镜像模式: 消息会同步到其他节点上,可以设置同步的节点个数,但吞吐量会下降。属于RabbitMQ的HA方案

消费方开启消息确认机制 :

// 开启消息确认机制
channel.basicConsume(QUEUE_NAME, false, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, “UTF-8”);
System.out.println("Received message: " + message);
// 手动发送消息确认
channel.basicAck(envelope.getDeliveryTag(), false);
}
});

手动确认 :

codechannel.basicConsume(“myQueue”, false, (consumerTag, delivery) -> {
// 处理消息
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}, consumerTag -> {});

RocketMQ

一、生产者提供SYNC的发送消息方式,等待broker处理结果。

RocketMQ生产者提供了3种发送消息方式,分别是:

同步发送:Producer 向 broker 发送消息,阻塞当前线程等待 broker 响应发送结果。

Message msg = new Message(“TopicTest”,
“TagA”,“OrderID188”,
“Hello world”.getBytes(RemotingHelper.DEFAULT_CHARSET));
//同步传递消息,消息会发给集群中的⼀个Broker节点。
SendResult sendResult = producer.send(msg);

异步发送:Producer 首先构建一个向 broker 发送消息的任务,把该任务提交给线程池,等执行完该任务时,回调用户自定义的回调函数,执行处理结果。

Message msg = new Message(“TopicTest”,“TagA”,“OrderID188”,
“Hello world”.getBytes(RemotingHelper.DEFAULT_CHARSET));

producer.send(msg, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
countDownLatch.countDown();
System.out.printf(“%-10d OK %s %n”, index, sendResult.getMsgId());
}

@Override
public void onException(Throwable e) {
countDownLatch.countDown();
System.out.printf(“%-10d Exception %s %n”, index, e);
e.printStackTrace();
}
});

Oneway发送:Oneway 方式只负责发送请求,不等待应答,Producer只负责把请求发出去,而不处理响应结果。

Message msg = new Message(“TopicTest” /* Topic */,
“TagA” /* Tag */,
("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
);
//Call send message to deliver message to one of brokers.
//核⼼:发送消息。没有返回值,发完消息就不管了,不知道有没有发送消息成功
producer.sendOneway(msg);

生产者使用重试机制 :
当发送消息发生异常或超时的时候重新循环发送。默认重试3次,重试次数可以通过setRetryTimesWhenSendFailed指定。

// 同步发送消息,如果5秒内没有发送成功,则重试3次
DefaultMQProducer producer = new DefaultMQProducer(“DefaultProducer”);
producer.setRetryTimesWhenSendFailed(3);
producer.send(msg, 5000L);

SendResult定义说明(来自RocketMQ官方)

  • SEND_OK
    消息发送成功。要注意的是消息发送成功也不意味着它是可靠的。要确保不会丢失任何消息,还应启用同步Master服务器或同步刷盘,即SYNC_MASTER或SYNC_FLUSH。
  • FLUSH_DISK_TIMEOUT
    消息发送成功但是服务器刷盘超时。此时消息已经进入服务器队列(内存),只有服务器宕机,消息才会丢失。消息存储配置参数中可以设置刷盘方式和同步刷盘时间长度,如果Broker服务器设置了刷盘方式为同步刷盘,即FlushDiskType=SYNC_FLUSH(默认为异步刷盘方式),当Broker服务器未在同步刷盘时间内(默认为5s)完成刷盘,则将返回该状态——刷盘超时。
  • FLUSH_SLAVE_TIMEOUT
    消息发送成功,但是服务器同步到Slave时超时。此时消息已经进入服务器队列,只有服务器宕机,消息才会丢失。如果Broker服务器的角色是同步Master,即SYNC_MASTER(默认是异步Master即ASYNC_MASTER),并且从Broker服务器未在同步刷盘时间(默认为5秒)内完成与主服务器的同步,则将返回该状态——数据同步到Slave服务器超时。
  • SLAVE_NOT_AVAILABLE
    消息发送成功,但是此时Slave不可用。如果Broker服务器的角色是同步Master,即- SYNC_MASTER(默认是异步Master服务器即ASYNC_MASTER),但没有配置slave Broker服务器,则将返回该状态——无Slave服务器可用。

我们在调用producer.send方法时,不指定回调方法,则默认采用同步发送消息的方式,这也是丢失几率最小的一种发送方式(但是效率比较低)。

二、Borker 方面 : 设置成同步刷盘及同步复制;开启集群模式,集群同步;

1)异步刷盘

默认。消息写入 CommitLog 时,并不会直接写入磁盘,而是先写入 PageCache 缓存后返回成功,然后用后台线程异步把消息刷入磁盘。异步刷盘提高了消息吞吐量,但是可能会有消息丢失的情况,比如断点导致机器停机,PageCache 中没来得及刷盘的消息就会丢失。

2)同步刷盘

消息写入内存后,立刻请求刷盘线程进行刷盘,如果消息未在约定的时间内(默认 5 s)刷盘成功,就返回 FLUSH_DISK_TIMEOUT,Producer 收到这个响应后,可以进行重试。同步刷盘策略保证了消息的可靠性,同时降低了吞吐量,增加了延迟。要开启同步刷盘,需要增加下面配置:

flushDiskType=SYNC_FLUSH

同步复制后,消息复制流程如下:

  • slave 初始化后,跟 master 建立连接并向 master 发送自己的 offset;
  • master 收到 slave 发送的 offset 后,将 offset 后面的消息批量发送给 slave;
  • slave 把收到的消息写入 commitLog 文件,并给 master 发送新的 offset;
  • master 收到新的 offset 后,如果 offset >= producer 发送消息后的 offset,给 Producer 返回 SEND_OK。

三、消费者

RocketMQ消费失败后的消费重试机制

手动提交消息偏移量

consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List msgs,
ConsumeConcurrentlyContext context) {
System.out.printf(“%s Receive New Messages: %s %n”, Thread.currentThread().getName(), msgs);
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});

public enum ConsumeConcurrentlyStatus {
//业务方消费成功
CONSUME_SUCCESS,
//业务方消费失败,之后进行重新尝试消费
RECONSUME_LATER;
}

RECONSUME_LATER “%RETRY%+ConsumeGroupName”—重试队列的主题

消息不丢失

RocketMQ怎么保证消息不丢失

Kafka

解决方案:
1、生产者调用异步回调消息。伪代码如下: producer.send(msg,callback);
2、生产者增加消息确认机制,设置生产者参数:acks = all。指定partition的leader副本接收到消息,等待所有的follower副本都同步到了消息之后,才认为本次生产者发送消息成功了;
3、生产者设置重试次数。比如:retries>=3,增加重试次数以保证消息的不丢失;
4、定义本地消息日志表,定时任务扫描这个表自动补偿,做好监控告警。
5、后台提供一个补偿消息的工具,可以手工补偿。
6、消费者Rebalance的时候


生产者设置同步发送 :

// 异步发送 默认
kafkaProducer.send(new ProducerRecord<>(“first”,“kafka” + i));
// 同步发送
RecordMetadata first = kafkaProducer.send(new ProducerRecord<>(“first”, “kafka” + i)).get();

生产者设置发送ack :

// 1. 创建 kafka 生产者的配置对象
Properties properties = new Properties();
// 设置 acks
properties.put(ProducerConfig.ACKS_CONFIG, “all”);
// 3. 创建 kafka 生产者对象
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<String, String>(properties);
kafkaProducer.send(new ProducerRecord<>(“first”,"atguigu " + i));

生产者设置重试次数。比如:retries>=3,增加重试次数以保证消息的不丢失;

// 1. 创建 kafka 生产者的配置对象
Properties properties = new Properties();
// 设置 acks
properties.put(ProducerConfig.ACKS_CONFIG, “all”);
// 重试次数 retries,默认是 int 最大值,2147483647
properties.put(ProducerConfig.RETRIES_CONFIG, 3);
// 3. 创建 kafka 生产者对象
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<String, String>(properties);

三、消费者:

通过在Consumer端设置“enable.auto.commit”属性为false后,
在代码中手动调用KafkaConsumer实例的commitSync()方法提交,

这里指的是同步阻塞commit消费的偏移量,等待Broker端的返回响应,需要注意Broker端在对commit请求做出响应之前,消费端会处于阻塞状态,从而限制消息的处理性能和整体吞吐量以确保消息能够正常被消费。

如果在消费过程中,消费端突然Crash,这时候消费偏移量没有commit,等正常恢复后依然还会处理刚刚未commit的消息。

生产者acks参数指定了必须要有多少个分区副本收到消息,生产者才会认为消息写入是成功的。这个参数对消息丢失的可能性有重要影响。

1)ack=0,生产者在成功写入悄息之前不会等待任何来自服务器的响应。

2)ack=1,只要集群的首领副本收到消息,生产者就会收到一个来自服务器的成功响应。

3)ack=all,只有当所有同步副本全部收到消息时,生产者才会收到一个来自 服务器的成功响应。

MQ如何保证顺序消息

严格顺序消费的注意事项

  • 生产者不能异步发送,异步发送在发送失败的情况下,就没办法保证消息顺序
    比如你连续发了1,2,3。 过了一会,返回结果1失败,2, 3成功。你把1再重新发送1遍,这个时候顺序就乱掉了。
  • 应用中应确保业务中添加事务锁,防止并发处理同一对象。
    比如修改业务员的手机号,操作员A和操作员B同时修改业务员张三的手机号,如两人的填入手机号相同无影响,如不同,操作员A输入正确,操作员B输入错误,可能造成消费顺序乱掉,手机号修改错误。
  • 对于消费端,不能并行消费,生产者顺序发送,消费端必须顺序消费。

在实现顺序消息前提下,必须保证消息不丢失。

RabbitMQ

消息单个消费者单线程消费。

RocketMQ

RocketMQ 提供了两种顺序消息模式:全局顺序消息和分区顺序消息。

  • 全局顺序消息:适用于性能要求不高的场景,所有消息按照严格的先入先出(FIFO)的顺序来发布和消费。全局顺序消息实际上是一种特殊的分区顺序消息,即Topic中只有一个分区,因此全局顺序和分区顺序的实现原理相同。由于分区顺序消息有多个分区,所以分区顺序消息比全局顺序消息的并发度和性能更高。
  • 分区顺序消息:适用于性能要求高的场景,所有消息根据Sharding Key进行区块分区,同一个分区内的消息按照严格的先进先出(FIFO)原则进行发布和消费。同一分区内的消息保证顺序,不同分区之间的消息顺序不做要求。对于指定的一个Topic,所有消息按照严格的先入先出(FIFO)的顺序来发布和消费。分区顺序消息比全局顺序消息的并发度和性能更高。

通常情况下,RocketMQ 默认为每个 Topic 分配4个队列。队列数量是在broker的配置文件中设置的,具体来说是在broker.conf文件中通过defaultTopicQueueNums属性来设置。
如果你想为特定的Topic设置队列数量,可以使用管理工具或者控制台来完成。

mqadmin updateTopic -b brokerIP:10911 -n nameserverIP:9876 -t YourTopicName --queue-nums 8

-b 参数后面是一个broker的IP和端口,-n 参数后面是nameserver的IP和端口,-t 参数后面是你要更新的Topic名称,–queue-nums 参数后面是你想要设置的队列数量。

请注意,更改队列数量是一个影响较大的操作,因为它需要重新分配broker上的主题资源。在生产环境中,通常需要在维护窗口内进行,并确保有足够的备份和监控系统以应对可能出现的问题。


发送方使用MessageQueueSelector选择队列 :

Message msg = new Message(“OrderTopicTest”, “order_”+orderId,
“KEY” + orderId,(“order_”+orderId+" step " + j).getBytes(RemotingHelper.DEFAULT_CHARSET));
//消息队列的选择器
SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
//第一个参数:所有的消息,第二个参数:发送的消息,第三个参数:根据什么发送,这里面传的是orderId
@Override
public MessageQueue select(List mqs, Message msg, Object arg) {
Integer id = (Integer) arg;
int index = id % mqs.size();
return mqs.get(index);
}
//同一个订单id可以放到同一个队列里面去
}, orderId);

消费方使用MessageListenerOrderly选择队列 :

consumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List msgs, ConsumeOrderlyContext context) {
//自动提交
context.setAutoCommit(true);
for(MessageExt msg:msgs){
System.out.println("收到消息内容 "+new String(msg.getBody()));
}
return ConsumeOrderlyStatus.SUCCESS;
}
});

Kafka

Kafka是分布式多partition的,它会将一个topic中的消息尽可能均匀的分发到每个partition上。那么问题就来了,这样怎么保证同一个topic消息的顺序呢?

kafka可以通过partitionKey,将某类消息写入同一个partition,一个partition只能对应一个消费线程,以保证数据有序。
也就是说生产者在写消息的时候,可以指定一个 key,比如说我们指定了某个订单 id 作为 key,那么这个订单相关的数据,一定会被分发到同一个 partition 中去,而且这个 partition 中的数据一定是有顺序的。

先后两条消息发送时,前一条消息发送失败,后一条消息发送成功,然后失败的消息重试后发送成功,造成乱序。为了解决重试机制引起的消息乱序为实现Producer的幂等性,Kafka引入了Producer ID(即PID)和Sequence Number。

对于每个PID,该Producer发送消息的每个<Topic, Partition>都对应一个单调递增的Sequence Number。
同样,Broker端也会为每个<PID, Topic, Partition>维护一个序号,并且每Commit一条消息时将其对应序号递增。
对于接收的每条消息,如果其序号比Broker维护的序号大一,则Broker会接受它,否则将其丢弃
如果消息序号比Broker维护的序号差值比一大,说明中间有数据尚未写入,即乱序,此时Broker拒绝该消息
如果消息序号小于等于Broker维护的序号,说明该消息已被保存,即为重复消息,Broker直接丢弃该消息
发送失败后会重试,这样可以保证每个消息都被发送到broker

消费者从 partition 中取出来数据的时候,也一定是有顺序的。到这里,顺序还是 ok 的,没有错乱。

指定发送partition的分区:

//没有指明 partition 值但有 key 的情况下,将 key 的 hash 值与 topic 的 partition 数进行取余得到 partition 值
// 依次指定 key 值为 a,b,f ,数据 key 的 hash 值与 3 个分区求余,
//kafkaProducer.send(new ProducerRecord<>(“first”,“a”,“atguigu " + i), new Callback() {}
kafkaProducer.send(new ProducerRecord(“first”, 0, “”, “atguigu” + i)
, new Callback() {
@Override
public void onCompletion(RecordMetadata metadata, Exception e) {
if (e == null) {
System.out.println(” 主题: " +
metadata.topic() + “->” + “分区:” + metadata.partition()
);
} else {
e.printStackTrace();
}
}
});

当部分Broker宕机,会触发Reblance,导致同一Topic下的分区数量有变化,此时生产端有可能会把顺序消息发送到不同的分区,这时会发生短暂消息顺序不一致的现象,如果生产端指定分区发送,则该分区所在的Broker宕机后将直接不可用;

广播消息&集群消息

RabbitMQ

广播消息:创建一个fanout类型的交换机,并绑定多个队列,就是广播消息

集群消息:direct和topic类型的交换机就是集群模式

RocketMQ

RocketMQ主要提供了两种消费模式:集群消费以及广播消费。我们只需要在定义消费者的时候通过setMessageModel(MessageModel.XXX)方法就可以指定是集群还是广播式消费,默认是集群消费模式,即每个Consumer Group中的Consumer均摊所有的消息。

生产者:

public class MQProducer {
public static void main(String[] args) throws MQClientException, UnsupportedEncodingException, RemotingException, InterruptedException, MQBrokerException {
// 创建DefaultMQProducer类并设定生产者名称
DefaultMQProducer mqProducer = new DefaultMQProducer(“producer-group-test”);
// 设置NameServer地址,如果是集群的话,使用分号;分隔开
mqProducer.setNamesrvAddr(“10.0.91.71:9876”);
// 消息最大长度 默认4M
mqProducer.setMaxMessageSize(4096);
// 发送消息超时时间,默认3000
mqProducer.setSendMsgTimeout(3000);
// 发送消息失败重试次数,默认2
mqProducer.setRetryTimesWhenSendAsyncFailed(2);
// 启动消息生产者
mqProducer.start();
// 循环十次,发送十条消息
for (int i = 1; i <= 10; i++) {
String msg = “hello, 这是第” + i + “条同步消息”;
// 创建消息,并指定Topic(主题),Tag(标签)和消息内容
Message message = new Message(“CLUSTERING_TOPIC”, “”, msg.getBytes(RemotingHelper.DEFAULT_CHARSET));
// 发送同步消息到一个Broker,可以通过sendResult返回消息是否成功送达
SendResult sendResult = mqProducer.send(message);
System.out.println(sendResult);
}
// 如果不再发送消息,关闭Producer实例
mqProducer.shutdown();
}
}

消费者:

public class MQConsumerB {
public static void main(String[] args) throws MQClientException {
// 创建DefaultMQPushConsumer类并设定消费者名称
DefaultMQPushConsumer mqPushConsumer = new DefaultMQPushConsumer(“consumer-group-test”);
// 设置NameServer地址,如果是集群的话,使用分号;分隔开
mqPushConsumer.setNamesrvAddr(“10.0.91.71:9876”);
// 设置Consumer第一次启动是从队列头部开始消费还是队列尾部开始消费
// 如果不是第一次启动,那么按照上次消费的位置继续消费
mqPushConsumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
// 设置消费模型,集群还是广播,默认为集群
mqPushConsumer.setMessageModel(MessageModel.CLUSTERING);
// 消费者最小线程量
mqPushConsumer.setConsumeThreadMin(5);
// 消费者最大线程量
mqPushConsumer.setConsumeThreadMax(10);
// 设置一次消费消息的条数,默认是1
mqPushConsumer.setConsumeMessageBatchMaxSize(1);
// 订阅一个或者多个Topic,以及Tag来过滤需要消费的消息,如果订阅该主题下的所有tag,则使用*
mqPushConsumer.subscribe(“CLUSTERING_TOPIC”, “*”);
// 注册回调实现类来处理从broker拉取回来的消息
mqPushConsumer.registerMessageListener(new MessageListenerConcurrently() {
// 监听类实现MessageListenerConcurrently接口即可,重写consumeMessage方法接收数据
@Override
public ConsumeConcurrentlyStatus consumeMessage(List msgList, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
MessageExt messageExt = msgList.get(0);
String body = new String(messageExt.getBody(), StandardCharsets.UTF_8);
System.out.println("消费者接收到消息: " + messageExt.toString() + “—消息内容为:” + body);
// 标记该消息已经被成功消费
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
// 启动消费者实例
mqPushConsumer.start();
}
}

setMessageModel(MessageModel.CLUSTERING);//设置集群消息
setMessageModel(MessageModel.BROADCASTING); //设置广播消息

1、在Rocket集群消费模式下,(订阅)同一个主题(Topic)下的消息,对于不同的消费者组是一种“广播形式”,即每个消费者组的都会消费消息。

2、在Rocket集群消费模式下,(订阅)同一个主题(Topic)下的消息,对于相同的消费者组的消费者而言是一种集群模式,即同一个消费者组内的所有消费者均分消息并消费。

集群消费模式下,相同Consumer Group的每个Consumer实例平均分摊消息。
⼴播消费模式下,相同Consumer Group的每个Consumer实例都接收全量的消息。

案例博客

Kafka

实现与RocketMQ一致

针对Kafka同⼀条消息只能被同⼀个消费组下的某⼀个消费者消费的特性,要实现多播只要保证这些消费者属于不同的消费组即可。我们再增加⼀个消费者,该消费者属于testGroup-2消费组,结果两个客户端都能收到消息。

消息重试

重试带来的副作用
不停的重试看起来很美好,但也是有副作用的,主要包括两方面:消息重复,服务端压力增大

  • 远程调用的不确定性,因请求超时触发消息发送重试流程,此时客户端无法感知服务端的处理结果;客户端进行的消息发送重试可能会导致消费方重复消费,应该按照用户ID、业务主键等信息幂等处理消息。
  • 较多的重试次数也会增大服务端的处理压力。

RabbitMQ

消费者默认是自动提交,如果消费时出现了RuntimException,会导致消息直接重新入队,再次投递(进入队首),进入死循环,继而导致后面的消息被阻塞。消费失败的时候,消息队列会一直重复的发送消息,导致程序死循环!

消息阻塞带来的后果是:后边的消息无法被消费;RabbitMQ服务端继续接收消息,占内存和磁盘越来越多。

重试机制有2种情况

  • 消息是自动确认时,如果抛出了异常导致多次重试都失败,消息被自动确认,消息就丢失了
  • 消息是手动确认时,如果抛出了异常导致多次重试都失败,消息没被确认,也无法nack,就一直是unacked状态,导致消息积压。

重试配置 :

消费者设置手动确认提交,当不确认消息,并且设置重新放回队列的时候,这时候消息会无线的重试!

// RabbitMQ的ack机制中,第二个参数返回true,表示需要将这条消息投递给其他的消费者重新消费;
// 第三个参数true,表示这个消息会重新进入队列
//channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false);

在手动确认的条件下,使用如上配置消息在重试三次之后,就会放入死信队列,事实上手动提交的时候,basicNack的最后一个参数requeue = true时(设置重新入队列),消息会被无限次的放入消费队列重新消费,直至回送ACK。但是当requeue = false 的时候,此时消息达到重试指定次数就会后立马进入到死信队列。通过给队列绑定死信交换机DLX(basic.reject / basic.nack且requeue=false消息进入绑定的DLX),保存并处理异常或多次重试的信息。

Spring-RabbitMQ中重试实现:

spring:
rabbitmq:
listener:
simple:
acknowledge-mode: manual # 消费方手动应答
direct:
retry:
enabled: true # 开启消费重试机制
max-attempts: 3 # 最大重试机制,默认为3
initial-interval: 1000 # 重试间隔,单位毫秒,默认1000

注意:max-attempts 指的是尝试次数,就是说最开始消费的那一次也是计算在内的,那么 max-attempts: 3 便是重试两次,另外一次是正常消费,消息重试了3次,之后会抛出ListenerExecutionFailedException的异常。后面附带着Retry Policy Exhausted,提示我们重试次数已经用尽了。消息重试次数用尽后,消息就会被抛弃。

@Component
@Slf4j
public class MessageReceiver {

@Autowired
private MsgService msgService;

@Autowired
private MsgLogService msgLogService;

@RabbitListener(queues = RabbitConstants.TEST_FANOUT_QUEUE_A)
public void testMsgReceiver(Message message, Channel channel) throws IOException, InterruptedException {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
Msg msg = JSON.parseObject(message.getBody(), Msg.class);
try {
int a = 1 / 0;
channel.basicAck(deliveryTag, false);
} catch (Exception e) {
Map<String, Object> headers = message.getMessageProperties().getHeaders();
//重试次数
Integer retryCount;
String mapKey = “retry-count”;
if (!headers.containsKey(mapKey)) {
retryCount = 0;
} else {
retryCount = (Integer) headers.get(mapKey);
}
if (retryCount++ < RETRY) {
log.info(“已经重试 " + retryCount + " 次”);
headers.put(“retry-count”, retryCount);
//当消息回滚到消息队列时,这条消息不会回到队列尾部,而是仍是在队列头部。
//这时消费者会立马又接收到这条消息进行处理,接着抛出异常,进行 回滚,如此反复进行
//而比较理想的方式是,出现异常时,消息到达消息队列尾部,这样既保证消息不回丢失,又保证了正常业务的进行。
//因此我们采取的解决方案是,将消息进行应答。
//这时消息队列会删除该消息,同时我们再次发送该消息 到消息队列,这时就实现了错误消息进行消息队列尾部的方案
//1.应答
channel.basicAck(deliveryTag, false);
//2.重新发送到MQ中
AMQP.BasicProperties basicProperties = new AMQP.BasicProperties().builder().contentType(“application/json”).headers(headers).build();
channel.basicPublish(message.getMessageProperties().getReceivedExchange(),
message.getMessageProperties().getReceivedRoutingKey(), basicProperties,
message.getBody());
} else {

log.info(“现在重试次数为:” + retryCount);
/**
* 重要的操作 存盘
* 手动ack
* channel.basicAck(deliveryTag,false);
* 通知人工处理
* log.error(“重试三次异常,快来人工处理”);
*/
//消息存盘
MsgLog msgLog = new MsgLog();
msgLog.setMsgId(msg.getMsgId());
msgLog.setMsg(new String(message.getBody(),“utf-8”));
msgLog.setExchange(message.getMessageProperties().getReceivedExchange());
msgLog.setRoutingKey(message.getMessageProperties().getReceivedRoutingKey());
msgLog.setTryCount(retryCount);
msgLog.setStatus(MsgLogStatusEnum.FAIL.getStatus());
msgLogService.save(msgLog);

/**
* 不重要的操作放入 死信队列
* 消息异常处理:消费出现异常后,延时几秒,然后从新入队列消费,直到达到ttl超时时间,再转到死信,证明这个信息有问题需要人工干预
*/
//休眠2s 延迟写入队列,触发转入死信队列
//Thread.sleep(2000);
//channel.basicNack(deliveryTag, false, true);
}
}
}

@RabbitListener(queues = RabbitConstants.TEST_DDL_QUEUE_A)
public void deadTestReceiver(Message message, Channel channel) throws IOException {
log.info(“消息将放入死信队列”, new String(message.getBody(), “UTF-8”));
String str = new String(message.getBody());
//转换为消息实体
Msg msg = JSON.parseObject(str, Msg.class);
log.info(“收到的消息为{}”, msg);
}
}

处理unacked消息: consumer前面加了一个缓冲容器,容器能容纳最大的消息数量就是PrefetchCount。如果容器没有满RabbitMQ就会将消息投递到容器内,如果满了就不投递了。当consumer对消息进行ack以后就会将此消息移除,从而放入新的消息。

重试机制

Spring-Rabbitmq配置重试
Spring-Rabbitmq重试机制

RockeMq

Producer端重试:
消息发送时,默认情况下会进行2次重试。如果重试次数达到上限,消息将不会被再次发送。

重试配置 :

DefaultMQProducer producer = new DefaultMQProducer(“ordermessage”);
producer.setRetryTimesWhenSendFailed(x)同步,参数默认值是2
producer.setRetryTimesWhenSendAsyncFailed()异步,参数默认值是2

Consumer端重试:

  • 当 Consumer 端遇到异常时,消息通常会重复重试16次,重试的时间间隔包括10秒、30秒、1分钟、2分钟、3分钟等。
  • 如果 Consumer 端没有返回 ConsumeConcurrentlyStatus.CONSUME_SUCCESS 或 ConsumeConcurrentlyStatus.RECONSUME_LATER,且消息没有消费成功,MQ 会无限制地发送给消费端,直到达到最大重试次数。
  • 在集群模式下,如果消费业务逻辑代码返回Action.ReconsumerLater、NULL 或抛出异常,消息最多会重试16次。如果重试16次后消息仍然失败,则会被丢弃。
  • 消息队列 RocketMQ 默认允许每条消息最多重试16次,每次重试的间隔时间根据配置的间隔而变化。如果消息在16次重试后仍然失败,则不再投递该消息。理论上,如果消息持续失败,最长可能需要4小时46分钟内完成这16次重试。

Consumer 消费失败,这里有 3 种情况,消费者重试示例 :

public class MessageListenerImpl implements MessageListener {
@Override
public Action consume(Message message, ConsumeContext context) {
//处理消息
doConsumeMessage(message);
//方式1:返回 Action.ReconsumeLater,消息将重试
return Action.ReconsumeLater;
//方式2:返回 null,消息将重试
return null;
//方式3:直接抛出异常, 消息将重试
throw new RuntimeException(“Consumer Message exceotion”);
}
}

如果希望消费失败后不重试,可以直接返回Action.CommitMessage

若达到最大重试次数后消息还没有成功被消费,消息将不会被再次发送,则消息将被投递至死信队列。

消息重试只针对集群消费模式生效;广播消费模式不提供失败重试特性,即消费失败后,失败消息不再重试,继续消费新的消息。在老版本的RocketMQ中,一条消息无论重试多少次,这些重试消息的MessageId始终都是一样的。但是在4.7.1版本中,每次重试MessageId都会重建。

通过consumer.setMaxReconsumeTimes(20);将重试次数设定为20次。当定制的重试次数超过16次后,消息的重试时间间隔均为2小时。

Properties properties = new Properties();
// 配置对应 Group ID的最大消息重试次数为 20 次
properties.put(PropertyKeyConst.MaxReconsumeTimes, “20”);
Consumer consumer =ONSFactory.createConsumer(properties);

或者:

最大重试次数:消息消费失败后,可被重复投递的最大次数。

consumer.setMaxReconsumeTimes(10);

重试间隔:消息消费失败后再次被投递给Consumer消费的间隔时间,只在顺序消费中起作用。

consumer.setSuspendCurrentQueueTimeMillis(5000);

顺序消费和并发消费的重试机制并不相同,顺序消费消费失败后会先在客户端本地重试直到最大重试次数,这样可以避免消费失败的消息被跳过,消费下一条消息而打乱顺序消费的顺序,而并发消费消费失败后会将消费失败的消息重新投递回服务端(在一个特色的队列中保存),再等待服务端重新投递回来,在这期间会正常消费队列后面的消息。

并发消费失败后并不是投递回原Topic,而是投递到一个特殊Topic,其命名为%RETRY%$ConsumerGroupName,$ConsumerGroupName为消费组的名称,集群模式下并发消费每一个ConsumerGroup会对应一个特殊Topic,并会默认订阅该Topic

消费类型重试间隔最大重试次数
顺序消费间隔时间可通过自定义设置,SuspendCurrentQueueTimeMillis最大重试次数可通过自定义参数MaxReconsumeTimes取值进行配置。该参数取值无最大限制。若未设置参数值,默认最大重试次数为Integer.MAX
并发消费间隔时间根据重试次数阶梯变化,取值范围:1秒~2小时。不支持自定义指定时间配置最大重试次数可通过自定义参数MaxReconsumeTimes取值进行配置。默认值为16次,该参数取值无最大限制,建议使用默认值

(1)重试队列:如果Consumer端因为各种类型异常导致本次消费失败,为防止该消息丢失而需要将其重新回发给Broker端保存,保存这种因为异常无法正常消费而回发给MQ的消息队列称之为重试队列。
考虑到异常恢复需要一些时间,RocketMQ会为重试队列设置多个重试级别,每个重试级别都有与之对应的重新投递延时,重试次数越多投递延时就越大。
RocketMQ会为每个消费组都设置一个Topic名称为“%RETRY%+consumerGroup”的重试队列(这里需要注意的是,这个Topic的重试队列是针对消费组,而不是针对每个Topic设置的),用于暂时保存因为各种异常而导致Consumer端无法消费的消息。
考虑到异常恢复起来需要一些时间,会为重试队列设置多个重试级别,每个重试级别都有与之对应的重新投递延时,重试次数越多投递延时就越大。RocketMQ对于重试消息的处理是先保存至Topic名称为“SCHEDULE_TOPIC_XXXX”的延迟队列中,后台定时任务按照对应的时间进行Delay后重新保存至“%RETRY%+consumerGroup”的重试队列中。

(2)死信队列:由于有些原因导致Consumer端长时间的无法正常消费从Broker端Pull过来的业务消息,为了确保消息不会被无故的丢弃,那么超过配置的“最大重试消费次数”后就会移入到这个死信队列中。在RocketMQ中,SubscriptionGroupConfig配置常量默认地设置了两个参数,一个是retryQueueNums为1(重试队列数量为1个),另外一个是retryMaxTimes为16(最大重试消费的次数为16次)。Broker端通过校验判断,如果超过了最大重试消费次数则会将消息移至这里所说的死信队列。这里,RocketMQ会为每个消费组都设置一个Topic命名为“%DLQ%+consumerGroup"的死信队列。一般在实际应用中,移入至死信队列的消息,需要人工干预处理;

重试队列&死信队列

官方文档

中文示例文档

重试配置

Kafka

生产者重试:

生产者设置重试次数。比如:retries>=3,增加重试次数以保证消息的不丢失;如果设置了重试,还想保证消息的有序性,需要设置MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION=1,否则在重试此失败消息的时候,其他的消息可能发送成功了。

// 1. 创建 kafka 生产者的配置对象
Properties properties = new Properties();
// 设置 acks
properties.put(ProducerConfig.ACKS_CONFIG, “all”);
// 重试次数 retries,默认是 int 最大值,2147483647
properties.put(ProducerConfig.RETRIES_CONFIG, 3);
// 3. 创建 kafka 生产者对象
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<String, String>(properties);

kafka消费者重试需要自己实现:

kafka消费者手动提交offset的方法有两种:分别是commitSync(同步提交)和commitAsync(异步提交)。

  • commitSync(同步提交):必须等待offset提交完毕,再去消费下一批数据。
  • commitAsync(异步提交) :发送完提交offset请求后,就开始消费下一批数据了。

两者的相同点是,都会将本次提交的一批数据最高的偏移量提交;不同点是,同步提交阻塞当前线程,一直到提交成
功,并且会自动失败重试(由不可控因素导致,也会出现提交失败);而异步提交则没有失败重试机制,故有可能提交失败。

kafka消费者重试后选择操作:

  • kafka消费者重试会导致的问题:问题是若是这条消息(通过目前的代码)可能永远不能消费成功,导致消费者不会继续处理后续的任何问题,导致消费者阻塞;
  • 跳过,跳过这条没有消费的消息;这个其实是不合理的,可能会造成数据不一致性;

例如设置为同步提交并try…catch

// 是否自动提交 offset
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
//3. 创建 kafka 消费者
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(properties);
try {
consumer.commitSync();// 处理成功,同步提交 offset ;会阻塞当前线程
} catch (Exception e) {
// 处理失败,不提交offset,消息会重试
e.printStackTrace();
// 判断当前重试次数,超过一定次数放入指定重试队列
}finally {
consumer.close();
}
// 异步提交 offset
//consumer.commitAsync();

消费者重试解决方案:

需要建立一个专门用于重试的topic(retry topic);当消费者没有正确消费这条消息的时候,则将这条消息转发(发布)到重试主题(retry topic)上,然后提交消息的偏移量,以便继续处理下一个消息;
注意:这个时候,这个没有正确消费的消息,其实对于这个消费者来说,也算是消费完成了,因为也正常提交了偏移量,只不过是业务没有正确处理,而且这个消息被发布到另一个topic中了(retry topic);顺序消息慎用

之后再创建一个重试消费者,用于订阅这个重试主题,只不过这个重试消费者,跟之前那个消费者处理相同的业务,两个逻辑是一样的;
如果这个重试消费者,也无法小得这条消息,那就把这个消息发布到另一个重试主题上,并提交该消息的偏移量;
一直循环,递归发送到不同的重试队列,最后,当创建了很多重试消费者的时候,在最终重试消费者无法处理某条消息后,把该消息发布到一个死信队列。

优缺点:
优点:可以重试,可能在重试中,就正常消费了;
缺点:可能一直重试,都不会正常消费,一直到死信队列中;可能我们就是消费的是一个错误的消息,比如说缺字段,或者数据库中根本就没有这个业务的id;或者消息中包含特殊字段,导致无法消费;这种是消息本身的错误,导致无法消费,除非你去解决消息,不然是永远不会成功的;

重试示例原文


在Spring-Kafka中,消费失败的重试次数可以通过配置来实现。默认情况下,当使用Spring-Kafka时,如果Consumer消费失败,会尝试重新消费最多10次,直到达到配置的重试次数。

Spring-Kafka3.0 版本默认失败重试次数为10次,准确讲应该是1次正常调用+9次重试,这个在这个类可以看到org.springframework.kafka.listener.SeekUtils

在Spring Boot的application.properties或application.yml文件中添加以下配置:

spring.kafka.consumer.retries: 10

这将把重试次数设置为10次。

@KafkaListener(topics = “your_topic”, retryTemplate = @RetryTemplate(maxRetries = 10))
public void consumeMessage(String message) {
// 处理消息的逻辑
}

要实现失败重试10次,可以考虑以下方案:

使用Kafka的自动提交模式,并设置auto.commit.interval.ms为适当的值,以便在每次消费消息后自动提交偏移量。
在代码中手动控制消费流程,每次消费消息后手动提交偏移量。
使用Kafka的消费者API提供的commitSync方法手动提交偏移量,并捕获可能抛出的异常,以便在失败时进行重试。
在代码中设置重试机制,例如使用循环语句实现重试10次的功能。
需要注意的是,在实现失败重试时,需要确保重试不会导致消息被重复消费或产生死循环等问题。因此,建议在重试时设置适当的间隔时间、限制重试次数或在重试前先检查消息的状态等措施。

Kafka中实现重试的主要方式是使用生产者-消费者模型。

生产者负责将消息发送到Kafka,如果发送失败,生产者会根据错误类型判断是否可以重试。如果可以重试,它将重新发送消息到Kafka。

消费者从Kafka中读取消息并处理,如果处理失败,消费者可以选择将消息放回队列(即进行重试)。消费者可以选择将消息放回队列中的某一个位置(例如队尾、队首或其他位置),以便在重试时能够按照不同的策略处理失败的消息。

值得注意的是,如果消费者重试时仍然失败,可能需要采取其他措施,例如将消息发送到另一个Kafka主题(topic)中进行处理,或者将消息写入到其他存储系统中等等。

Kafka重试使用示例场景

Spring-Kafka消费端消费重试和死信队列

Spring-Kafka 3.0 消费者消费失败处理方案

kafka自定义消息重试

所有MQ消息重试注意 :
虽然增加重试次数可以提高消息的可靠性,但过度的重试可能会导致消息处理的延迟和资源的浪费。因此,需要根据实际情况和业务需求进行权衡和调整。

回退队列

延时消息

RabbitMQ

死信和延迟使用示例

Rabbitmq 插件实现延迟队列

如果使用在消息属性上设置 TTL 的方式,消息可能并不会按时“死亡“,因为 RabbitMQ 只会检查第一个消息是否过期,如果过期则丢到死信队列,如果第一个消息的延时时长很长,而第二个消息的延时时长很短,第二个消息并不会优先得到执行。

官网上下载,下载rabbitmq_delayed_message_exchange 插件,然后解压放置到 RabbitMQ 的插件目录。
进入 RabbitMQ 的安装目录下的 plgins 目录,执行下面命令让该插件生效,然后重启 RabbitMQ

/usr/lib/rabbitmq/lib/rabbitmq_server-3.8.8/plugins rabbitmq-plugins enable rabbitmq_delayed_message_exchang

RocketMQ

定时消息(延迟队列)是指消息发送到broker后,不会立即被消费,等待特定时间投递给真正的topic。RocketMQ 支持发送延迟消息,但不支持任意时间的延迟消息的设置,仅支持内置预设值的延迟时间间隔的延迟消息;预设值的延迟时间间隔。
发消息时,设置delayLevel等级即可:msg.setDelayLevel(level)。

messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h

定时消息会暂存在名为SCHEDULE_TOPIC_XXXX的topic中,并根据delayTimeLevel存入特定的queue,queueId = delayTimeLevel – 1,即一个queue只存相同延迟的消息,保证具有相同发送延迟的消息能够顺序消费。broker会调度地消费SCHEDULE_TOPIC_XXXX,将消息写入真实的topic。

Message msg = new Message(“TopicTest”,“TagA” ,
("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
);
//messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
msg.setDelayTimeLevel(3);
SendResult sendResult = producer.send(msg);

Kafka

Kafka没有直接的延迟消息设置
Kafka延迟发送的解决思路:利用Redis的ZSet集合,实现Redis缓存队列

生产者在调用延迟发送方法时,消息并不会立刻被投递到Topic中,转而发送到延迟队列

将当前时间戳与延迟时间进行相加,将结果作为ZSet的score进行设置。

除此之外,延迟队列有包含线程池、分布式锁。每5s循环一次,对比当前时间戳与ZSet的score。拉取缓存队列中到期的消息,将消息重新组装,投递到Topic并进行消费。

Kafka延迟消息实现代码

死信队列

RabbitMQ

死信的来源

  • 消息 TTL 过期
  • 队列达到最大长度(队列满了,无法再添加数据到 mq 中)
  • 消息被拒绝(basic.reject 或 basic.nack)并且 requeue=false(不再重新入队)

需要给交换机设置绑定死信队列,并为死信队列设置专门的消费者。

Channel channel = RabbitMqUtils.getChannel();

//声明死信和普通交换机 类型为 direct
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
channel.exchangeDeclare(DEAD_EXCHANGE,BuiltinExchangeType.DIRECT);

//声明死信队列
String deadQueue = “dead-queue”;
//死信队列绑定死信交换机与 routingkey
channel.queueBind(deadQueue,DEAD_EXCHANGE,“lisi”);
//正常队列绑定死信队列信息
Map<String, Object> params = new HashMap<>();
//正常队列设置死信交换机 参数 key 是固定值
params.put(“x-dead-letter-exchange”, DEAD_EXCHANGE);
//正常队列设置死信 routing-key 参数 key 是固定值
params.put(“x-dead-letter-routing-key”, “lisi”);
channel.queueDeclare(queuelame, true, false, false,agruments);

使用示例

死信和延迟使用示例

RocketMQ

当一条消息初次消费失败,消息队列 RocketMQ 会自动进行消息重试;达到最大重试次数后(RocketMQ对于失败次数超过16次的消息设置为死信消息),若消费依然失败,则表明消费者在正常情况下无法正确地消费该消息,此时,消息队列 RocketMQ 不会立刻将消息丢弃,而是将其发送到该消费者对应的特殊队列中。

在消息队列 RocketMQ 中,这种正常情况下无法被消费的消息称为死信消息(Dead-Letter Message),存储死信消息的特殊队列称为死信队列(Dead-Letter Queue)。

RocketMQ死信队列具有如下特征:

  • 死信队列中的消息不会再被消费者正常消费,即DLQ对于消费者是不可见的
  • 死信存储有效期与正常消息相同,均为3天,3 天后会被自动删除。因此,请在死信消息产生后的 3 天内及时处理。(commitLog文件的过期时间)
  • 死信队列就是一个特殊的Topic,名称为%DLQ%consumerGroup,即每个消费者组都有一个死信队列
  • 如果一个消费者组未产生死信消息,则不会为其创建对应的死信队列

死信队列:

  • 一个死信队列对应一个 Group ID, 而不是对应单个消费者实例。
  • 如果一个 Group ID 未产生死信消息,消息队列 RocketMQ 不会为其创建相应的死信队列。
  • 一个死信队列包含了对应 Group ID 产生的所有死信消息,不论该消息属于哪个 Topic。
  • 死信队列是一个特殊的Topic,名称为%DLQ%consumerGroup

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

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

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

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

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

如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注大数据获取)
img

死信消息),若消费依然失败,则表明消费者在正常情况下无法正确地消费该消息,此时,消息队列 RocketMQ 不会立刻将消息丢弃,而是将其发送到该消费者对应的特殊队列中。

在消息队列 RocketMQ 中,这种正常情况下无法被消费的消息称为死信消息(Dead-Letter Message),存储死信消息的特殊队列称为死信队列(Dead-Letter Queue)。

RocketMQ死信队列具有如下特征:

  • 死信队列中的消息不会再被消费者正常消费,即DLQ对于消费者是不可见的
  • 死信存储有效期与正常消息相同,均为3天,3 天后会被自动删除。因此,请在死信消息产生后的 3 天内及时处理。(commitLog文件的过期时间)
  • 死信队列就是一个特殊的Topic,名称为%DLQ%consumerGroup,即每个消费者组都有一个死信队列
  • 如果一个消费者组未产生死信消息,则不会为其创建对应的死信队列

死信队列:

  • 一个死信队列对应一个 Group ID, 而不是对应单个消费者实例。
  • 如果一个 Group ID 未产生死信消息,消息队列 RocketMQ 不会为其创建相应的死信队列。
  • 一个死信队列包含了对应 Group ID 产生的所有死信消息,不论该消息属于哪个 Topic。
  • 死信队列是一个特殊的Topic,名称为%DLQ%consumerGroup

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

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

因此收集整理了一份《2024年大数据全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
[外链图片转存中…(img-oQzMaZlJ-1712528685572)]
[外链图片转存中…(img-i3GQKG4c-1712528685572)]
[外链图片转存中…(img-DaB5XTut-1712528685573)]
[外链图片转存中…(img-oyaaMLQp-1712528685573)]
[外链图片转存中…(img-BU1W2zLD-1712528685573)]

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

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

如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注大数据获取)
[外链图片转存中…(img-X1zLBrqa-1712528685574)]

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值