Redis实现消息队列,消息不能正常消费的问题排查与解决

问题描述

用Redis实现消息队列,消息发送到Stream中后没有被正常消费,堆积在里面


    @Override
    public void consume(ObjectRecord<String, MyMessage> message) {
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            MQLogs mqLogs = objectMapper.readValue(message.getValue().getContent(), MQLogs.class);
            JdeWorkOrderIssuanceDto dto = objectMapper.readValue(mqLogs.getContent(), JdeWorkOrderIssuanceDto.class);

            String token = stringRedisTemplate.opsForValue().get(GlobalConstant.JDE_TOKEN);
            dto.setToken(token);

            Call<JdeRootBean<JdeWorkOrderIssuanceBean>> response = RetrofitUtil.getRetrofit(jdeConfig.getUrl())
                    .create(HttpService.class)
                    .workOrderIssuance(dto);
            Response<JdeRootBean<JdeWorkOrderIssuanceBean>> execute = response.execute();
            MQLogs mqLogs1 = mqlogsDao.findById(mqLogs.getId()).orElseThrow(() -> new IllegalStateException("消息日志不存在"));
            if (execute.code() == 200) {
                mqLogs1.setStatus(1);
            }
            mqLogs1.setTime(mqLogs1.getTime() + 1);
            mqLogs1.setUpdatedAt(LocalDateTime.now());
            mqlogsDao.save(mqLogs1);
            this.stringRedisTemplate.opsForStream().delete(message);

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

原因分析:

正常运行一天后出现阻塞情况,后面多次检查发现注释掉网络请求部分问题解决


解决方案:

当时想到由于第三方提供的接口响应时间过长,有时几分钟都无法响应,于是想到使用异步的方式执行来解决问题:

 CompletableFuture.supplyAsync(() -> {
                try {
                    // 发送网络请求
                    Call<JdeRootBean<JdeWorkOrderIssuanceBean>> response = RetrofitUtil.getRetrofit(jdeConfig.getUrl())
                            .create(HttpService.class)
                            .workOrderIssuance(dto);
                    Response<JdeRootBean<JdeWorkOrderIssuanceBean>> execute = response.execute();

                    // 处理网络请求响应
                    if (execute.code() == 200) {
                        // 更新消息状态等操作
                        MQLogs mqLogs1 = mqlogsDao.findById(mqLogs.getId()).orElseThrow(() -> new IllegalStateException("消息日志不存在"));
                        mqLogs1.setStatus(1);
                        mqLogs1.setTime(mqLogs1.getTime() + 1);
                        mqLogs1.setUpdatedAt(LocalDateTime.now());
                        mqlogsDao.save(mqLogs1);
                    }
                    return execute;
                } catch (IOException e) {
                    throw new CompletionException(e);
                }
            }).thenAccept(response -> {
                // 处理成功后的逻辑
                log.info("HTTP请求成功: {}", response);
                // 删除消息
                this.stringRedisTemplate.opsForStream().delete(message);
            }).exceptionally(ex -> {
                // 处理异常情况
                log.error("HTTP请求失败: {}", ex.getMessage(), ex);
                return null;
            });

实际修改后发现还是会出现消息未被消费的情况于是在之前的基础上增加了超时时间

            // 异步执行网络请求
            CompletableFuture.supplyAsync(() -> {
                        try {
                            // 发送网络请求
                            Call<JdeRootBean<JdeWorkOrderIssuanceBean>> response = RetrofitUtil.getRetrofit(jdeConfig.getUrl())
                                    .create(HttpService.class)
                                    .workOrderIssuance(dto);
                            Response<JdeRootBean<JdeWorkOrderIssuanceBean>> execute = response.execute();

                            // 处理网络请求响应
                            if (execute.code() == 200) {
                                // 更新消息状态等操作
                                MQLogs mqLogs1 = mqlogsDao.findById(mqLogs.getId()).orElseThrow(() -> new IllegalStateException("消息日志不存在"));
                                mqLogs1.setStatus(1);
                                mqLogs1.setTime(mqLogs1.getTime() + 1);
                                mqLogs1.setUpdatedAt(LocalDateTime.now());
                                mqlogsDao.save(mqLogs1);
                            }
                            return execute;
                        } catch (IOException e) {
                            throw new CompletionException(e);
                        }
                    }).completeOnTimeout(null, 5, TimeUnit.MINUTES) // 设置超时时间为5分钟
                    .thenAccept(response -> {
                        // 处理成功后的逻辑
                        log.info("HTTP请求成功: {}", response);
                        // 删除消息
                        this.stringRedisTemplate.opsForStream().delete(message);
                    }).exceptionally(ex -> {
                        // 处理异常情况
                        log.error("HTTP请求失败: {}", ex.getMessage(), ex);
                        return null;
                    });

修改过后,问题依旧,百思不得其解,询问GPT得到修改方案如下


    @Override
    public void consume(ObjectRecord<String, MyMessage> message) {
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            MQLogs mqLogs = objectMapper.readValue(message.getValue().getContent(), MQLogs.class);
            JdeWorkOrderIssuanceDto dto = objectMapper.readValue(mqLogs.getContent(), JdeWorkOrderIssuanceDto.class);
            String token = jdeTokenService.getToken();
            dto.setToken(token);

            ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
            AtomicBoolean completed = new AtomicBoolean(false);

            CompletableFuture.runAsync(() -> {
                try {
                    Call<Map<String, Object>> response = RetrofitUtil.getRetrofit(jdeConfig.getUrl())
                            .create(HttpService.class)
                            .workOrderIssuance(dto);
                    Response<Map<String, Object>> execute = response.execute();

                    if (completed.compareAndSet(false, true)) {
                        processResponse(execute, mqLogs, message, executorService);
                    }
                } catch (IOException e) {
                    if (completed.compareAndSet(false, true)) {
                        handleException(mqLogs, e, message, executorService);
                    }
                }
            }, executorService);

            executorService.schedule(() -> {
                if (completed.compareAndSet(false, true)) {
                    handleTimeout(mqLogs, message, executorService);
                }
            }, 5, TimeUnit.MINUTES);

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private void processResponse(Response<Map<String, Object>> response, MQLogs mqLogs, ObjectRecord<String, MyMessage> message, ScheduledExecutorService executorService) {
        int statusCode = response.code();
        if (response.isSuccessful() && statusCode == 200) {
            Map<String, Object> body = response.body();
            if (body != null) {
                mqLogs.setStatus(1);
            }
        }

        closeExecutorServiceAndDeleteMessage(mqLogs, message, executorService);
    }

    private void handleException(MQLogs mqLogs, Exception e, ObjectRecord<String, MyMessage> message, ScheduledExecutorService executorService) {
        mqLogs.setStatus(0); // 或者设置其他表示失败的状态码

        closeExecutorServiceAndDeleteMessage(mqLogs, message, executorService);
    }

    private void handleTimeout(MQLogs mqLogs, ObjectRecord<String, MyMessage> message, ScheduledExecutorService executorService) {

        mqLogs.setStatus(0); // 或者设置其他表示超时的状态码

        closeExecutorServiceAndDeleteMessage(mqLogs, message, executorService);
    }

    private void closeExecutorServiceAndDeleteMessage(MQLogs mqLogs, ObjectRecord<String, MyMessage> message, ScheduledExecutorService executorService) {
        mqLogs.setTime(mqLogs.getTime() + 1);
        mqLogs.setUpdatedAt(LocalDateTime.now());
        mqlogsDao.save(mqLogs);
        this.stringRedisTemplate.opsForStream().delete(message);
        executorService.shutdown(); // 在处理结束后关闭executorService
    }

修改后问题得到好转,但是问题还是会出现,继续排查问题,换通义千问追问得到答案为:Retrofit 中使用的execute方法会同步执行,并阻塞当前线程,并不能完成异步操作,修改为enqueue 则可以实现异步效果.
至此问题解决

  • 20
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Redis 是一个开源的内存数据库,以其高性能和灵活的数据结构而闻名。《Redis 入门到精通》是一本介绍 Redis 使用和深入学习的书籍,适合有一定编程基础的开发者或对数据库有一定了解的人士。 这本书从介绍 Redis 的概述和安装入手,帮助读者了解 Redis 的基本概念和使用方法。接着,书中详细介绍了 Redis 中常用的数据结构,如字符串、列表、哈希、集合和有序集合,并提供了示例代码和运用场景,让读者能够深入理解这些数据结构的使用和特点。 此外,书中还介绍了 Redis 的高级特性和应用,如发布-订阅模式、事务、持久化、复制等。这些内容帮助读者进一步了解 Redis 的技术原理和性能优势,帮助开发者更好地应用 Redis 解决实际问题。 《Redis 入门到精通》还包括了性能优化、监控和故障处理等实用的内容,读者可以学习如何配置和优化 Redis 以提升系统性能,并学习如何排查解决常见故障。 总体而言,《Redis 入门到精通》是一本全面介绍和学习 Redis 的实用书籍,通过深入浅出的讲解和实例,帮助读者从入门到精通 Redis,并能够灵活运用 Redis 解决实际问题。无论是想要了解 Redis 的基础知识还是深入学习 Redis 的高级特性,都可以从这本书中获得帮助和指导。 ### 回答2: Redis(Remote Dictionary Server)是一个开源的、基于键值对的存储系统。它以其高性能、易使用和丰富的功能而备受推崇。《Redis入门到精通》PDF是一本针对Redis的学习资料,从入门到深入掌握Redis的各个方面进行了详细介绍。 这本书首先介绍了Redis的基本概念和原理,包括键值对存储、数据类型、持久化等核心概念。然后,它深入讲解了Redis的各种高级功能,如发布订阅、事务、Lua脚本等。此外,书中还包含了丰富的示例代码和实际应用场景,帮助读者更好地理解和应用Redis。 《Redis入门到精通》PDF适合各个层次的读者。对于初学者,它提供了系统全面的入门指导,从安装配置开始,一步步引导读者熟悉和掌握Redis的使用。对于有一定经验的开发者,它提供了高级特性和实战案例,让读者深入了解Redis的内部机制和最佳实践。 此外,这本书还介绍了Redis的性能调优和集群部署等内容,帮助读者在实际应用中充分发挥Redis的潜力。无论是Web应用的缓存消息队列还是实时数据分析,Redis都应该是开发者的首选。 总之,《Redis入门到精通》PDF是一本权威而实用的Redis学习资料,通过系统而全面的介绍,帮助读者从入门到精通Redis,更好地应用Redis解决实际问题。无论是初学者还是有经验的开发者,都可以从中获益匪浅。 ### 回答3: 《Redis 入门到精通》是一本介绍 Redis 数据库的权威指南。Redis 是一种高性能、可扩展的键值存储系统,常用于缓存消息队列、实时统计和分布式会话管理等领域。 该书从 Redis 的基础概念入手,包括安装配置、数据结构、持久化、集群部署等内容,帮助读者快速上手 Redis。接着介绍了 Redis 的高级功能,例如发布订阅、事务、Lua 脚本编写、并发控制等,让读者深入了解 Redis 的各种用法和技巧。 《Redis 入门到精通》还涵盖了 Redis 在实际应用中的最佳实践,包括如何优化性能、如何设计和实现分布式系统等。此外,书中还介绍了常见的 Redis 开源工具和框架,如 Redisson、Spring Data Redis 等,帮助读者更好地利用 Redis 构建高效的应用程序。 该书内容丰富、结构清晰,适合初学者和有一定经验的开发人员。无论是想快速上手 Redis,还是想深入学习 Redis 的高级用法,本书都能提供详细的指导和实例演示。 总之,通过阅读《Redis 入门到精通》这本书,读者可以系统地学习和掌握 Redis 数据库的各个方面,从入门到精通。无论是作为开发人员、DBA 还是系统架构师,都能在实际工作中充分发挥 Redis 强大的功能和性能优势。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值