【Easylive】视频删除方法详解:重点分析异步线程池使用

news2025/7/15 4:19:44

【Easylive】项目常见问题解答(自用&持续更新中…) 汇总版

方法整体功能

这个deleteVideo方法是一个综合性的视频删除操作,主要完成以下功能:

  1. 权限验证:检查视频是否存在及用户是否有权限删除
  2. 核心数据删除:删除视频主信息、投稿信息
  3. 经济系统调整:扣除用户发布视频获得的硬币
  4. 搜索索引清理:从Elasticsearch中移除文档
  5. 异步清理关联数据:使用线程池异步删除分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. 异步设计的优缺点分析

优点
  1. 响应速度:主线程快速返回,用户体验好
  2. 资源隔离:IO密集型操作不影响核心业务
  3. 错误隔离:文件删除失败不影响主流程
缺点及风险
  1. 事务不一致

    // 主事务提交后异步任务才执行
    // 若异步任务失败,系统处于不一致状态
    
  2. 错误处理缺失

    // 当前实现没有记录任务执行结果
    // 无法知道异步操作是否成功
    
  3. 资源竞争

    // 固定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();
    }
});

总结

这个删除方法通过线程池实现了:

  1. 核心数据同步删除:保证关键数据立即清除
  2. 资源异步清理:提升响应速度
  3. 物理文件删除:释放存储空间

关键改进方向
• 线程池参数优化
• 完善错误处理和状态跟踪
• 考虑引入事务事件机制
• 增加监控和告警能力

这种设计适合对实时性要求高但允许最终一致性的场景,是典型的"快速响应+后台清理"架构模式。

异步线程池及executorService.execute详解

一、异步线程池基础

1. 线程池核心概念

线程池是一种线程管理机制,它维护着多个线程,避免频繁创建和销毁线程带来的性能开销。在Java中,主要通过ExecutorService接口及其实现类来使用线程池。

2. 线程池关键参数

参数说明示例值
corePoolSize核心线程数10
maximumPoolSize最大线程数50
keepAliveTime空闲线程存活时间60秒
workQueue任务队列new LinkedBlockingQueue(1000)
threadFactory线程创建工厂Executors.defaultThreadFactory()
handler拒绝策略AbortPolicy

3. 线程池工作流程

  1. 提交任务时,优先使用核心线程处理
  2. 核心线程全忙时,任务进入队列
  3. 队列满时,创建新线程(不超过maxPoolSize)
  4. 线程数达最大值且队列满时,触发拒绝策略

二、executorService.execute方法详解

1. 方法签名

void execute(Runnable command)

2. 核心特点

异步执行:立即返回,不阻塞调用线程
无返回值:适用于不需要获取结果的场景
异常处理:任务异常会传递给未捕获异常处理器

3. 执行流程

Caller Executor Queue Worker execute(task) 立即执行 放入队列 队列非空时取出执行 alt [有可用核心线程] [无可用核心线程] 任务完成 Caller Executor Queue Worker

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?

对比项executesubmit
返回值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. 各参数说明

  1. corePoolSize=5:保持5个常驻线程
  2. maxPoolSize=20:突发流量时可扩展到20线程
  3. keepAliveTime=60s:空闲线程60秒后回收
  4. 有界队列(1000):防止资源耗尽
  5. CallerRunsPolicy:队列满时由调用线程执行任务

四、异常处理机制

1. 当前实现的异常处理

try {
    FileUtils.deleteDirectory(...);
} catch (IOException e) {
    log.error("删除文件失败...");
}

• 仅记录日志,无恢复机制
• 异常不会传播到主线程

2. 增强型异常处理方案

