当前位置: 首页 > news >正文

非阻塞重试与 Spring Kafka 的集成测试

        如何为启用重试和死信发布的消费者的 Spring Kafka 实现编写集成测试。

Kafka 非阻塞重试

Kafka 中的非阻塞重试是通过为主主题配置重试主题来完成的。如果需要,还可以配置其他死信主题。如果所有重试均已用尽,事件将转发至 DLT。公共领域提供了大量资源来了解技术细节。 

要测试什么?

在代码中为重试机制编写集成测试时,这可能是一项具有挑战性的工作。 

  • 如何测试该事件是否已重试所需的次数? 
  • 如何测试仅在发生某些异常时才执行重试,而对于其他异常则不执行重试?
  • 如果上次重试中异常已解决,如何测试是否未进行另一次重试?
  • 在(n-1)次重试尝试失败后,如何测试重试中的第n次尝试是否成功?
  • 当所有重试尝试都用完后,如何测试事件是否已发送到死信队列?

让我们看一些代码。您可以找到很多很好的文章,展示如何使用 Spring Kafka 设置非阻塞重试。下面给出了一种这样的实现。这是使用Spring-Kafka 的@RetryableTopic@DltHandler  注释来完成的。

设置可重试消费者

@Slf4j
@Component
@RequiredArgsConstructor
public class CustomEventConsumer {private final CustomEventHandler handler;@RetryableTopic(attempts = "${retry.attempts}",backoff = @Backoff(delayExpression = "${retry.delay}",multiplierExpression = "${retry.delay.multiplier}"),topicSuffixingStrategy = TopicSuffixingStrategy.SUFFIX_WITH_INDEX_VALUE,dltStrategy = FAIL_ON_ERROR,autoStartDltHandler = "true",autoCreateTopics = "false",include = {CustomRetryableException.class})@KafkaListener(topics = "${topic}", id = "${default-consumer-group:default}")public void consume(CustomEvent event, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic) {try {log.info("Received event on topic {}", topic);handler.handleEvent(event);} catch (Exception e) {log.error("Error occurred while processing event", e);throw e;}}@DltHandlerpublic void listenOnDlt(@Payload CustomEvent event) {log.error("Received event on dlt.");handler.handleEventFromDlt(event);}}

如果您注意到上面的代码片段,include参数包含CustomRetryableException.class. 这告诉使用者仅在该方法抛出 CustomRetryableException 时才重试CustomEventHandler#handleEvent。您可以根据需要添加任意数量。还有一个排除参数,但一次可以使用其中任何一个参数。

${retry.attempts}在发布到 DLT 之前,事件处理应重试最多次数。

设置测试基础设施

要编写集成测试,您需要确保拥有一个正常运行的 Kafka 代理(首选嵌入式)和一个功能齐全的发布者。让我们设置我们的基础设施:

@EnableKafka
@SpringBootTest
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
@EmbeddedKafka(partitions = 1,brokerProperties = {"listeners=" + "${kafka.broker.listeners}", "port=" + "${kafka.broker.port}"},controlledShutdown = true,topics = {"test", "test-retry-0", "test-retry-1", "test-dlt"}
)
@ActiveProfiles("test")
class DocumentEventConsumerIntegrationTest {@Autowiredprivate KafkaTemplate<String, CustomEvent> testKafkaTemplate;// tests}

** 配置是从 application-test.yml 文件导入的。

使用嵌入式 kafka 代理时,重要的是要提及要创建的主题。它们不会自动创建。在本例中,我们创建四个主题,即 

"test", "test-retry-0", "test-retry-1", "test-dlt"

我们已将最大重试尝试次数设置为 3 次。每个主题对应于每次重试尝试。因此,如果 3 次重试都用尽,则应将事件转发到 DLT。

测试用例

如果第一次尝试消费成功,则不应重试。

CustomEventHandler#handleEvent这可以通过该方法仅被调用一次的事实来测试。还可以添加对 Log 语句的进一步测试。

