Spring整合Kafka(十七)----处理异常_defaulterrorhandler

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

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

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

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

如果你需要这些资料,可以添加V获取:vip204888 (备注大数据)
img

正文

}

}


当容器配置为“AckMode.MMANUAL\_IMMEDIATE”时,可以配置错误处理程序来提交已恢复记录的偏移量;将commitRecovered属性设置为true。  
 另请参见[发布死信记录]( )。  
 使用事务时,DefaultAfterRollbackProcessor也提供类似的功能。请参阅[回滚后处理器]( )。  
 DefaultErrorHandler认为某些异常是fatal的,并跳过对此类异常的重试;在第一次失败时调用恢复器。默认情况下,被视为fatal的异常有:


* DeserializationException
* MessageConversionException
* ConversionException
* MethodArgumentResolutionException
* NoSuchMethodException
* ClassCastException  
 因为这些异常不太可能在重试delivery时得到解决。  
 你可以将更多异常类型添加到不可重试类别,或者完全替换已分类异常的映射。有关更多信息,请参阅DefaultErrorHandler.addNotRetryableException()和DefaultErrorHandler.setClassifications()的Javadocs,以及spring-retry BinaryExceptionClassifier的那些文档。  
 以下是一个将IllegalArgumentException添加到不可重试异常中的示例:



@Bean
public DefaultErrorHandler errorHandler(ConsumerRecordRecoverer recoverer) {
DefaultErrorHandler handler = new DefaultErrorHandler(recoverer);
handler.addNotRetryableExceptions(IllegalArgumentException.class);
return handler;
}


错误处理程序可以配置一个或多个RetryListener,接收重试和恢复进度的通知。



@FunctionalInterface
public interface RetryListener {

void failedDelivery(ConsumerRecord<?, ?> record, Exception ex, int deliveryAttempt);

default void recovered(ConsumerRecord<?, ?> record, Exception ex) {
}

default void recoveryFailed(ConsumerRecord<?, ?> record, Exception original, Exception failure) {
}

default void failedDelivery(ConsumerRecords<?, ?> records, Exception ex, int deliveryAttempt) {
}

default void recovered(ConsumerRecords<?, ?> records, Exception ex) {
}

default void recoveryFailed(ConsumerRecords<?, ?> records, Exception original, Exception failure) {
}

}


如果恢复器失败(引发异常),则失败的记录将包含在seek中。如果恢复器失败,默认情况下将重置BackOff,并且在再次尝试恢复之前,重新deliver将再次执行。若要在恢复失败后跳过重试,请将错误处理程序的resetStateOnRecoveryFailure设置为false。  
 你可以为错误处理程序提供BiFunction<ConsumerRecord<?, ?>, Exception, BackOff>,根据失败的记录或异常来确定要使用的BackOff:



handler.setBackOffFunction((record, ex) -> { … });


如果函数返回null,则将使用处理程序的默认BackOff。  
 将resetStateOnExceptionChange设置为true,如果异常类型在每次失败中发生变化,则重试序列将重新启动(包括选择新的BackOff,如果已配置)。如果为false,则不考虑异常类型。  
 另请参阅[投递尝试头Delivery Attempts Header]( )。


## 五、使用batch错误处理程序的转换错误Conversion Errors with Batch Error Handlers


将MessageConverter与ByteArrayDeserializer、BytesDeserializer或StringDeserializer以及DefaultErrorHandler一起使用时,batch监听器现在可以正确处理转换错误。当发生转换错误时,payload被设置为null,并且反序列化异常被添加到record header中,类似于ErrorHandlingDeserializer。监听器中有ConversionException的列表,因此监听器可以抛出BatchListenerFailedException,指出发生转换异常的第一个索引。



@KafkaListener(id = “test”, topics = “topic”)
void listen(List in, @Header(KafkaHeaders.CONVERSION_FAILURES) List exceptions) {
for (int i = 0; i < in.size(); i++) {
Foo foo = in.get(i);
if (foo == null && exceptions.get(i) != null) {
throw new BatchListenerFailedException(“Conversion error”, exceptions.get(i), i);
}
process(foo);
}
}


## 六、重试完整批Retrying Complete Batches


