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

  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.xdnf.cn/news/1548042.html

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

相关文章

照片压缩方法分享,掌握这些小技巧轻松压缩

照片已成为我们记录生活、分享美好的重要方式。然而&#xff0c;随着手机像素的不断提升&#xff0c;照片文件体积也越来越大&#xff0c;给存储和传输带来了不小的挑战。今天&#xff0c;就为大家介绍几种高效的照片压缩方法&#xff0c;掌握这些方法就能够轻易对图片进行压缩…

寻找右区间

题目链接 寻找右区间 题目描述 注意点 -10^6 < starti < endi < 10^6每个间隔的起点都 不相同如果某个区间 i 不存在对应的 右侧区间 &#xff0c;则下标 i 处的值设为 -1 解答思路 因为本题需要找到每个interval大于interval对应end的最小start值&#xff0c;所…

vue-i18n在使用$t时提示类型错误

1. 问题描述 Vue3项目中&#xff0c;使用vue-i18n&#xff0c;在模版中使用$t时&#xff0c;页面可以正常渲染&#xff0c;但是类型报错。 相关依赖版本如下&#xff1a; "dependencies": {"vue": "^3.4.29","vue-i18n": "^9.1…

红绿灯倒计时读秒数字识别系统源码分享

红绿灯倒计时读秒数字识别检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of …

小程序开发平台源码系统 各行各业适用的小程序开的平台 带完整的安装代码包以及搭建部署教程

系统概述 本系统采用模块化设计&#xff0c;包含前端展示层、后端逻辑处理层、数据库存储层以及管理后台等多个核心组件。前端展示层负责小程序的界面设计与交互体验&#xff1b;后端逻辑处理层则负责数据处理、业务逻辑实现及与第三方服务的对接&#xff1b;数据库存储层用于…

符合二级等保要求的SSL证书

根据等级保护对象在国家安全、经济建设、社会生活中的重要程度&#xff0c;以及一旦遭到破坏、丧失功能或者数据被篡改、泄露、丢失、损毁后&#xff0c;对国家安全、社会秩序、公共利益以及公民&#xff0c;法人和其他组织的合法权益的侵害程度等因素&#xff0c;等级保护对象…

第1章 C++初识

1.1 编写第一个C程序 1.打开Visual Studio点击"创建新项目" 2.点击"空项目"&#xff0c;并点击"下一步" 3.设置"项目名称"并"设置地址" 4.打开项目后&#xff0c;右击"源文件"并选择"添加"的"新建…

低代码可视化开发-uniapp新闻跑马灯组件-代码生成器

新闻跑马灯效果组件是一种在新闻、数据可视化大屏或其他信息展示场景中常用的动态文本展示方式。它通过滚动文本的形式&#xff0c;在有限的空间内展示更多的信息内容&#xff0c;同时增加了视觉吸引力和动态感。以下是对新闻跑马灯效果组件的详细介绍&#xff1a; 一、定义与…

LVGL-触摸屏-实体按键-编码器-多功能按键)

在使用stm32移植lvgl时由于没有触摸屏&#xff0c;所以选择了编码器和按键作为输入设备。但是按照教程全部正确的设置了编码器和按键后&#xff0c;编码器的回调函数不能被调用即encoder_read();函数中的内容不能被调用。debug后发现是创建输入设备时的indev_drv被覆盖&#xf…

​ETHShanghai 2024:十月盛典,首批嘉宾重磅揭晓!

随着「ETHShanghai 2024」的筹备工作不断推进&#xff0c;已经邀请了多位重要嘉宾参与。同时&#xff0c;以太坊联合创始人Vitalik Buterin 也将通过线上形式参与并进行开幕致辞。 目前&#xff0c;已经确认出席的嘉宾还包括 Mask Network 创始人 Suji Yan、EthStorage 创始人…

eCharts扩展图表

地址&#xff1a;echarts图表集 示例截图&#xff1a;

【Redis】下载安装Redis和Redis图形化界面工具教程(2024最新版本,史上最详细)