    @Testvoid test_should_not_retry_if_consumption_is_successful() throws ExecutionException, InterruptedException {CustomEvent event = new CustomEvent("Hello");// GIVENdoNothing().when(customEventHandler).handleEvent(any(CustomEvent.class));// WHENtestKafkaTemplate.send("test", event).get();// THENverify(customEventHandler, timeout(2000).times(1)).handleEvent(any(CustomEvent.class));verify(customEventHandler, timeout(2000).times(0)).handleEventFromDlt(any(CustomEvent.class));}

如果引发不可重试的异常,则不应重试。

在这种情况下,该CustomEventHandler#handleEvent方法应该只调用一次:

    @Testvoid test_should_not_retry_if_non_retryable_exception_raised() throws ExecutionException, InterruptedException {CustomEvent event = new CustomEvent("Hello");// GIVENdoThrow(CustomNonRetryableException.class).when(customEventHandler).handleEvent(any(CustomEvent.class));// WHENtestKafkaTemplate.send("test", event).get();// THENverify(customEventHandler, timeout(2000).times(1)).handleEvent(any(CustomEvent.class));verify(customEventHandler, timeout(2000).times(0)).handleEventFromDlt(any(CustomEvent.class));}

如果抛出 a,则重试配置的最大次数RetryableException,并在重试用完后将其发布到死信主题。

在这种情况下,该CustomEventHandler#handleEvent方法应被调用三次(maxRetries)次,并且CustomEventHandler#handleEventFromDlt该方法应被调用一次。

    @Testvoid test_should_retry_maximum_times_and_publish_to_dlt_if_retryable_exception_raised() throws ExecutionException, InterruptedException {CustomEvent event = new CustomEvent("Hello");// GIVENdoThrow(CustomRetryableException.class).when(customEventHandler).handleEvent(any(CustomEvent.class));// WHENtestKafkaTemplate.send("test", event).get();// THENverify(customEventHandler, timeout(10000).times(maxRetries)).handleEvent(any(CustomEvent.class));verify(customEventHandler, timeout(2000).times(1)).handleEventFromDlt(any(CustomEvent.class));}

**在验证阶段添加了相当大的超时,以便在测试完成之前可以考虑指数退避延迟。这很重要,如果设置不当可能会导致断言失败。

应该重试直到RetryableException解决,并且如果引发不可重试的异常或消费最终成功,则不应继续重试。

测试已设置为RetryableException先抛出 a 然后再抛出 a NonRetryable exception,以便重试一次。

    @Testvoid test_should_retry_until_retryable_exception_is_resolved_by_non_retryable_exception() throws ExecutionException,InterruptedException {CustomEvent event = new CustomEvent("Hello");// GIVENdoThrow(CustomRetryableException.class).doThrow(CustomNonRetryableException.class).when(customEventHandler).handleEvent(any(CustomEvent.class));// WHENtestKafkaTemplate.send("test", event).get();// THENverify(customEventHandler, timeout(10000).times(2)).handleEvent(any(CustomEvent.class));verify(customEventHandler, timeout(2000).times(0)).handleEventFromDlt(any(CustomEvent.class));}@Testvoid test_should_retry_until_retryable_exception_is_resolved_by_successful_consumption() throws ExecutionException,InterruptedException {CustomEvent event = new CustomEvent("Hello");// GIVENdoThrow(CustomRetryableException.class).doNothing().when(customEventHandler).handleEvent(any(CustomEvent.class));// WHENtestKafkaTemplate.send("test", event).get();// THENverify(customEventHandler, timeout(10000).times(2)).handleEvent(any(CustomEvent.class));verify(customEventHandler, timeout(2000).times(0)).handleEventFromDlt(any(CustomEvent.class));}

结论

因此,您可以看到集成测试是策略、超时、延迟和验证的混合和匹配,以确保 Kafka 事件驱动架构的重试机制万无一失。

相关文章:

非阻塞重试与 Spring Kafka 的集成测试

如何为启用重试和死信发布的消费者的 Spring Kafka 实现编写集成测试。 Kafka 非阻塞重试 Kafka 中的非阻塞重试是通过为主主题配置重试主题来完成的。如果需要&#xff0c;还可以配置其他死信主题。如果所有重试均已用尽&#xff0c;事件将转发至 DLT。公共领域提供了大量资…...

基于 Debian 12 的MX Linux 23 正式发布!

导读MX Linux 是基于 Debian 稳定分支的面向桌面的 Linux 发行&#xff0c;它是 antiX 及早先的 MEPIS Linux 社区合作的产物。它采用 Xfce 作为默认桌面环境&#xff0c;是一份中量级操作系统&#xff0c;并被设计为优雅而高效的桌面与如下特性的结合&#xff1a;配置简单、高…...

Nginx代理功能与负载均衡详解

序言 Nginx的代理功能与负载均衡功能是最常被用到的&#xff0c;关于nginx的基本语法常识与配置已在上篇文章中有说明&#xff0c;这篇就开门见山&#xff0c;先描述一些关于代理功能的配置&#xff0c;再说明负载均衡详细。 Nginx代理服务的配置说明 1、上一篇中我们在http…...

部署问题集合(特辑)虚拟机常用命令

基础 查看ip&#xff1a;ip addr或ipconfig压缩&#xff1a;tar -zcvf redis-3.2.8.tar.gz redis-3.2.8/ 注意&#xff1a;-zcvf对应gz&#xff0c;-vcf对应tar 解压&#xff1a;tar -zxvf redis-3.2.8.tar.gz压缩zip&#xff1a;zip nginx.zip nginx.txt nginx2.txt解压zip&a…...

【Git】如何将本地文件进行Git仓库归档

Git 全局设置 git config --global user.name "mcihael" git config --global user.email "michael520.com"创建新版本库 git clone gitcode.xxxxxx.git cd branch-name touch README.md git add README.md git commit -m "add README" git pu…...

uniapp 使用腾讯视频 的 坑

1. 版本号的问题 注意 1.X.X不维护了 &#xff0c; 需要升级要 2.X.X 2. 官网的 组件事件 调用需要去掉bind 才能调用 官网地址&#xff1a;腾讯视频 | 小程序插件 | 微信公众平台...

LinkedList

LinkedList的模拟实现&#xff08;底层是一个双向链表&#xff09;LinkedList使用 LinkedList的模拟实现&#xff08;底层是一个双向链表&#xff09; 无头双向链表&#xff1a;有两个指针&#xff1b;一个指向前一个节点的地址&#xff1b;一个指向后一个节点的地址。 节点定…...

创作新纪元:知乎、阅文加码AI大模型,撬动创作者经济

输入几个关键词就能生成一篇文章、一篇新闻、一篇小说&#xff0c;ChatGPT自诞生以来文本生成能力一直备受赞誉&#xff0c;ChatGPT要替代记者、编辑、作家的言论愈演愈烈&#xff0c;甚至有一些互联网企业宣布砍掉记者、编辑、文案等岗位全面拥抱AIGC。 目前ChatGPT是否会全面…...

PAT(Advanced Level) Practice(with python)——1067 Sort with Swap(0, i)

Code # 输入有毒&#xff0c;需避坑 # N int(input()) L list(map(int,input().split())) N L[0] L L[1:] res 0 for i in range(1,N):while L[0]!0:# 把所有不在正常位置下的数换到正常t L[0]L[0],L[t] L[t],L[0]res1if L[i]!i:# 换完全后如果对应位置下的数不是目标…...

Python爬取斗罗大陆全集

打开网址http://www.luoxu.cc/dmplay/C888H-1-265.html F12打开Fetch/XHR&#xff0c;看到m3u8&#xff0c;ts&#xff0c;一眼顶真&#xff0c;打开index.m3u8 由第一个包含第二个index.m3u8的地址&#xff0c;ctrlf在源代码中一查index&#xff0c;果然有&#xff0c;不过/…...

前馈神经网络解密:深入理解人工智能的基石

目录 一、前馈神经网络概述什么是前馈神经网络前馈神经网络的工作原理应用场景及优缺点 二、前馈神经网络的基本结构输入层、隐藏层和输出层激活函数的选择与作用网络权重和偏置 三、前馈神经网络的训练方法损失函数与优化算法反向传播算法详解避免过拟合的策略 四、使用Python…...

顺序栈Sequential-stack

0、节点结构体定义 typedef struct SqStack{int *base;int *top; } SqStack; 1、初始化 bool InitStack(SqStack &S) {S.base new int[Maxsize]; //eg. #define Maxsize 100if(!S.base){return false;}S.top S.base;return true; } 2、入栈 bool Push(SqStack &…...

关于工牌(必须5-10个字)

今天蹲坑&#xff0c;低头看了下工牌觉得挺有意思&#xff1a;我从啥时候起也不排斥将工牌挂在脖子上了&#xff1f; 工牌&#xff0c;一个标识。不仅标识了你&#xff0c;也标识了你所在的群体。如果你认可这个群体&#xff0c;佩戴它那是一种荣誉、荣耀&#xff1b;如果你不…...

PHP混淆加密以及常用的一些加密工具

PHP混淆加密是一种将源代码转换为难以理解和阅读的方式&#xff0c;以保护代码的安全性。以下是一些常见的PHP混淆加密方法&#xff1a; 代码压缩&#xff1a;使用代码压缩工具&#xff08;如UglifyJS&#xff09;将PHP代码压缩为一行&#xff0c;去除空格、换行符等可读性的字…...

无涯教程-PHP - ereg()函数

ereg() - 语法 int ereg(string pattern, string originalstring, [array regs]); ereg()函数在string指定的字符串中搜索pattern指定的字符串&#xff0c;如果找到pattern&#xff0c;则返回true&#xff0c;否则返回false。搜索对于字母字符区分大小写。 可选的输入参数re…...

【Ubuntu】简洁高效企业级日志平台后起之秀Graylog

简介 Graylog 是一个用于集中式日志管理的开源平台。在现代数据驱动的环境中&#xff0c;我们需要处理来自各种设备、应用程序和操作系统的大量数据。Graylog提供了一种方法来聚合、组织和理解所有这些数据。它的核心功能包括流式标记、实时搜索、仪表板可视化、告警触发、内容…...

TCP特点UDP编程

目录 1、tcp协议和udp协议 2、多线程并发和多进程并发&#xff1a; &#xff08;1&#xff09;多进程并发服务端 &#xff08;2&#xff09;多进程并发客户端&#xff1a; 3、tcp: 4、粘包 5、UDP协议编程流程 (1)服务器端&#xff1a; (2)客户端&#xff1a; 6、tcp状…...

超级计算机

超级计算机是一种高性能计算机&#xff0c;它能够以极高的速度执行大规模的计算任务。超级计算机通常由数千个甚至数百万个处理器组成&#xff0c;这些处理器能够同时处理大量的数据&#xff0c;从而实现高效的计算。超级计算机广泛应用于科学、工程、金融、天气预报等领域&…...

LeetCode863. 二叉树中所有距离为 K 的结点(相关话题:深度遍历,广度遍历)

题目描述 给定一个二叉树(具有根结点 root), 一个目标结点 target ,和一个整数值 k 。 返回到目标结点 target 距离为 k 的所有结点的值的列表。 答案可以以 任何顺序 返回。 示例 1: 输入:root = [3,5,1,6,2,0,8,null,null,7,4], target = 5, k = 2 输出:[7,4,1] 解释…...

Kotlin 基础学习

NULL检查机制 Kotlin的空安全设计对于声明可为空的参数&#xff0c;在使用是进行空判断处理&#xff0c;有两种处理方式&#xff0c;字段后加 !! 像 java 一样抛出空异常&#xff0c;另外字段后面加 ? 可不做处理返回值为 null 或者配合 ?: 做空判断处理。 //类型后面加 ? 表…...

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版​分享

平时用 iPhone 的时候&#xff0c;难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵&#xff0c;或者买了二手 iPhone 却被原来的 iCloud 账号锁住&#xff0c;这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...

基于当前项目通过npm包形式暴露公共组件

1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹&#xff0c;并新增内容 3.创建package文件夹...

土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等

&#x1f50d; 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术&#xff0c;可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势&#xff0c;还能有效评价重大生态工程…...

Golang——6、指针和结构体

指针和结构体 1、指针1.1、指针地址和指针类型1.2、指针取值1.3、new和make 2、结构体2.1、type关键字的使用2.2、结构体的定义和初始化2.3、结构体方法和接收者2.4、给任意类型添加方法2.5、结构体的匿名字段2.6、嵌套结构体2.7、嵌套匿名结构体2.8、结构体的继承 3、结构体与…...

C# 表达式和运算符(求值顺序)

求值顺序 表达式可以由许多嵌套的子表达式构成。子表达式的求值顺序可以使表达式的最终值发生 变化。 例如&#xff0c;已知表达式3*52&#xff0c;依照子表达式的求值顺序&#xff0c;有两种可能的结果&#xff0c;如图9-3所示。 如果乘法先执行&#xff0c;结果是17。如果5…...

保姆级【快数学会Android端“动画“】+ 实现补间动画和逐帧动画!!!

目录 补间动画 1.创建资源文件夹 2.设置文件夹类型 3.创建.xml文件 4.样式设计 5.动画设置 6.动画的实现 内容拓展 7.在原基础上继续添加.xml文件 8.xml代码编写 (1)rotate_anim (2)scale_anim (3)translate_anim 9.MainActivity.java代码汇总 10.效果展示 逐帧…...

FFmpeg avformat_open_input函数分析

函数内部的总体流程如下&#xff1a; avformat_open_input 精简后的代码如下&#xff1a; int avformat_open_input(AVFormatContext **ps, const char *filename,ff_const59 AVInputFormat *fmt, AVDictionary **options) {AVFormatContext *s *ps;int i, ret 0;AVDictio…...

SQL进阶之旅 Day 22:批处理与游标优化

【SQL进阶之旅 Day 22】批处理与游标优化 文章简述&#xff08;300字左右&#xff09; 在数据库开发中&#xff0c;面对大量数据的处理任务时&#xff0c;单条SQL语句往往无法满足性能需求。本篇文章聚焦“批处理与游标优化”&#xff0c;深入探讨如何通过批量操作和游标技术提…...

「Java基本语法」变量的使用

变量定义 变量是程序中存储数据的容器&#xff0c;用于保存可变的数据值。在Java中&#xff0c;变量必须先声明后使用&#xff0c;声明时需指定变量的数据类型和变量名。 语法 数据类型 变量名 [ 初始值]; 示例&#xff1a;声明与初始化 public class VariableDemo {publi…...

iOS 项目怎么构建稳定性保障机制?一次系统性防错经验分享(含 KeyMob 工具应用)

崩溃、内存飙升、后台任务未释放、页面卡顿、日志丢失——稳定性问题&#xff0c;不一定会立刻崩&#xff0c;但一旦积累&#xff0c;就是“上线后救不回来的代价”。 稳定性保障不是某个工具的功能&#xff0c;而是一套贯穿开发、测试、上线全流程的“观测分析防范”机制。 …...