Retrying Complete Batches是batch监听器的DefaultErrorHandler的fallback行为,其中监听器抛出了除BatchListenerFailedException以外的异常。  
 在重新deliver batch时,无法保证该批次具有相同数量的记录和重新deliver的记录的顺序相同。因此,不可能轻松地维护batch的重试状态。FallbackBatchErrorHandler采用以下方法。如果batch 监听器抛出的异常不是BatchListenerFailedException,则会从内存中的batch记录中执行重试。为了避免在扩展的重试序列中发生rebalance,错误处理程序暂停consumer,在每次重试时,在睡眠前对其进行poll以获取back off,然后再次调用监听器。当重试次数用完时,会为batch中的每个记录调用ConsumerRecordRecoverer。如果恢复器抛出异常,或者线程在睡眠期间中断,那么该批记录将在下一次poll时重新deliver。在退出之前,无论结果如何,都会恢复consumer。  
 此机制不能用于事务。  
 在BackOff的间隔等待时,错误处理程序将以短暂的睡眠循环,直到达到所需的延迟,同时检查容器是否已停止,从而允许睡眠在stop()之后很快退出,而不导致延迟。


## 七、容器停止错误处理程序Container Stopping Error Handlers


如果监听器抛出异常,CommonContainerStoppingErrorHandler会停止容器。对于record 监听器,当AckMode为RECORD时,将提交已处理记录的偏移量。对于record 监听器,当AckMode为任何手动值时,将提交已acknowledge记录的偏移量。对于batch监听器,当AckMode为BATCH时,在容器重新启动时会replay整个batch。  
 容器停止后,将引发一个包装了ListenerExecutionFailedException的异常。这是为了使事务回滚(如果启用了事务)。


## 八、委托错误处理程序Delegating Error Handler


CommonDelegatingErrorHandler可以根据异常类型委托给不同的错误处理程序。例如,你可能希望为大多数异常调用DefaultErrorHandler,然后为其他异常调用CommonContainerStoppingErrorHandler。


## 九、日志错误处理程序Logging Error Handler


CommonLoggingErrorHandler只是在日志记录异常;当使用record监听器,先前轮询的剩余记录会被传递给监听器。对于batch监听器,batch中的所有records将被记录。


## 十、为record和batch监听器使用不同的常见错误处理程序Using Different Common Error Handlers for Record and Batch Listeners


如果希望对record和batch监听器使用不同的错误处理策略,框架提供了CommonMixedErrorHandler,允许为每种监听器类型配置特定的错误处理程序。


## 十一、常见错误处理程序摘要Common Error Handler Summary


* DefaultErrorHandler
* CommonContainerStoppingErrorHandler
* CommonDelegatingErrorHandler
* CommonLoggingErrorHandler
* CommonMixedErrorHandler


## 十二、遗留错误处理程序及其替代Legacy Error Handlers and Their Replacements




| 遗留错误处理程序 | 替代 |
| --- | --- |
| LoggingErrorHandler | CommonLoggingErrorHandler |
| BatchLoggingErrorHandler | CommonLoggingErrorHandler |
| ConditionalDelegatingErrorHandler | DelegatingErrorHandler |
| ConditionalDelegatingBatchErrorHandler | DelegatingErrorHandler |
| ContainerStoppingErrorHandler | CommonContainerStoppingErrorHandler |
| ContainerStoppingBatchErrorHandler | CommonContainerStoppingErrorHandler |
| SeekToCurrentErrorHandler | DefaultErrorHandler |
| SeekToCurrentBatchErrorHandler | 没有替换,使用带有无限BackOff的DefaultErrorHandler。 |
| RecoveringBatchErrorHandler | DefaultErrorHandler |
| RetryingBatchErrorHandler | 没有替换,使用DefaultErrorHandler并抛出BatchListenerFailedException以外的异常。 |


### 12.1 将自定义遗留错误处理程序实现迁移到CommonErrorHandler Migrating Custom Legacy Error Handler Implementations to CommonErrorHandler