目录 一、Redis简介 二、Redis下载和安装 2.1、下载 2.2、安装 2.3、环境变量配置&#xff08;可省略&#xff09; 三、Redis启动验证 3.1、点击键盘上的WinR键&#xff0c;在跳出的运行界面中输入cmd并确定 3.2、输入redis-cli -v 查看redis的版本号 3.3、接着我们再…

python爬虫案例——抓取三级跳转网页,实现逐页抓取,数据存入mysql数据库(10)

文章目录 1、目标任务2、网页分析3、完整代码1、目标任务 目标站点:情话网(http://www.ainicr.cn/tab/) 任务:抓取该网站下所有标签下的所有情话语句,并将其存入mysql数据库 2、网页分析 用浏览器打开网页,按F12或右键检查,进入开发者模式,在Network-Doc下找到网页的数…

Thingsboard规则链:Related Device Attributes节点详解

引言 在物联网&#xff08;IoT&#xff09;领域&#xff0c;Thingsboard作为一款强大的物联网平台&#xff0c;其规则链功能为企业提供了高度定制化的数据处理和自动化控制方案。其中&#xff0c;Related Device Attributes节点是一个特别实用的组件&#xff0c;它能够访问和操…

【专题】新能源发电行业及其市场化进程概览白皮书报告合集PDF分享(附原数据表)

原文链接&#xff1a;https://tecdat.cn/?p37802 随着中国经济结构的持续优化以及能源政策的不断进步&#xff0c;我国的能源消费呈现出稳定增长的态势。与此同时&#xff0c;能源利用效率逐步提高&#xff0c;清洁能源在能源结构中的比例也在稳步上升&#xff0c;这为国家的…

进阶数据库系列(十三):PostgreSQL 分区分表

概述 在组件开发迭代的过程中&#xff0c;随着使用时间的增加&#xff0c;数据库中的数据量也不断增加&#xff0c;因此数据库查询越来越慢。 通常加速数据库的方法很多&#xff0c;如添加特定的索引&#xff0c;将日志目录换到单独的磁盘分区&#xff0c;调整数据库引擎的参…

老照片修复工具有哪些?怎么让老照片焕发新光彩?

在那些泛黄的相框中&#xff0c;珍藏着我们最珍贵的记忆。 岁月流转&#xff0c;照片上的影像逐渐模糊&#xff0c;但那份情感却愈发深刻。 如何让这些老照片恢复往日的光彩&#xff0c;让那些珍贵的瞬间再次清晰呈现&#xff1f; 本文将带你探索老照片修复高清的技巧&#…

新书速览|Stable Diffusion-ComfyUI AI绘画工作流解析

《Stable Diffusion-ComfyUI AI绘画工作流解析》 本书内容 《Stable Diffusion-ComfyUI AI绘画工作流解析》从零开始&#xff0c;详尽系统地讲解从本地部署ComfyUI、下载安装自定义节点&#xff0c;到搭建各种工作流程的全过程。同时&#xff0c;辅以3D形象转绘、艺术二维码和证…

如火似茶的AI Bots到底有什么现实意义呢?

你好&#xff0c;我是三桥君 自AIGC潮流兴起以来&#xff0c;基于自注意力机制的大模型成为资本市场疯狂炒作的对象。然而&#xff0c;经过一年多的狂热之后&#xff0c;市场逐渐回归理性。这时候会有人担心&#xff0c;大模型是否会像元宇宙、Web 3.0&#xff0c;甚至比特币那…

Llama 3.1 技术研究报告-4

五、结果 我们对Llama 3进⾏了⼴泛的系列评估&#xff0c;研究了以下⽅⾯的性能&#xff1a;(1) 预训练语⾔模型&#xff0c;(2) 后训练语⾔模型&#xff0c;以及 (3) Llama 3的安全特性。我们在下⾯的各个⼩节中分别呈现这些评估的结果。 5.1 预训练语⾔模型 在本节中&…