方案1:全局异常处理器
executorService = new ThreadPoolExecutor(
    // ...其他参数
    new ThreadPoolExecutor.AbortPolicy() {
        @Override
        public 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;
    }
    
    @Override
    public 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. 为什么适合使用线程池?

  1. 解耦:将清理操作与主业务分离
  2. 提速:主线程快速返回
  3. 可控:通过线程池限制资源使用
  4. 可扩展:方便添加重试等机制

3. 潜在风险及应对

风险应对措施
线程泄漏使用有界队列,合理配置存活时间
任务丢失添加持久化队列或任务记录
资源竞争监控和动态调整线程池参数
异常传播完善任务级别的异常处理

七、总结最佳实践

  1. 选择合适的线程池类型:根据场景选择fixed/cached/custom
  2. 使用有界队列:防止资源耗尽
  3. 配置合理的拒绝策略:如CallerRunsPolicy
  4. 完善异常处理:任务级别和全局级别
  5. 添加监控:实时了解线程池状态
  6. 考虑任务重要性:关键任务建议使用带返回值的submit

在视频删除场景中,通过线程池异步处理清理任务是一种合理的设计,但需要注意:
• 线程池参数的合理配置
• 异常情况的妥善处理
• 重要操作的日志记录
• 系统资源的监控告警

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

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

相关文章

力扣hot100_回溯(2)_python版本

一、39. 组合总和&#xff08;中等&#xff09; 代码&#xff1a; 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…

LPDDR4内存颗粒命名规则全解析:三星、镁光、海力士、南亚、长鑫等厂商型号解码与选型指南

由于之前DDR的系列选型文章有很好的反馈&#xff0c;所以补充LPDDR4低功耗内存的选型和命名规则&#xff0c;总结了目前市面上常用的内存&#xff0c;供硬件工程师及数码爱好者参考。 在智能手机、平板电脑和低功耗设备中&#xff0c;LPDDR4 SDRAM凭借其高带宽、低功耗特性成为…

【杂谈】Godot4.4导出到Android平台(正式导出)

学博而后可约&#xff0c;事历而后知要。 目录 一、准备二、Gradle构建三、配置Java SDK四、配置Android SDK五、配置密钥 一、准备 本文在前文【杂谈】Godot4.4导出到安卓平台&#xff08;调试导出&#xff09;的基础上&#xff0c;进行正式导出。调试导出并不是真正的编译导…

基于AI设计开发出来的业务系统是什么样的?没有菜单?没有表格?

基于AI设计开发出的业务系统仍然会包含菜单、表格等传统UI元素&#xff0c;但AI技术会显著改变它们的实现方式和交互逻辑。以下是具体分析&#xff1a; 一、传统元素的持续存在 功能刚需性 • 菜单承担着系统导航的核心功能&#xff0c;表格则是结构化数据展示的基础载体。根…

数字足迹管理(DFM):你的网络隐身指南

数字足迹管理&#xff08;DFM&#xff09;&#xff1a;你的网络隐身指南 ‌你可能不知道&#xff0c;你的姓名、电话、住址正在网上被“明码标价”‌ ——而这一切&#xff0c;可能只是因为你点过外卖、寄过快递&#xff0c;甚至注册过一个网站。 一、什么是数字足迹管理&#…

如何避免“过度承诺”导致的验收失败

如何避免“过度承诺”导致的验收失败&#xff1f;关键在于&#xff1a; 评估可行性、设置合理目标、高频沟通反馈、阶段性验收、做好风险管理。其中设置合理目标至关重要&#xff0c;很多团队往往在项目初期为迎合客户或领导而报出“最理想方案”&#xff0c;忽略了资源、技术及…

紧跟数字人热潮:123 数字人分身克隆系统源码部署与风口洞察

在当今数字化浪潮中&#xff0c;数字人技术无疑已成为最具活力与潜力的领域之一&#xff0c;正以迅猛之势席卷多个行业&#xff0c;重塑着人们的交互方式与商业运作模式。C 站作为技术交流的前沿阵地&#xff0c;汇聚了众多关注前沿科技的开发者与技术爱好者&#xff0c;今天来…

QT控件 修改QtTreePropertyBrowser自定义属性编辑器源码,添加第一列标题勾选,按钮,右键菜单事件等功能

头阵子遇到一个需要修改QtTreePropertyBrowser控件的需求&#xff0c;QT开发做这么久了&#xff0c;这个控件倒是第一次用&#xff0c;费了点时间研究&#xff0c;在这里做个简单的总结。 QtTreePropertyBrowser控件 是 Qt 解决方案 (Qt Solutions) 中的一个组件&#xff0c;用…

开源模型应用落地-模型上下文协议(MCP)-从数据孤岛到万物互联(一)

一、前言 当开发者还在为每个AI工具编写臃肿的API适配器时&#xff0c;一场关于「连接」的技术革命已悄然降临。模型上下文协议&#xff08;MCP&#xff09;正在用一套全新的交互语法&#xff0c;重新定义人工智能与物理世界的对话方式。MCP协议如同为AI系统装上了“万能接口”…

【区块链安全 | 第三十八篇】合约审计之获取私有数据(二)

文章目录 前言漏洞代码代码审计攻击步骤修复建议审计思路 前言 在【区块链安全 | 第三十七篇】合约审计之获取私有数据&#xff08;一&#xff09;中&#xff0c;介绍了私有数据、访问私有数据实例、Solidity 中的数据存储方式等知识&#xff0c;本文通过分析具体合约代码进行…

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就下载了一…

Proximal Policy Optimization (PPO)

2.1 策略梯度方法 策略梯度方法计算策略梯度的估计值并将其插入到随机梯度上升算法中。最常用的梯度估计器的形式如下&#xff1a; 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"><!-- 动态生成表头 -->&…

python-Leetcode 65.搜索旋转排序数组

题目&#xff1a; 整数数组nums按升序排列&#xff0c;数组中的值互不相同 在传递给函数之前&#xff0c;nums在预先未知的某个小标K上进行了旋转&#xff0c;使数组变为[nums[k], nums[k1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]&#xff0c;小标从0开始计数。…

Django学习记录-1

Django学习记录-1 虽然网上教程都很多&#xff0c;但是感觉自己记录一下才属于自己&#xff0c;之后想找也方面一点&#xff0c;文采不佳看的不爽可绕道。 参考贴 从零开始的Django框架入门到实战教程(内含实战实例) - 01 创建项目与app、加入静态文件、模板语法介绍&#xff…

K8s私有仓库拉取镜像报错解决:x509 certificate signed by unknown authority

前言 在Kubernetes环境中使用自签名证书的私有Harbor镜像仓库时&#xff0c;常会遇到证书验证失败的问题。本文将详细讲解如何解决这个常见的证书问题。 环境信息&#xff1a; Kubernetes版本&#xff1a;1.28.2容器运行时&#xff1a;containerd 1.6.20私有仓库&#xff1a…

LabVIEW 长期项目开发

LabVIEW 凭借其图形化编程的独特优势&#xff0c;在工业自动化、测试测量等领域得到了广泛应用。对于长期运行、持续迭代的 LabVIEW 项目而言&#xff0c;其开发过程涵盖架构设计、代码管理、性能优化等多个关键环节&#xff0c;每个环节都对项目的成功起着至关重要的作用。下面…

蓝桥杯真题——好数、R格式

目录 蓝桥杯2024年第十五届省赛真题-好数 【模拟题】 题目描述 输入格式 输出格式 样例输入 样例输出 提示 代码1&#xff1a;有两个案例过不了&#xff0c;超时 蓝桥杯2024年第十五届省赛真题-R 格式 【vector容器的使用】 题目描述 输入格式 输出格式 样例输入…

Windows版-RabbitMQ自动化部署

一键完成Erlang环境变量配置&#xff08;ERLANG_HOME系统变量&#xff09;‌ 一键完成RabbitMQ环境变量配置&#xff08;RabbitMQ系统变量&#xff09;‌ 实现快速安装部署RabbitMQ PS&#xff1a; 需提前下载安装&#xff1a; - otp_win64_25.0.exe (Erlang) - rabbit…

openEuler24.03 LTS下安装Flink

目录 Flink的安装模式下载Flink安装Local模式前提条件解压安装包启动集群查看进程提交作业文件WordCount持续流WordCount 查看Web UI配置flink-conf.yaml简单使用 关闭集群 Standalone Session模式前提条件Flink集群规划解压安装包配置flink配置flink-conf.yaml配置workers配置…