请参阅CommonErrorHandler中的javadocs。  
 要替换ErrorHandler或ConsumerAwareErrorHandler实现,你应该实现handleOne(),并让seeksAfterHandle()返回false(默认值)。你还应该实现handleOtherException(),以处理发生在记录处理范围之外的异常(例如consumer错误)。  
 要替换RemainingRecordsErrorHandler实现,应实现handleRemaining()并重写seeksAfterHandle()以返回true(错误处理程序必须执行必要的seek)。你还应该实现handleOtherException(),以处理发生在记录处理范围之外的异常(例如consumer错误)。  
 要替换任何BatchErrorHandler实现,你应该实现handleBatch()。你还应该实现handleOtherException(),以处理发生在记录处理范围之外的异常(例如consumer错误)。


## 十三、rollback之后处理器After-rollback Processor


在使用事务时,如果监听器抛出异常(如果存在错误处理程序,则抛出异常),则事务将回滚。默认情况下,任何未处理的记录(包括失败的记录)都会在下一次轮询中重新提取。这是通过在DefaultAfterRollbackProcessor中执行seek操作来实现的。使用batch监听器,将重新处理整批记录(容器不知道batch中的哪条记录失败)。要修改此行为,可以使用自定义的AfterRollbackProcessor配置监听器容器。例如,对于record-based的监听器,你可能希望跟踪失败的记录,并在多次尝试后放弃,也许可以将其发布到一个死信(dead-letter)主题。  
 DefaultAfterRollbackProcessor现在可以恢复(跳过)不断失败的记录。默认情况下,在十次失败后,会记录失败的记录(ERROR 级别)。你可以使用自定义恢复程序(BiConsumer)和最大失败数来配置processor。将maxFailures属性设置为负数会导致无限次重试。以下示例配置三次尝试后的恢复:



AfterRollbackProcessor<String, String> processor =
new DefaultAfterRollbackProcessor((record, exception) -> {
// recover after 3 failures, with no back off - e.g. send to a dead-letter topic
}, new FixedBackOff(0L, 2L));


当你不使用事务时,你可以通过配置DefaultErrorHandler来实现类似的功能。请参阅[容器错误处理程序]( )。  
 使用batch监听器无法进行恢复(Recovery ),因为框架不知道batch中的哪条记录一直在失败。在这种情况下,应用程序监听器必须处理不断失败的记录。  
 另请参见[发布死信记录]( )。  
 DefaultAfterRollbackProcessor可以在新事务中调用(在失败事务回滚后启动)。然后,如果你使用DeadLetterPublishingRecoverer发布失败的记录,则processor会将恢复的记录在原始主题/分区中的偏移量发送到事务。要启用此功能,请在DefaultAfterRollbackProcessor上设置commitRecovered和kafkaTemplate属性。  
 如果恢复器失败(引发异常),则失败的记录将包含在seek中。如果恢复器失败,默认情况下将重置BackOff,并且在再次尝试恢复之前,redeliveries将再次经历back offs。在早期版本中,BackOff并未重置,在下一次失败时会重新尝试恢复。要恢复到以前的行为,请将processor的resetStateOnRecoveryFailure属性设置为false。  
 你现在可以为processor提供BiFunction<ConsumerRecord<?, ?>, Exception, BackOff>,根据失败的记录或异常来确定要使用的BackOff:



