【Easylive】视频删除方法详解:重点分析异步线程池使用
【Easylive】项目常见问题解答(自用&持续更新中…) 汇总版
方法整体功能
这个deleteVideo方法是一个综合性的视频删除操作,主要完成以下功能:
- 权限验证:检查视频是否存在及用户是否有权限删除
- 核心数据删除:删除视频主信息、投稿信息
- 经济系统调整:扣除用户发布视频获得的硬币
- 搜索索引清理:从Elasticsearch中移除文档
- 异步清理关联数据:使用线程池异步删除分P视频、弹幕、评论等关联数据及物理文件
重点:异步线程池部分详解
1. 线程池初始化
private static ExecutorService executorService = Executors.newFixedThreadPool(10);
• 线程池类型:固定大小线程池(10个线程)
• 特点:
• 池中线程数量固定不变
• 适合已知并发量的稳定负载场景
• 超出线程数的任务会在队列中等待
• 潜在问题:
• 使用无界队列(默认LinkedBlockingQueue),可能导致OOM
• 静态变量生命周期与应用一致,可能造成线程泄漏
2. 异步任务执行逻辑
executorService.execute(() -> {// 异步任务代码块
});
• 任务封装:使用Lambda表达式封装Runnable任务
• 执行方式:execute()方法提交任务到线程池
• 与事务的关系:
• 异步任务在新线程中执行
• 不受主方法@Transactional注解影响,形成独立的事务上下文
• 若异步操作需要事务,需在任务内部添加事务注解
3. 异步任务具体操作
(1) 查询和删除分P视频
VideoInfoFileQuery videoInfoFileQuery = new VideoInfoFileQuery();
videoInfoFileQuery.setVideoId(videoId);
List<VideoInfoFile> videoInfoFileList = this.videoInfoFileMapper.selectList(videoInfoFileQuery);
videoInfoFileMapper.deleteByParam(videoInfoFileQuery);
• 操作顺序:先查询后删除
• 目的:获取文件路径用于后续物理删除
(2) 删除关联投稿信息
VideoInfoFilePostQuery videoInfoFilePostQuery = new VideoInfoFilePostQuery();
videoInfoFilePostQuery.setVideoId(videoId);
videoInfoFilePostMapper.deleteByParam(videoInfoFilePostQuery);
• 直接删除:无需查询,根据videoId直接删除
(3) 删除弹幕数据
VideoDanmuQuery videoDanmuQuery = new VideoDanmuQuery();
videoDanmuQuery.setVideoId(videoId);
videoDanmuMapper.deleteByParam(videoDanmuQuery);
• 批量删除:通过videoId一次性删除所有关联弹幕
(4) 删除评论数据
VideoCommentQuery videoCommentQuery = new VideoCommentQuery();
videoCommentQuery.setVideoId(videoId);
videoCommentMapper.deleteByParam(videoCommentQuery);
• 级联删除:通常需要确保评论的关联数据(回复、点赞等)也被清理
(5) 物理文件删除
for (VideoInfoFile item : videoInfoFileList) {try {FileUtils.deleteDirectory(new File(appConfig.getProjectFolder() + item.getFilePath()));} catch (IOException e) {log.error("删除文件失败,文件路径:{}", item.getFilePath());}
}
• 关键点:
• 使用deleteDirectory删除整个目录
• 捕获并记录IO异常,避免任务中断
• 文件路径拼接了项目基础目录(appConfig.getProjectFolder())
4. 异步设计的优缺点分析
优点
- 响应速度:主线程快速返回,用户体验好
- 资源隔离:IO密集型操作不影响核心业务
- 错误隔离:文件删除失败不影响主流程
缺点及风险
-
事务不一致:
// 主事务提交后异步任务才执行 // 若异步任务失败,系统处于不一致状态 -
错误处理缺失:
// 当前实现没有记录任务执行结果 // 无法知道异步操作是否成功 -
资源竞争:
// 固定10个线程可能在高并发时成为瓶颈 // 文件删除操作可能阻塞其他异步任务
5. 改进建议
(1) 增强型线程池配置
private static ExecutorService executorService = new ThreadPoolExecutor(5, // 核心线程数20, // 最大线程数60, TimeUnit.SECONDS, // 空闲线程存活时间new ArrayBlockingQueue<>(1000), // 有界队列new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
(2) 添加任务结果处理
Future<?> future = executorService.submit(() -> {// 任务代码
});// 可选:通过Future跟踪任务状态
future.get(10, TimeUnit.SECONDS); // 带超时的等待
(3) 事务补偿机制
@TransactionalEventListener(phase = AFTER_COMMIT)
public void handleAfterCommit(VideoDeleteEvent event) {// 主事务提交后执行异步清理asyncCleanService.cleanVideoResources(event.getVideoId());
}
(4) 完善日志监控
executorService.execute(() -> {MDC.put("traceId", UUID.randomUUID().toString());try {// 任务代码log.info("视频资源清理完成: {}", videoId);} catch (Exception e) {log.error("视频资源清理失败: {}", videoId, e);// 发送告警或记录失败状态} finally {MDC.clear();}
});
总结
这个删除方法通过线程池实现了:
- 核心数据同步删除:保证关键数据立即清除
- 资源异步清理:提升响应速度
- 物理文件删除:释放存储空间
关键改进方向:
• 线程池参数优化
• 完善错误处理和状态跟踪
• 考虑引入事务事件机制
• 增加监控和告警能力
这种设计适合对实时性要求高但允许最终一致性的场景,是典型的"快速响应+后台清理"架构模式。
异步线程池及executorService.execute详解
一、异步线程池基础
1. 线程池核心概念
线程池是一种线程管理机制,它维护着多个线程,避免频繁创建和销毁线程带来的性能开销。在Java中,主要通过ExecutorService接口及其实现类来使用线程池。
2. 线程池关键参数
| 参数 | 说明 | 示例值 |
|---|---|---|
| corePoolSize | 核心线程数 | 10 |
| maximumPoolSize | 最大线程数 | 50 |
| keepAliveTime | 空闲线程存活时间 | 60秒 |
| workQueue | 任务队列 | new LinkedBlockingQueue(1000) |
| threadFactory | 线程创建工厂 | Executors.defaultThreadFactory() |
| handler | 拒绝策略 | AbortPolicy |
3. 线程池工作流程
- 提交任务时,优先使用核心线程处理
- 核心线程全忙时,任务进入队列
- 队列满时,创建新线程(不超过maxPoolSize)
- 线程数达最大值且队列满时,触发拒绝策略
二、executorService.execute方法详解
1. 方法签名
void execute(Runnable command)
2. 核心特点
• 异步执行:立即返回,不阻塞调用线程
• 无返回值:适用于不需要获取结果的场景
• 异常处理:任务异常会传递给未捕获异常处理器
3. 执行流程
4. 在示例代码中的使用
executorService.execute(() -> {// 1. 查询和删除分P视频VideoInfoFileQuery videoInfoFileQuery = new VideoInfoFileQuery();videoInfoFileQuery.setVideoId(videoId);List<VideoInfoFile> videoInfoFileList = this.videoInfoFileMapper.selectList(videoInfoFileQuery);videoInfoFileMapper.deleteByParam(videoInfoFileQuery);// 2. 删除其他关联数据...// 3. 删除物理文件for (VideoInfoFile item : videoInfoFileList) {try {FileUtils.deleteDirectory(new File(appConfig.getProjectFolder() + item.getFilePath()));} catch (IOException e) {log.error("删除文件失败,文件路径:{}", item.getFilePath());}}
});
5. 为什么使用execute而不是submit?
| 对比项 | execute | submit |
|---|---|---|
| 返回值 | 无 | Future对象 |
| 异常处理 | 直接抛出 | 封装在Future中 |
| 适用场景 | 简单异步任务 | 需要获取结果的任务 |
| 示例代码 | 当前场景适合 | 需要结果时使用 |
在当前场景下:
• 不需要获取清理操作的结果
• 简单的日志记录已足够
• 更轻量级的执行方式
三、线程池配置优化建议
1. 当前实现的潜在问题
private static ExecutorService executorService = Executors.newFixedThreadPool(10);
• 使用无界队列(默认LinkedBlockingQueue),可能导致OOM
• 固定线程数无法应对突发流量
• 缺少合理的拒绝策略
2. 推荐改进方案
private static ExecutorService executorService = new ThreadPoolExecutor(5, // 核心线程数20, // 最大线程数60, TimeUnit.SECONDS, // 空闲线程存活时间new ArrayBlockingQueue<>(1000), // 有界队列new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
3. 各参数说明
- corePoolSize=5:保持5个常驻线程
- maxPoolSize=20:突发流量时可扩展到20线程
- keepAliveTime=60s:空闲线程60秒后回收
- 有界队列(1000):防止资源耗尽
- CallerRunsPolicy:队列满时由调用线程执行任务
四、异常处理机制
1. 当前实现的异常处理
try {FileUtils.deleteDirectory(...);
} catch (IOException e) {log.error("删除文件失败...");
}
• 仅记录日志,无恢复机制
• 异常不会传播到主线程
2. 增强型异常处理方案
方案1:全局异常处理器
executorService = new ThreadPoolExecutor(// ...其他参数new ThreadPoolExecutor.AbortPolicy() {@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor e) {// 记录被拒绝的任务log.warn("Task rejected: {}", r.toString());super.rejectedExecution(r, e);}}
);// 设置未捕获异常处理器
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {log.error("Uncaught exception in thread: {}", t.getName(), e);
});
方案2:封装任务
public class SafeRunnable implements Runnable {private final Runnable task;public SafeRunnable(Runnable task) {this.task = task;}@Overridepublic void run() {try {task.run();} catch (Exception e) {log.error("Task execution failed", e);// 可添加重试或补偿逻辑}}
}// 使用方式
executorService.execute(new SafeRunnable(() -> {// 任务代码
}));
五、性能监控建议
1. 添加线程池监控
// 定时打印线程池状态
ScheduledExecutorService monitor = Executors.newSingleThreadScheduledExecutor();
monitor.scheduleAtFixedRate(() -> {ThreadPoolExecutor tpe = (ThreadPoolExecutor) executorService;log.info("Pool stats: active={}, queue={}/{}, completed={}",tpe.getActiveCount(),tpe.getQueue().size(),tpe.getQueue().remainingCapacity(),tpe.getCompletedTaskCount());
}, 1, 1, TimeUnit.MINUTES);
2. 关键监控指标
| 指标 | 说明 | 健康值参考 |
|---|---|---|
| activeCount | 活动线程数 | < maxPoolSize |
| queueSize | 队列大小 | < queueCapacity * 0.8 |
| completedTaskCount | 已完成任务 | 持续增长 |
| rejectedCount | 被拒绝任务 | = 0 |
六、实际应用场景分析
1. 当前视频删除场景特点
• 耗时操作:文件删除可能很慢
• 非关键路径:不影响主业务流程
• 允许延迟:最终一致性即可
• 可能失败:文件可能被占用等
2. 为什么适合使用线程池?
- 解耦:将清理操作与主业务分离
- 提速:主线程快速返回
- 可控:通过线程池限制资源使用
- 可扩展:方便添加重试等机制
3. 潜在风险及应对
| 风险 | 应对措施 |
|---|---|
| 线程泄漏 | 使用有界队列,合理配置存活时间 |
| 任务丢失 | 添加持久化队列或任务记录 |
| 资源竞争 | 监控和动态调整线程池参数 |
| 异常传播 | 完善任务级别的异常处理 |
七、总结最佳实践
- 选择合适的线程池类型:根据场景选择fixed/cached/custom
- 使用有界队列:防止资源耗尽
- 配置合理的拒绝策略:如CallerRunsPolicy
- 完善异常处理:任务级别和全局级别
- 添加监控:实时了解线程池状态
- 考虑任务重要性:关键任务建议使用带返回值的submit
在视频删除场景中,通过线程池异步处理清理任务是一种合理的设计,但需要注意:
• 线程池参数的合理配置
• 异常情况的妥善处理
• 重要操作的日志记录
• 系统资源的监控告警
相关文章:
【Easylive】视频删除方法详解:重点分析异步线程池使用
【Easylive】项目常见问题解答(自用&持续更新中…) 汇总版 方法整体功能 这个deleteVideo方法是一个综合性的视频删除操作,主要完成以下功能: 权限验证:检查视频是否存在及用户是否有权限删除核心数据删除&…...
力扣hot100_回溯(2)_python版本
一、39. 组合总和(中等) 代码: class Solution:def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:ans []path []def dfs(i: int, left: int) -> None:if left 0:# 找到一个合法组合ans.append(pa…...
SGLang实战:从KV缓存复用到底层优化,解锁大模型高效推理的全栈方案
在当今快速发展的人工智能领域,大型语言模型(LLM)的应用已从简单对话扩展到需要复杂逻辑控制、多轮交互和结构化输出的高级任务。面对这一趋势,如何高效地微调并部署这些大模型成为开发者面临的核心挑战。本文将深入探讨SGLang——这一专为大模型设计的高…...
LPDDR4内存颗粒命名规则全解析:三星、镁光、海力士、南亚、长鑫等厂商型号解码与选型指南
由于之前DDR的系列选型文章有很好的反馈,所以补充LPDDR4低功耗内存的选型和命名规则,总结了目前市面上常用的内存,供硬件工程师及数码爱好者参考。 在智能手机、平板电脑和低功耗设备中,LPDDR4 SDRAM凭借其高带宽、低功耗特性成为…...
特权FPGA之Johnson移位
完整代码: module johnson(clk,rst_n,led,sw1_n,sw2_n,sw3_n);input clk; //时钟信号,50MHz input rst_n; //复位信号,低电平有效 output[3:0] led; //LED控制,1--灭…...
网络安全小知识课堂(最终完结版)
网络安全入门 :从 “小白” 到 “守护者” 的蜕变之旅 写在完结之际 历经 13 篇的深度探索,我们从 DDoS 攻击的 “流量洪水” 一路闯关到 HTTPS 的 “加密堡垒”,揭开了网络安全世界的层层面纱。感谢每一位读者的陪伴与互动,你们…...
2025年AI生成引擎搜索发展现状与趋势总结
2025年AI生成引擎搜索发展现状与趋势总结 一、国内外AI生成引擎搜索发展现状 1. 国内动态 社交搜索崛起:小红书2024年Q4日均搜索量达6亿次,用户更依赖社交平台UGC内容进行决策(如购物、旅游场景)&#…...
【杂谈】Godot4.4导出到Android平台(正式导出)
学博而后可约,事历而后知要。 目录 一、准备二、Gradle构建三、配置Java SDK四、配置Android SDK五、配置密钥 一、准备 本文在前文【杂谈】Godot4.4导出到安卓平台(调试导出)的基础上,进行正式导出。调试导出并不是真正的编译导…...
VBA将Word文档内容逐行写入Excel
如果你需要将Word文档的内容导入Excel工作表来进行数据加工,使用下面的代码可以实现: Sub ImportWordToExcel()Dim wordApp As Word.ApplicationDim wordDoc As Word.DocumentDim excelSheet As WorksheetDim filePath As VariantDim i As LongDim para…...
基于AI设计开发出来的业务系统是什么样的?没有菜单?没有表格?
基于AI设计开发出的业务系统仍然会包含菜单、表格等传统UI元素,但AI技术会显著改变它们的实现方式和交互逻辑。以下是具体分析: 一、传统元素的持续存在 功能刚需性 • 菜单承担着系统导航的核心功能,表格则是结构化数据展示的基础载体。根…...
C++ -异常之除以 0 问题(整数除以 0 编译时检测、整数除以 0 运行时检测、浮点数除以 0 编译时检测、浮点数除以 0 运行时检测)
一、整数除以 0(编译时检测) 1、演示 #include <iostream>using namespace std;int main() {int result 10 / 0;cout << result << endl;return 0; }程序无法运行,输出结果 error C2124: 被零除或对零求模2、演示解读 …...
数字足迹管理(DFM):你的网络隐身指南
数字足迹管理(DFM):你的网络隐身指南 你可能不知道,你的姓名、电话、住址正在网上被“明码标价” ——而这一切,可能只是因为你点过外卖、寄过快递,甚至注册过一个网站。 一、什么是数字足迹管理&#…...
如何避免“过度承诺”导致的验收失败
如何避免“过度承诺”导致的验收失败?关键在于: 评估可行性、设置合理目标、高频沟通反馈、阶段性验收、做好风险管理。其中设置合理目标至关重要,很多团队往往在项目初期为迎合客户或领导而报出“最理想方案”,忽略了资源、技术及…...
MySQL学习笔记集--游标
游标 在MySQL中,游标(Cursor)是一种数据库对象,它允许您逐行处理查询结果集。游标通常与存储过程一起使用,因为它们需要在存储过程或函数中声明和操作。游标的使用涉及几个步骤:声明游标、打开游标、从游标…...
紧跟数字人热潮:123 数字人分身克隆系统源码部署与风口洞察
在当今数字化浪潮中,数字人技术无疑已成为最具活力与潜力的领域之一,正以迅猛之势席卷多个行业,重塑着人们的交互方式与商业运作模式。C 站作为技术交流的前沿阵地,汇聚了众多关注前沿科技的开发者与技术爱好者,今天来…...
QT控件 修改QtTreePropertyBrowser自定义属性编辑器源码,添加第一列标题勾选,按钮,右键菜单事件等功能
头阵子遇到一个需要修改QtTreePropertyBrowser控件的需求,QT开发做这么久了,这个控件倒是第一次用,费了点时间研究,在这里做个简单的总结。 QtTreePropertyBrowser控件 是 Qt 解决方案 (Qt Solutions) 中的一个组件,用…...
Excel 日期值转换问题解析
目录 问题原因 解决方案 方法1:使用 DateTime.FromOADate 转换 方法2:处理可能为字符串的情况 方法3:使用 ExcelDataReader 时的处理 额外提示 当你在 Excel 单元格中看到 2024/12/1,但 C# 读取到 45627 时,这是…...
0. 七小时挑战:自研企业级任务调度器--前言
在软件开发的世界里,有一个亘古不变的问题:“为什么不直接用现成的?”这句话听起来合理、理性、务实,甚至有点老道。毕竟,时间宝贵、预算有限,轮子已经造好了,何必再动手? 但有时候…...
Spring 核心注解深度解析:@Autowired、@Repository 与它们的协作关系
引言 在 Spring 框架中,依赖注入(DI) 是实现松耦合架构的核心机制。Autowired 和 Repository 作为两个高频使用的注解,分别承担着 依赖装配 和 数据访问层标识 的关键职责。本文将深入探讨它们的功能特性、协作模式…...
开源模型应用落地-模型上下文协议(MCP)-从数据孤岛到万物互联(一)
一、前言 当开发者还在为每个AI工具编写臃肿的API适配器时,一场关于「连接」的技术革命已悄然降临。模型上下文协议(MCP)正在用一套全新的交互语法,重新定义人工智能与物理世界的对话方式。MCP协议如同为AI系统装上了“万能接口”…...
基于YOLO的半自动化标注方法:提升铁路视频缺陷检测效率
论文地址:https://arxiv.org/pdf/2504.01010 1. 论文结构概述 本文提出了一种半自动化标注方法,旨在解决铁路缺陷检测中大规模图像/视频数据集标注成本高、耗时长的问题。论文结构清晰,分为以下核心部分: 引言(Introduction) 强调传统手动标注的痛点(耗时、易错、…...
Spring Boot 国际化配置项详解
Spring Boot 国际化配置项详解 1. 核心配置项分类 将配置项分为以下类别,便于快速定位: 1.1 消息源配置(MessageSource 相关) 控制属性文件的加载、编码、缓存等行为。 配置项作用默认值示例说明spring.messages.basename指定属…...
【区块链安全 | 第三十八篇】合约审计之获取私有数据(二)
文章目录 前言漏洞代码代码审计攻击步骤修复建议审计思路 前言 在【区块链安全 | 第三十七篇】合约审计之获取私有数据(一)中,介绍了私有数据、访问私有数据实例、Solidity 中的数据存储方式等知识,本文通过分析具体合约代码进行…...
[ctfshow web入门] web23
前置知识 include:包含一个文件,也可以包含一些其他东西,后续用到再解析 substr:对字符串进行切片,第一个参数是字符串,第二第三个参数出从第a个索引开始切n个,索引从0开始计数。 例如…...
mac 苍穹外卖 后端初始 SkyApplication 报错
报错内容 java: java.lang.NoSuchFieldError: Class com.sun.tools.javac.tree.JCTree$JCImport does not have member field com.sun.tools.javac.tree.JCTree qualid deepseek 解决 打开 File > Project Structure > Project SDK, 选择 JDK17。我没有 JDK17就下载了一…...
CSS中的inline-flex与flex的区别
在CSS中,flex 和 inline-flex 都是用于实现弹性布局(Flexbox)的显示属性,但它们在布局行为上有所不同。 flex 属性会使元素表现为块级弹性容器,这意味着元素会在页面上占据一整行的空间,无论其内部内容的大…...
不用第三方库调用DeepSeek
又双叒叕很久不写博客,今天吐一口老曹。 一、为啥干这个 之前在修改OJ的时候,本着少修改多收益的原则,用Python写了一些DeepSeek的调用,真的很简单,用拉下来OpenAI按照官方文档复制粘贴就可以。接口文档页面ÿ…...
Proximal Policy Optimization (PPO)
2.1 策略梯度方法 策略梯度方法计算策略梯度的估计值并将其插入到随机梯度上升算法中。最常用的梯度估计器的形式如下: g ^ E t [ ∇ θ log π θ ( a t ∣ s t ) A ^ t ] (1) \hat{g} \mathbb{E}_t \left[ \nabla_{\theta} \log \pi_{\theta}(a_t | s_t) \h…...
微信小程序:动态表格实现,表头单元格数据完全从data中获取,宽度自定义,自定义文本框,行勾选,样式效果,横向滚动表格(解决背景色不足的问题)等
一、样式效果 二、代码 1、wxml <view class"line flex flex-center"><view class"none" wx:if"{{info.length 0}}">暂无料号</view><view wx:else class"table-container"><!-- 动态生成表头 -->&…...
Java基础编程练习第38题-除法器
题目:编写一个除法器,输入被除数和除数,并将结果输出。 这道题看似很简单,实则也不难。 就是假如用户输入的类型不同怎么办呢?用户输入int或者double类型应该怎么解决。这里我们就需要用到函数的重载。 代码如下&am…...
