AsyncContext优雅实现HTTP长轮询接口
一、背景
接到一个需求,实现方案时需要提供一个HTTP接口,接口需要hold住5-8秒,轮询查询数据库,一旦数据库中值有变化,取出变化的值进行处理,处理完成后返回响应。这不就是长轮询吗,如何优雅的实现呢?
二、方案设计
在 Spring 中,AsyncContext 是用于支持异步处理的一个重要的特性。它允许我们在 servlet 请求处理过程中,将长时间运行的操作放在一个单独的线程中执行,而不会阻塞其他请求的处理。
AsyncContext 在以下两种情况下特别有用:
-
长时间运行的操作:当我们需要执行一些耗时的操作,例如网络请求、数据库查询或其他 I/O 操作时,通过将这些操作放在一个新的线程中,可以避免阻塞 servlet 容器中的线程,提高应用的并发性能。
-
推送异步响应:有时候,我们可能需要推送异步产生的响应,而不是等到所有操作都完成后再下发响应。通过 AsyncContext,我们可以在任何时间点上触发异步响应,将结果返回给客户端。
使用 AsyncContext 的步骤如下:
- 在 servlet 中启用异步模式:在 servlet 中,通过调用
startAsync()方法,可以获取到当前请求的 AsyncContext 对象,从而启用异步处理模式。
HttpServletRequest request = ...;
AsyncContext asyncContext = request.startAsync();
- 指定异步任务:通过调用 AsyncContext 对象的
start()方法,在新的线程中执行需要异步处理的任务。
asyncContext.start(() -> {// 异步任务逻辑
});
- 提交响应:在异步任务完成后,可以调用 AsyncContext 对象的
complete()方法,以表示异步操作完成。
asyncContext.complete();
需要注意的是,我们在使用 AsyncContext 时需要特别注意线程安全。由于异步任务在单独的线程中执行,所以可能存在并发问题。因此,在编写异步任务逻辑时,需要注意线程安全性,使用合适的同步措施。
另外,AsyncContext 也支持超时设置、错误处理、事件监听等功能,这些可以通过相应的方法和回调进行配置。可以根据具体的需求使用这些功能来优化异步处理的逻辑。
总结来说,Spring 的 AsyncContext 提供了方便的异步处理机制,可以提高应用的并发性能,并支持推送异步响应,使得应用更具有响应性和可伸缩性。
三、代码1
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.*;@RestController
@RequestMapping("/api/byai/transform")
@Slf4j
public class AsyncTestController {@Resourceprivate RedisTemplate<String, String> redisTemplate;private final ExecutorService timeoutChecker = new ThreadPoolExecutor(1,1,1000,TimeUnit.SECONDS,new ArrayBlockingQueue<>(1000));private static final ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("longPolling-timeout-checker-%d").build();// private static boolean result = false;@PostMapping("/async")public void async(HttpServletRequest request, HttpServletResponse response) {// 创建AsyncContextAsyncContext asyncContext = request.startAsync(request, response);// 设置处理超时时间8sasyncContext.setTimeout(8000L);// asyncContext监听JdAsyncTestListener asyncListener = new JdAsyncTestListener(redisTemplate,asyncContext);asyncContext.addListener(asyncListener);// 定时处理业务,处理成功后asyncContext.complete();完成异步请求asyncContext.start(asyncListener);}// 模拟业务处理完成@PostMapping("/set")public ResultModel notify(String key, String value) {redisTemplate.opsForValue().set(key, value);return ResultModel.success();}@PostMapping("/get")public ResultModel get(String key) {String s = redisTemplate.opsForValue().get(key);return ResultModel.success(s);}@PostMapping("/del")public ResultModel del(String key) {redisTemplate.delete(key);return ResultModel.success();}
}
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.*;@RestController
@RequestMapping("/api/test")
@Slf4j
public class AsyncTestController {@Resourceprivate RedisTemplate<String, String> redisTemplate;private final ExecutorService timeoutChecker = new ThreadPoolExecutor(1,1,1000,TimeUnit.SECONDS,new ArrayBlockingQueue<>(1000));private static final ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("longPolling-timeout-checker-%d").build();// private static boolean result = false;@PostMapping("/async")public void async(HttpServletRequest request, HttpServletResponse response) {// 创建AsyncContextAsyncContext asyncContext = request.startAsync(request, response);// 设置处理超时时间8sasyncContext.setTimeout(8000L);// asyncContext监听JdAsyncTestListener asyncListener = new JdAsyncTestListener(redisTemplate,asyncContext);asyncContext.addListener(asyncListener);// 定时处理业务,处理成功后asyncContext.complete();完成异步请求asyncContext.start(asyncListener);}// 模拟业务处理完成@PostMapping("/set")public ResultModel notify(String key, String value) {redisTemplate.opsForValue().set(key, value);return ResultModel.success();}@PostMapping("/get")public ResultModel get(String key) {String s = redisTemplate.opsForValue().get(key);return ResultModel.success(s);}@PostMapping("/del")public ResultModel del(String key) {redisTemplate.delete(key);return ResultModel.success();}
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import java.io.IOException;@Slf4j
public class JdAsyncTestListener implements AsyncListener,Runnable {boolean isComplete;private RedisTemplate<String, String> redisTemplate;private AsyncContext asyncContext;public JdAsyncTestListener(RedisTemplate<String, String> redisTemplate, AsyncContext asyncContext) {this.redisTemplate = redisTemplate;this.asyncContext = asyncContext;}@Overridepublic void run() {try {while(true){if(isComplete){log.info("已经退出");break;}boolean b = redisTemplate.opsForValue().get(1) != null;log.info("获取标志位:"+b);Thread.sleep(300);if (b) {asyncContext.getResponse().getWriter().print(1);asyncContext.complete();}}} catch (IOException e) {e.printStackTrace();} catch (InterruptedException e) {throw new RuntimeException(e);}}@Overridepublic void onComplete(AsyncEvent asyncEvent) throws IOException {log.info("结束了");isComplete = true;}@Overridepublic void onTimeout(AsyncEvent asyncEvent) throws IOException {log.info("超时了");isComplete = true;}@Overridepublic void onError(AsyncEvent asyncEvent) throws IOException {}@Overridepublic void onStartAsync(AsyncEvent asyncEvent) throws IOException {}
}
四、代码二
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;@Validated
@RestController
@RequestMapping("/api/test")
@Slf4j
public class TestController {@Resourceprivate RedisTemplate<String, String> redisTemplate;private final ScheduledExecutorService timeoutChecker = new ScheduledThreadPoolExecutor(10, threadFactory);private static final ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("longPolling-timeout-checker-%d").build();private static boolean result = false;private final boolean isTimeout = false;/*** 消息** @return*/@PostMapping("/test")public void callback(@RequestBody TestLongPollRequest testLongPollRequest, HttpServletRequest request, HttpServletResponse response) {// 创建AsyncContextAsyncContext asyncContext = request.startAsync(request, response);String jdCustomerId = jdLongPollRequest.getJdCustomerId();// 设置处理超时时间8sasyncContext.setTimeout(8000L);// asyncContext监听asyncContext.addListener(new AsyncListener() {@Overridepublic void onComplete(AsyncEvent asyncEvent) throws IOException {log.info("onComplete={}", asyncEvent);}@Overridepublic void onTimeout(AsyncEvent asyncEvent) throws IOException {log.info("onTimeout={}", asyncEvent);ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();map.put("code", "500"); asyncContext.getResponse().getWriter().print(JSON.toJSONString(map));asyncContext.complete();}@Overridepublic void onError(AsyncEvent asyncEvent) throws IOException {log.info("onError={}", asyncEvent);}@Overridepublic void onStartAsync(AsyncEvent asyncEvent) throws IOException {log.info("onStartAsync={}", asyncEvent);}});// 定时处理业务,处理成功后asyncContext.complete();完成异步请求timeoutChecker.scheduleAtFixedRate(() -> {try {String redisKey = getcustomerProcessRes(customerId);String redisValue = redisTemplate.opsForValue().get(redisKey);result = StringUtils.isNotBlank(redisValue);if (result) {//todo 长轮询查询数据库。通过customerId查询send(customerId, redisValue);ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();map.put("code", "200");map.put("msg", redisValue);asyncContext.getResponse().getWriter().print(JSON.toJSONString(map));asyncContext.complete();}} catch (IOException e) {e.printStackTrace();}}, 0, 100L, TimeUnit.MILLISECONDS);}/*** 发送消息*/private void send(String customerId, String content) {}}
相关文章:
AsyncContext优雅实现HTTP长轮询接口
一、背景 接到一个需求,实现方案时需要提供一个HTTP接口,接口需要hold住5-8秒,轮询查询数据库,一旦数据库中值有变化,取出变化的值进行处理,处理完成后返回响应。这不就是长轮询吗,如何优雅的实…...
如何制作一个百货小程序
在这个数字化时代,小程序已成为各行各业的必备工具。其中,百货小程序因其便捷性和多功能性,越来越受到人们的青睐。那么,如何制作一个百货小程序呢?下面,我们就详细介绍一下无需编写代码的步骤。 一、进入后…...
【人工智能】—局部搜索算法、爬山法、模拟退火、局部剪枝、遗传算法
文章目录 局部搜索算法内存限制局部搜索算法示例:n-皇后爬山算法随机重启爬山模拟退火算法局部剪枝搜索遗传算法小结 局部搜索算法 在某些规模太大的问题状态空间内,A*往往不够用 问题空间太大了无法访问 f 小于最优的所有状态通常,甚至无法储…...
MATLAB旋转动图的绘制
MATLAB旋转动图的绘制 文章目录 MATLAB旋转动图的绘制1、动图效果2、matlab代码 利用matlab实现三维旋转动图的绘制。 1、动图效果 2、matlab代码 close all clear clcf(x,y,z)(x.^2 (9./4).*y.^2 z.^2 - 1).^3 - x.^2.*z.^3 - (9./80).*y.^2.*z.^3; [x,y,z]meshgrid(linspac…...
算法笔记 近似最近邻查找(Approximate Nearest Neighbor Search,ANN)
1 介绍 精准最近邻搜索中数据维度一般较低,所以会采用穷举搜索,即在数据库中依次计算其中样本与所查询数据之间的距离,抽取出所计算出来的距离最小的样本即为所要查找的最近邻。 当数据量非常大的时候,搜索效率急剧下降。——>…...
uni-app 之 vue语法
uni-app 之 vue语法 image.png --- v-html 字符 --- image.png <template><view><view>{{title}}</view>--- v-html 字符 ---<view>{{title2}}</view><view v-html"title2"></view><view>{{arr}}</view&g…...
Android之RecyclerView仿ViewPage滑动
文章目录 前言一、效果图二、实现步骤1.xml主布局2.所有用到的drawable资源文件3.xml item布局4.adapter适配器5.javabean实体类6.activity使用 总结 前言 我们都知道ViewPageFragment滑动,但是的需求里面已经有了这玩意,但是在Fragment中还要有类似功能…...
【owt-server】AudioSendAdapter分析
owt-server/source/core/rtc_adapter/AudioSendAdapter.cc使用其他线程运行rtprtcpmodule taskrunner分配线程:因此,对rtprtcp的使用都是加了mutex的:首先为音频发送者生成一个随机的ssrc并注册 // SSRCs of this type.std::vector<uint32_t> ssrcs_;发送还要向rtprtc…...
day33 List接口
List实现类 java.util.ArrayList: 底层通过数组保存数据 , 查询快,增删慢 java.util.LinkedList: 底层通过链表保存数据, 查询慢,增删快 如果对操作性能没有特殊要求,我们一般选择ArrayList…...
云原生周刊:Linkerd 发布 v2.14 | 2023.9.4
开源项目推荐 Layerform Layerform 是一个 Terraform 包装器,可帮助工程师使用纯 Terraform 文件构建可重用的基础设施。 为了实现重用,Layerform 引入了层的概念。每层都包含一些基础设施,并且可以堆叠在另一层之上。 除了更易于使用之外…...
CS420 课程笔记 P5 - 内存编辑 数据类型
文章目录 IntroductionData typesBooleansNegative numbers (Signed integers)Floating-point numbers (fractional numbers) Unknown value scansHealth findingFloat finding (Player position hack / Teleport hack) Additional things Introduction 这节课将结束数据类型并…...
oracle报错 ORA-02290: 违反检查约束条件问题
保存数据库信息时,提示违反检查约束条件,如图: org.springframework.dao.DataIntegrityViolationException: ### Error updating database. Cause: java.sql.SQLIntegrityConstraintViolationException: ORA-02290: 违反检查约束条件 (MXUSER…...
Prometheus + grafana 的监控平台部署
一、Prometheus安装 tar -zxvf prometheus-2.44.0.linux-amd64.tar.gz -C /opt/module/ sudo chown -R bigdata:bigdata /opt/module/prometheus-2.44.0.linux-amd64 mv /opt/module/prometheus-2.44.0.linux-amd64 /opt/module/prometheus-2.44.0 ln -s /opt/module/promethe…...
npm、yarn、pnpm
一、简介 CommonJS 的出现,使 node 环境下的 JS 代码可以用模块更加细粒度的划分。一个类、一个函数、一个对象、一个配置等等均可以作为模块,这种细粒度的划分,是开发大型应用的基石。 为了解决在开发过程中遇到的常见问题,比如…...
力扣|两数相加
先放题目: 给你两个非空的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。 请你将两个数相加,并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外,…...
prometheus通过blackbox-exporter监控web站点证书
1 概述 线上站点普遍是https,因此监控https web站点的证书的过期时间,是一个基础性需求。例如,证书过期会导致tls握手失败,进而导致用户无法正常访问web站点。 blackbox-expoter是一个web服务,它暴露了一个接口&#…...
CentOS7 Hadoop3.3.0 安装与配置
一、安装JDK 1、创建文件夹tools和training用于存放压缩包和解压使用,tools存放压缩包,training用于解压后安装jdk和hadoop的路径。 1)回到路径为 / 的位置 cd /2) 创建 tools 和 training mkdir toolsmkdir training3) 进入tools文件夹 …...
2023年9月CDGA/CDGP数据治理认证考试报名,当然弘博创新
据DAMA中国官方网站消息,2023年度第三期DAMA中国CDGA和CDGP认证考试定于2023年9月23日举行。 报名通道现已开启,相关事宜通知如下: 考试科目: 数据治理工程师(CertifiedDataGovernanceAssociate,CDGA) 数据治理专家(CertifiedDataGovernanc…...
Re45:读论文 GPT-1 Improving Language Understanding by Generative Pre-Training
诸神缄默不语-个人CSDN博文目录 诸神缄默不语的论文阅读笔记和分类 论文全名:Improving Language Understanding by Generative Pre-Training 论文下载地址:https://www.mikecaptain.com/resources/pdf/GPT-1.pdf 本文是2018年OpenAI的工作,…...
VB.NET 如何将某个Excel的工作表中复制到另一个的Excel中的工作表中https://bbs.csdn.net/topics/392861034
参考http://share.freesion.com/306372/可以实现直接拷贝指定表 Private Sub Excel复制工作簿()Dim myExcelApp As New Microsoft.Office.Interop.Excel.ApplicationmyExcelApp.Workbooks.Open(System.Environment.CurrentDirectory "\\测试用例.xlsx", Type.Missin…...
GitHub Actions缓存终极升级指南:从v3到v5的平滑迁移路径
GitHub Actions缓存终极升级指南:从v3到v5的平滑迁移路径 【免费下载链接】cache Cache dependencies and build outputs in GitHub Actions 项目地址: https://gitcode.com/gh_mirrors/cach/cache GitHub Actions缓存是加速CI/CD工作流程的关键工具…...
企业级翻译系统落地:TranslateGemma助力国际化团队代码协作
企业级翻译系统落地:TranslateGemma助力国际化团队代码协作 1. 引言:全球化开发的语言挑战 在跨国企业技术团队中,代码协作常常面临语言障碍:核心框架文档是英文,而部分团队成员更习惯使用中文;开源项目注…...
Calibre中文路径保护插件:如何让电子书保持原汁原味的中文命名
Calibre中文路径保护插件:如何让电子书保持原汁原味的中文命名 【免费下载链接】calibre-do-not-translate-my-path Switch my calibre library from ascii path to plain Unicode path. 将我的书库从拼音目录切换至非纯英文(中文)命名 项目…...
Cadence计算器实战:从波形运算到自定义函数编程
1. 差分信号处理的核心挑战 在模拟电路设计中,差分信号的处理一直是工程师们面临的常见难题。我刚入行时,第一次看到差分信号的波形图完全懵了——两条看似镜像对称的曲线,到底该怎么计算它们的共模电压、差模电压这些关键参数?传…...
ElementUI 年份范围选择器实战:手把手教你封装 el-year-picker 组件(附完整代码)
ElementUI 年份范围选择器实战:手把手教你封装 el-year-picker 组件(附完整代码) 在后台管理系统开发中,日期选择组件是使用频率极高的功能模块。ElementUI 作为 Vue 生态中最受欢迎的 UI 框架之一,虽然提供了丰富的日…...
零基础玩转GLM-OCR:一键部署,轻松解析图片里的文字、表格和公式
零基础玩转GLM-OCR:一键部署,轻松解析图片里的文字、表格和公式 1. 引言:你的“智能读图”助手来了 想象一下这个场景:你收到一份PDF格式的合同,需要把里面的关键条款摘出来;或者你看到一张满是数据的表格…...
抖音下载器技术深度解析:从单视频到批量下载的完整实战指南
抖音下载器技术深度解析:从单视频到批量下载的完整实战指南 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback …...
AI 净界环境搭建:利用 Docker 镜像免配置运行
AI 净界环境搭建:利用 Docker 镜像免配置运行 你是不是也遇到过这样的烦恼?好不容易拍了一张满意的照片,或者找到一张心仪的素材图,却因为背景杂乱而无法直接使用。用传统的抠图工具,要么边缘粗糙得像狗啃的ÿ…...
OpenClaw模型微调:让Phi-3-mini适配你的专属工作流
OpenClaw模型微调:让Phi-3-mini适配你的专属工作流 1. 为什么需要微调Phi-3-mini? 当我第一次将Phi-3-mini接入OpenClaw时,发现这个"聪明"的小模型在处理我的专业领域任务时总有些力不从心。它能够理解通用指令,但当我…...
interactive-deep-colorization与Adobe Photoshop Elements对比分析:免费AI上色工具如何超越专业软件?
interactive-deep-colorization与Adobe Photoshop Elements对比分析:免费AI上色工具如何超越专业软件? 【免费下载链接】interactive-deep-colorization Deep learning software for colorizing black and white images with a few clicks. 项目地址: …...
