【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
在視頻刪除場景中,通過線程池異步處理清理任務是一種合理的設計,但需要注意:
? 線程池參數的合理配置
? 異常情況的妥善處理
? 重要操作的日志記錄
? 系統資源的監控告警