handler.setBackOffFunction((record, ex) -> { … }


如果函数返回null,则将使用processor的默认BackOff。  
 将resetStateOnExceptionChange设置为true,如果异常类型在各失败之间发生变化,则重试序列将重新启动(包括选择新的BackOff,如果配置了此选项)。默认情况下,不考虑异常类型。


从2.3.1版本开始,类似于DefaultErrorHandler,DefaultAfterRollbackProcessor认为某些异常是fatal的,并跳过对此类异常的重试;在第一次失败时调用恢复器。默认情况下,被视为fatal的异常有:


* DeserializationException
* MessageConversionException
* ConversionException
* MethodArgumentResolutionException
* NoSuchMethodException
* ClassCastException


因为这些异常不太可能在重试delivery时得到解决。  
 你可以将更多异常类型添加到不可重试类别,或者完全替换已分类异常的映射。有关更多信息,请参阅DefaultAfterRollbackProcessor.setClassifications()的Javadocs,以及spring-retry BinaryExceptionClassifier的Javadocs。  
 以下是一个将IllegalArgumentException添加到不可重试异常中的示例:



@Bean
public DefaultAfterRollbackProcessor errorHandler(BiConsumer<ConsumerRecord<?, ?>, Exception> recoverer) {
DefaultAfterRollbackProcessor processor = new DefaultAfterRollbackProcessor(recoverer);
processor.addNotRetryableException(IllegalArgumentException.class);
return processor;
}


另请参阅[投递尝试头]( )。  
 对于当前kafka-clients,容器无法检测ProducerFencedException是由再平衡(rebalance)引起的,还是生产者的transactional.id因超时或过期而被吊销。因为在大多数情况下,它是由再平衡引起的,所以容器不会调用AfterRollbackProcessor(因为我们不再被分配分区,所以不适合seek分区)。如果你确保超时足够大,可以处理每个事务,并定期执行“空”事务(例如通过ListenerContainerIdleEvent),则可以避免由于超时和过期而设置围栏(fencing)。或者,你可以将stopContainerWhenFenced容器属性设置为true,容器将停止,从而避免记录丢失。你可以使用ConsumerStoppedEvent并检查Reason的属性FENCED来检测此情况。由于该事件还引用了容器,因此可以使用此事件重新启动容器。  
 在等待BackOff间隔时,错误处理程序将以短暂的睡眠循环,直到达到所需的延迟,同时检查容器是否已停止,从而允许睡眠在stop()之后很快退出,而不是导致延迟。  
 processor可以配置一个或多个RetryListener,接收重试和恢复进度的通知。



@FunctionalInterface
public interface RetryListener {

void failedDelivery(ConsumerRecord<?, ?> record, Exception ex, int deliveryAttempt);

default void recovered(ConsumerRecord<?, ?> record, Exception ex) {
}

default void recoveryFailed(ConsumerRecord<?, ?> record, Exception original, Exception failure) {
}

}


## 十四、投递尝试头Delivery Attempts Header


以下内容仅适用于record监听器,不适用于batch监听器。  
 当使用实现DeliveryAttemptAware的ErrorHandler或AfterRollbackProcessor时,可以启用向记录添加“KafkaHeaders.DELIVERY\_ATTEMPT” 的header(kafka\_deliveryAttempt)。此header的值是一个从1开始的递增整数。收到原始ConsumerRecord<?, ?>时整数在byte[4]中。



int delivery = ByteBuffer.wrap(record.headers()
.lastHeader(KafkaHeaders.DELIVERY_ATTEMPT).value())
.getInt()


当将@KafkaListener与DefaultKafkaHeaderMapper或SimpleKafkaHeaderMapper一起使用时,可以通过在listener方法中添加@Header(KafkaHeads.DELIVERY\_ATTEMPT)int DELIVERY作为参数来获得。  
 若要启用此header的填充,请将容器属性deliveryAttemptHeader设置为true。默认情况下,它是禁用的,以避免查找每条记录的状态和添加header的开销。  
 DefaultErrorHandler和DefaultAfterRollbackProcessor支持此功能。


## 十五、监听信息头Listener Info Header


在某些情况下,能够知道监听器在哪个容器中运行是很有用的。  
 你现在可以在监听器容器上设置listenerInfo属性,或者在@KafkaListener注解上设置info属性。然后,容器会将其添加到所有传入消息的“KafkaListener.LISTENER\_INFO”头中;然后,它可以用于record拦截器、过滤器等,也可以用于监听器本身。



@KafkaListener(id = “something”, topic = “topic”, filter = “someFilter”,
info = “this is the something listener”)
public void listen2(@Payload Thing thing,
@Header(KafkaHeaders.LISTENER_INFO) String listenerInfo) {


在RecordInterceptor或RecordFilterStrategy实现中使用时,header在consumer 记录中作为字节数组,使用KafkaListenerAnnotationBeanPostProcessor的charSet属性进行转换。  
 当从consumer记录创建MessageHeaders时,header映射器也会转换为String,并且从不将此header映射到出站(outbound)记录上。  
 对于POJO batch监听器,header被复制到batch的每个成员中,并且在转换后也可以作为单个String参数使用。



@KafkaListener(id = “list2”, topics = “someTopic”, containerFactory = “batchFactory”,
info = “info for batch”)
public void listen(List list,
@Header(KafkaHeaders.RECEIVED_KEY) List keys,
@Header(KafkaHeaders.RECEIVED_PARTITION) List partitions,
@Header(KafkaHeaders.RECEIVED_TOPIC) List topics,
@Header(KafkaHeaders.OFFSET) List offsets,
@Header(KafkaHeaders.LISTENER_INFO) String info) {

}


如果batch监听器有一个过滤器,并且该过滤器导致一个空batch,则需要将required=false添加到@Header参数中,因为该info对于空batch不可用。  
 如果你收到List<Message>,则信息在每个Message<?>的“KafkaHeaders.LISTENER\_info”header中。  
 有关消费batch的详细信息,请参见[batch监听器]( )。


## 十六、发布死信记录Publishing Dead-letter Records


当达到记录的最大失败次数时,可以使用记录恢复器配置DefaultErrorHandler和DefaultAfterRollbackProcessor。框架提供了DeadLetterPublishingRecoverer,它将失败的消息发布到另一个topic。recoverer需要一个KafkaTemplate<Object, Object>,用于发送记录。你也可以选择使用“BiFunction<ConsumerRecord<?, ?>, Exception, TopicPartition>”来配置它,该函数用于解析目标主题和分区。  
 默认情况下,死信记录被发送到名为“.DLT”的主题(原始主题名加.DLT后缀)以及与原始记录相同的分区。因此,当您使用默认的解析器时,死信主题的分区数必须至少与原始主题的分区数量相同。


如果返回的TopicPartition有一个负分区,那么ProducerRecord中没有设置该分区,所以该分区是由Kafka选择的。任何ListenerExecutionFailedException(例如,当在@KafkaListener方法中检测到异常时抛出)都会配置groupId属性。这允许目标解析程序除了使用ConsumerRecord中的信息来选择死信主题之外,还可以使用此信息。  
 以下示例显示了如何注入自定义目标解析程序:



DeadLetterPublishingRecoverer recoverer = new DeadLetterPublishingRecoverer(template,
(r, e) -> {
if (e instanceof FooException) {
return new TopicPartition(r.topic() + “.Foo.failures”, r.partition());
}
else {
return new TopicPartition(r.topic() + “.other.failures”, r.partition());
}
});
CommonErrorHandler errorHandler = new DefaultErrorHandler(recoverer, new FixedBackOff(0L, 2L));


发送到死信主题的记录有以下headers:


* KafkaHeaders.DLT\_EXCEPTION\_FQCN:Exception类名(通常是ListenerExecutionFailedException,但也可以是其他类)。
* KafkaHeaders.DLT\_EXCEPTION\_CAUSE\_FQCN:异常原因类名(如果存在)。
* KafkaHeaders.DLT\_EXCEPTION\_STACKTRACE:异常堆栈跟踪。
* KafkaHeaders.DLT\_EXCEPTION\_MESSAGE:异常消息。
* KafkaHeaders.DLT\_KEY\_EXCEPTION\_FQCN:异常类名(仅限key反序列化错误)。
* KafkaHeaders.DLT\_KEY\_EXCEPTION\_STACKTRACE:异常堆栈跟踪(仅限key反序列化错误)。
* KafkaHeaders.DLT\_KEY\_EXCEPTION\_MESSAGE:异常消息(仅限key反序列化错误)。
* KafkaHeaders.DLT\_ORIGINAL\_TOPIC:原始主题。
* KafkaHeaders.DLT\_ORIGINAL\_PARTITION:原始分区。
* KafkaHeaders。DLT\_ORIGINAL\_OFFSET:原始偏移量。
* KafkaHeaders.DLT\_ORIGINAL\_TIMESTAMP:原始时间戳。
* KafkaHeaders.DLT\_ORIGINAL\_TIMESTAMP\_TYPE:原始时间戳类型。
* KafkaHeaders.DLT\_ORIGINAL\_CONSUMER\_GROUP:未能处理记录的原始consumer组。  
 Key异常仅由反序列化异常引起,因此不存在DLT\_KEY\_EXCEPTION\_CAUSE\_FQCN。  
 有两种机制可以添加更多的header。


1. 创建recoverer子类并覆盖createProducerRecord()-调用super.createProducerRecord()并添加更多header。
2. 提供一个BiFunction来接收消费者记录和异常,返回一个Headers对象;来自那里的headers将被复制到最终producer记录中;另请参阅[管理死信记录头]( )。使用setHeadersFunction()设置BiFunction。  
 第二个实现起来更简单,但第一个有更多的可用信息,包括已经组装好的标准headers。  
 当与ErrorHandlingDeserializer一起使用时,发布者将把死信生产者记录中的value()恢复为未能反序列化的原始值。以前,value()为null,用户代码必须解码消息头中的DeserializationException。此外,您还可以向发布者提供多个KafkaTemplate;例如,如果你希望从反序列化异常中发布byte[],以及发布反序列化成功的value,它使用了不同序列化程序,则可能需要这样做。下面是一个使用String和byte[]序列化程序的KafkaTemplate配置发布服务器的示例:



@Bean
public DeadLetterPublishingRecoverer publisher(KafkaTemplate<?, ?> stringTemplate,
KafkaTemplate<?, ?> bytesTemplate) {

Map<Class<?>, KafkaTemplate<?, ?>> templates = new LinkedHashMap<>();
templates.put(String.class, stringTemplate);
templates.put(byte[].class, bytesTemplate);
return new DeadLetterPublishingRecoverer(templates);

}


发布者使用map的key来定位适合即将发布的value()的template。建议使用LinkedHashMap,以便按顺序检查key。  
 当发布null值时,并且有多个template时,恢复器将为Void类寻找一个template;如果不存在,将使用values().iterator()中的第一个template。  
 你可以使用setFailIfSendResultIsError方法,以便在消息发布失败时抛出异常。你还可以使用setWaitForSendResultTimeout设置验证发送方成功的超时时间。  
 如果恢复器失败(引发异常),则失败的记录将包含在seek中。如果恢复器失败,默认情况下将重置BackOff,并且在再次尝试恢复之前,重新deliver将再次经历back off。在早期版本中,BackOff未重置,并在下一次失败时重新尝试恢复。若要恢复到以前的行为,请将错误处理程序的resetStateOnRecoveryFailure属性设置为false。  
 将resetStateOnExceptionChange设置为true,如果异常类型在各次失败之间发生变化,则重试序列将重新启动(包括选择新的BackOff,如果配置了此选项)。默认情况下,不考虑异常类型。  
 恢复器也可以与Kafka Streams一起使用-有关更多信息,请参阅从反序列化异常中恢复(Recovery from Deserialization Exceptions)。  
 ErrorHandlingDeserializer在header “ErrorHandlingDeserializer.VALUE\_DESERIALIZER\_EXCEPTION\_HEADER”和“ErrorHandlingDeserializer.KEY\_DESERIALIZER\_EXCEPTION\_HEADER”中添加反序列化异常(使用java 序列化)。默认情况下,这些header不会保留在发布到死信主题的消息中。如果键和值都未能反序列化,则在发送给DLT的记录中填充这两者的原始值。  
 如果传入的记录相互依赖,但可能会无序到达,则将失败的记录重新发布到原始主题的尾部(多次)可能会很有用,而不是直接发送到死信主题。有关示例,请参阅[此stackoverflow问题]( )。  
 以下错误处理程序配置将完全做到这一点:



@Bean
public ErrorHandler eh(KafkaOperations<String, String> template) {
return new DefaultErrorHandler(new DeadLetterPublishingRecoverer(template,
(rec, ex) -> {
org.apache.kafka.common.header.Header retries = rec.headers().lastHeader(“retries”);
if (retries == null) {
retries = new RecordHeader(“retries”, new byte[] { 1 });
rec.headers().add(retries);
}
else {
retries.value()[0]++;
}
return retries.value()[0] > 5
? new TopicPartition(“topic.DLT”, rec.partition())
: new TopicPartition(“topic”, rec.partition());
}), new FixedBackOff(0L, 0L));
}

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

   ? new TopicPartition("topic.DLT", rec.partition())
                    : new TopicPartition("topic", rec.partition());
        }), new FixedBackOff(0L, 0L));

}

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)
[外链图片转存中…(img-B01prD38-1713155547436)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值