SpringBoot 流式输出时,正常输出后为何突然报错?

news2024/11/17 12:50:09
  1. 一个 SpringBoot 项目同时使用了 Tomcat 的过滤器和 Spring 的拦截器,一些线程变量在过滤器中初始化并在拦截器中使用。

  2. 该项目需要调用大语言模型进行流式输出。

  3. 项目中,笔者使用 SpringBoot 的 ResponseEntity<StreamingResponseBody> 将流式输出返回前端。

问题出现

问题出现在上述第 3 点:正常输出一段内容后,后台突然报错,而报错内容由拦截器产生

笔者仔细查看了报错日志,发现只是拦截器的问题:执行时由于某些线程变量不存在而报错。但是,这些线程变量已经在过滤器中初始化了。

那么问题来了:为什么这个接口明明可以正常通过过滤器和拦截器,并开始正常输出,却又突然在拦截器中报错呢?

场景重现

Filter

@Slf4j@Component@Order(1)public class MyFilter implements Filter {
    @Override    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {        // 要继续处理请求,必须添加 filterChain.doFilter()        log.info("doFilter method is running..., thread: {}, dispatcherType: {}", Thread.currentThread(), servletRequest.getDispatcherType());         filterChain.doFilter(servletRequest,servletResponse);    } }

复制代码

Interceptor

@Slf4jpublic class MyInterceptor implements HandlerInterceptor {
    @Override    public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) throws Exception {        log.info("preHandle method is running..., thread: {}, dispatcherType: {}", Thread.currentThread(), request.getDispatcherType());        if (DispatcherType.ASYNC == request.getDispatcherType()) {            log.info("preHandle dispatcherType={}", request.getDispatcherType());        }        return true;    }        @Override    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {        log.info("postHandle method is running..., thread: {}", Thread.currentThread());    }              @Override    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {        log.info("afterCompletion method is running..., thread: {}", Thread.currentThread());    } }

复制代码

WebMvcConfigurer

@Configurationpublic class WebAppConfigurer implements WebMvcConfigurer {        @Bean    public MyInterceptor myInterceptor() {        return new MyInterceptor();    }        @Override    public void addInterceptors(InterceptorRegistry registry) {        registry.addInterceptor(myInterceptor()).addPathPatterns("/**");    }        @Override    public void configureAsyncSupport(AsyncSupportConfigurer configurer) {        configurer.setDefaultTimeout(120_000L);        configurer.registerCallableInterceptors();        configurer.registerDeferredResultInterceptors();            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();        executor.setCorePoolSize(5);        executor.setMaxPoolSize(10);        executor.setQueueCapacity(100);        executor.setThreadNamePrefix("web-async-");        executor.initialize();        configurer.setTaskExecutor(executor);    }}

复制代码

Controller

@Slf4j@RestController@RequestMapping("/test-stream")public class TestStreamController {
    @ApiOperation("流式输出示例")    @PostMapping(value = "/example", produces = MediaType.TEXT_EVENT_STREAM_VALUE)    public ResponseEntity<StreamingResponseBody> example() {        log.info("Stream method is running, thread: {}", Thread.currentThread());        return  ResponseEntity.status(HttpStatus.OK)            .contentType(new MediaType(MediaType.TEXT_EVENT_STREAM, StandardCharsets.UTF_8))            .body(outputStream -> {                log.info("Internal stream method is running, thread: {}", Thread.currentThread());                try (outputStream) {                    String msg = "To be or not to be!";                    outputStream.write(msg.getBytes(StandardCharsets.UTF_8));                    outputStream.flush();                }            });    }}

复制代码

根据以下运行日志,我们可以看到拦截器的 preHandle 确实执行了两次,并且此次调用过程共有 3 个线程(io-14000-exec-1web-async-1io-14000-exec-2)参与了工作。

2024-05-06 07:35:27.362  INFO 209108 --- [io-14000-exec-1] o.a.c.c.C.[.[localhost].[/java-study]    : Initializing Spring DispatcherServlet 'dispatcherServlet'2024-05-06 07:35:27.362  INFO 209108 --- [io-14000-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'2024-05-06 07:35:27.365  INFO 209108 --- [io-14000-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 3 ms2024-05-06 07:35:27.402  INFO 209108 --- [io-14000-exec-1] com.peng.java.study.web.config.MyFilter  : doFilter method is running..., thread: Thread[http-nio-14000-exec-1,5,main], dispatcherType: REQUEST2024-05-06 07:35:28.107  INFO 209108 --- [io-14000-exec-1] c.p.java.study.web.config.MyInterceptor  : preHandle method is running..., thread: Thread[http-nio-14000-exec-1,5,main], dispatcherType: REQUEST2024-05-06 07:35:28.121  INFO 209108 --- [io-14000-exec-1] c.p.j.s.w.r.test.TestStreamController    : Stream method is running, thread: Thread[http-nio-14000-exec-1,5,main]2024-05-06 07:35:28.152  INFO 209108 --- [    web-async-1] c.p.j.s.w.r.test.TestStreamController    : Internal stream method is running, thread: Thread[web-async-1,5,main]2024-05-06 07:35:28.167  INFO 209108 --- [io-14000-exec-2] c.p.java.study.web.config.MyInterceptor  : preHandle method is running..., thread: Thread[http-nio-14000-exec-2,5,main], dispatcherType: ASYNC2024-05-06 07:35:28.167  INFO 209108 --- [io-14000-exec-2] c.p.java.study.web.config.MyInterceptor  : preHandle dispatcherType=ASYNC2024-05-06 07:35:28.174  INFO 209108 --- [io-14000-exec-2] c.p.java.study.web.config.MyInterceptor  : postHandle method is running..., thread: Thread[http-nio-14000-exec-2,5,main]2024-05-06 07:35:28.183  INFO 209108 --- [io-14000-exec-2] c.p.java.study.web.config.MyInterceptor  : afterCompletion method is running..., thread: Thread[http-nio-14000-exec-2,5,main]

复制代码

问题分析

1. 方法调用流程的差异

众所周知,SpringBoot 的普通输出接口调用流程图如图 1 所示。

图1-SpringBoot 普通输出调用流程图

结合日志,我们可以简单画出流式输出接口对应的流程图(图 2)。

图2-SpringBoot 流式输出调用流程图

2. 线程的差异

普通接口的执行时序图如图 3 所示。

图3-普通接口的时序图

而流式接口的时序图如图 4 所示。

图4-流式接口的调用时序图

解决问题

通过分析,对流式输出的情况提出两种解决方案:

  1. 将过滤器中的部分业务逻辑迁移到拦截器中。

  2. 根据条件,跳过第二次的拦截器 preHandle 方法。

笔者选择了第二个方案,实现代码如下。

@Slf4jpublic class MyInterceptor implements HandlerInterceptor {        @Override    public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) throws Exception {        log.info("preHandle method is running..., thread: {}, dispatcherType: {}", Thread.currentThread(), request.getDispatcherType());        // 如果是异步请求,则跳过        if (DispatcherType.ASYNC == request.getDispatcherType()) {            log.info("preHandle dispatcherType={}", request.getDispatcherType());            return true;        }        return true;    }        @Override    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {        log.info("postHandle method is running..., thread: {}", Thread.currentThread());         }        @Override    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {        log.info("afterCompletion method is running..., thread: {}", Thread.currentThread());    } }

复制代码

需要注意,请求线程和回调线程都需考虑清理线程变量,不然会导致内存泄漏。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2167234.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【YOLO目标检测马铃薯叶病害数据集】共1912张、已标注txt格式、有训练好的yolov5的模型

目录 说明图片示例 说明 数据集格式&#xff1a;YOLO格式 图片数量&#xff1a;1912 标注数量(txt文件个数)&#xff1a;1912 标注类别数&#xff1a;5 标注类别名称&#xff1a; health General early blight Severe early blight General late blight Severe late bligh…

Vue3使用vue-quill富文本编辑器实现图片大小调整

安装uill-image-resize npm install quill-image-resize --save在项目中导入并注册插件 import { QuillEditor, Quill } from vueup/vue-quill; import ImageUploader from quill-image-uploader; import ImageResize from quill-image-resize; //导入插件 import vueup/vue-…

webservice xfire升级为cxf cxf常用注解 cxf技术点 qualified如何设置

关键点 确保参数名称保持一致确保参数命名空间保持一致确保接口命名空间保持一致确保请求头设置正确确保用soapui工具解析的参数结构一致 cxf常用注解 定义接口用到的注解 定义接口名称&#xff0c;和接口命名空间 WebService(name“ams” ,targetNamespace “http://ifac…

海山数据库(He3DB)+AI(五):一种基于强化学习的数据库旋钮调优方法

[TOC] 0 前言 在海山数据库(He3DB)AI&#xff08;三&#xff09;中&#xff0c;介绍了四种旋钮调优方法&#xff1a;基于启发式&#xff0c;基于贝叶斯&#xff0c;基于深度学习和基于强化学习。本文介绍一种基于强化学习的旋钮调优方法&#xff1a;QTune: A Query-Aware Dat…

回归预测 | Matlab基于SO-ESN蛇群算法优化回声状态网络多输入单输出回归预测

回归预测 | Matlab基于SO-ESN蛇群算法优化回声状态网络多输入单输出回归预测 目录 回归预测 | Matlab基于SO-ESN蛇群算法优化回声状态网络多输入单输出回归预测预测效果基本描述程序设计参考资料 预测效果 基本描述 1.蛇群算法(SO)优化回声状态网络做拟合回归预测&#xff0c;…

Spring Security - 用户授权

1.用户授权介绍&#xff1a; 在SpringSecurity中&#xff0c;会使用默认的FilterSecurityInterceptor来进行权限校验。在FilterSecurityInterceptor中会从SecurityContextHolder获取其中的Authentication&#xff0c;然后获取其中的权限信息。判断当前用户是否拥有访问当前资源…

汉口银行IPO之路再添坎坷:多名股东甩卖股权,内控是“老大难”

撰稿|芋圆 近日&#xff0c;总部设在武汉的商业银行——汉口银行股份有限公司&#xff08;以下简称“汉口银行”&#xff09;的股权再现交易信息&#xff0c;先后“亮相” 上海联合产权交易所、北京产权交易所&#xff0c;出售股权的股东包括中国电信以及中国移动全资子公司等…

神经网络(五):U2Net图像分割网络

文章目录 一、网络结构1.1第一种block结构1.2第二种block结构1.3特征图融合模块1.4损失函数1.5总体网络架构1.6代码汇总1.7普通残差块与RSU对比 二、代码复现 参考论文&#xff1a;U2-Net: Going deeper with nested U-structure for salient object detection   这篇文章基于…

从销售到 AI 算法工程师 | 转行人工智能大模型(含面经裁员幸存指南)

我叫王东&#xff0c;90后&#xff0c;和大家分享一下我的人工智能转型之路。 农学毕业&#xff0c;投身互联网做销售 机遇难求&#xff0c;养殖梦碎 我是土生土长的农村人&#xff0c;小时候经常和小鱼小虾打交道&#xff0c;上大学的时候就选择了农学专业&#xff0c;想着…

qmt量化交易策略小白学习笔记第67期【qmt编程之获取ETF申赎清单】

qmt编程之获取ETF申赎清单 qmt更加详细的教程方法&#xff0c;会持续慢慢梳理。 也可找寻博主的历史文章&#xff0c;搜索关键词查看解决方案 --获取ETF申赎清单&#xff01; 实盘或回测qmt&#xff0c;可关注博主咨询~ ETF申赎清单 提示 使用前需要调用xtdata.download_…

JDBC 事务

文章目录 准备数据JDBC操作事务API介绍案例代码小结 准备数据 # 创建一个表&#xff1a;账户表. create database day05_db; # 使用数据库 use day05_db; # 创建账号表 create table account(id int primary key auto_increment,name varchar(20),money double ); # 初始化数据…

如何管理自己的工作任务和时间

在当今快节奏的工作环境中&#xff0c;有效地管理工作任务和时间是取得成功和保持工作生活平衡的关键。以下是一些实用的方法&#xff0c;可以帮助你更好地掌控自己的工作。 一、明确工作任务 1、制定任务清单 每天开始工作前&#xff0c;列出当天需要完成的所有任务。可以使用…

PWA(Progressive web APPs,渐进式 Web 应用)

文章目录 引言I 什么是 PWA功能特性II Web 应用清单引言 PWA 是 Google 于 2016 年提出的概念,于 2017 年正式落地,于 2018 年迎来重大突破,全球顶级的浏览器厂商,Google、Microsoft、Apple 已经全数宣布支持 PWA 技术。 PWA 目的是通过各种 Web 技术实现与原生 App 相近…

Java读取YAML文件

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐&#xff1a;「storm…

基于注意力机制的图表示学习:GRAPH-BERT模型

人工智能咨询培训老师叶梓 转载标明出处 图神经网络&#xff08;GNNs&#xff09;在处理图结构数据方面取得了显著的进展&#xff0c;但现有模型在深层结构中存在性能问题&#xff0c;如“悬挂动画问题”和“过平滑问题”。而且图数据内在的相互连接特性限制了大规模图输入的并…

三天搞了7000,AI绘本副业赚钱新途径,抓住绘本创业,轻松开启副业

**项目简介&#xff1a;**这个项目的原理是利用AI文本工具来编写绘本故事脚本、人物描述及场景设定&#xff0c;完成整个绘本内容的创作。接着&#xff0c;使用百度翻译将文本翻译成英文&#xff0c;并在MJ软件上输入关键词生成相应的图片。随后&#xff0c;将这些图片上传至Pi…

《我的世界:地下城》风灵月影修改器使用指南 —— 掌控冒险,轻松畅游

对于热爱《我的世界&#xff1a;地下城》并寻求更加个性化游戏体验的玩家来说&#xff0c;风灵月影修改器提供了一个强大而便捷的辅助工具。 以下简要步骤将指导你如何安全高效地利用此修改器&#xff0c;让你的探险之旅如虎添翼。 1.下载与安装&#xff1a; 首先&#xff0c…

ESP32 入门笔记02: ESP32-C3 系列( 芯片ESP32-C3FN4) (ESP-IDF + VSCode)

ESP32-C3 系列的 芯片 / 模组 / 开发板 ESP32-C3-DevKitM-1是乐鑫一款搭载 ESP32-C3-MINI-1 或 ESP32-C3-MINI-1U 模组的入门级开发板&#xff08;内置 ESP32-C3FH4 或 ESP32-C3FN4 芯片&#xff09;。 板上模组大部分管脚均已引出至两侧排针&#xff0c;可根据开发实际需求&a…

Android平台RTMP推送模块的设计意义

为什么要做RTMP推送 RTMP是一种广泛使用的流媒体传输协议&#xff0c;它允许视频和音频数据在互联网上实时、高效地传输。实现RTMP推送功能&#xff0c;主要是为了满足以下需求&#xff1a; 实时性要求&#xff1a;RTMP协议具有低延迟的特点&#xff0c;适合用于需要实时交互的…

大数据毕业设计选题推荐-租房数据分析系统-Hive-Hadoop-Spark

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…