怎么做視頻解析的網(wǎng)站如何設(shè)置友情鏈接
文章目錄
- 版權(quán)聲明
- 修復問題
- 內(nèi)存溢出問題分類
- 分頁查詢文章接口的內(nèi)存溢出
- 問題背景
- 解決思路
- 問題根源
- 解決思路
- Mybatis導致的內(nèi)存溢出
- 問題背景
- 問題根源
- 解決思路
- 導出大文件內(nèi)存溢出
- 問題背景
- 問題根源
- 解決思路
- ThreadLocal占用大量內(nèi)存
- 問題背景
- 問題根源
- 解決思路
- 文章內(nèi)容審核接口的內(nèi)存問題
- 問題背景
- 設(shè)計1:Async異步審核
- 存在問題
- 設(shè)計2:生產(chǎn)者消費者模式
- 存在問題
- 設(shè)計3:Mq消息隊列模式
- 問題根源和解決思路
版權(quán)聲明
- 本博客的內(nèi)容基于我個人學習黑馬程序員課程的學習筆記整理而成。我特此聲明,所有版權(quán)屬于黑馬程序員或相關(guān)權(quán)利人所有。本博客的目的僅為個人學習和交流之用,并非商業(yè)用途。
- 我在整理學習筆記的過程中盡力確保準確性,但無法保證內(nèi)容的完整性和時效性。本博客的內(nèi)容可能會隨著時間的推移而過時或需要更新。
- 若您是黑馬程序員或相關(guān)權(quán)利人,如有任何侵犯版權(quán)的地方,請您及時聯(lián)系我,我將立即予以刪除或進行必要的修改。
- 對于其他讀者,請在閱讀本博客內(nèi)容時保持遵守相關(guān)法律法規(guī)和道德準則,謹慎參考,并自行承擔因此產(chǎn)生的風險和責任。
- 本博客中的部分觀點和意見僅代表我個人,不代表黑馬程序員的立場。
修復問題
內(nèi)存溢出問題分類
- 修復內(nèi)存溢出問題的要具體問題具體分析,問題總共可以分成三類
- 代碼中的內(nèi)存泄漏
- 解決方案:完善代碼
- 并發(fā)引起內(nèi)存溢出
- 參數(shù)不當 由于參數(shù)設(shè)置不當,比如堆內(nèi)存設(shè)置過小,導致并發(fā)量增加之后超過堆內(nèi)存的上限。
- 解決方案:調(diào)整參數(shù),下一章中詳細介紹
- 并發(fā)引起內(nèi)存溢出 – 設(shè)計不當
- 系統(tǒng)的方案設(shè)計不當,比如:從數(shù)據(jù)庫獲取超大數(shù)據(jù)量的數(shù)據(jù)、線程池設(shè)計不當、生產(chǎn)者-消費者模型,消費者消費性能問題
- 解決方案:優(yōu)化設(shè)計方案
分頁查詢文章接口的內(nèi)存溢出
問題背景
- 背景:小李負責的新聞資訊類項目采用了微服務(wù)架構(gòu),其中有一個文章微服務(wù),這個微服務(wù)在業(yè)務(wù)高峰期出現(xiàn)內(nèi)存溢出的現(xiàn)象
解決思路
- 服務(wù)出現(xiàn)OOM內(nèi)存溢出時,生成內(nèi)存快照
- 使用MAT分析內(nèi)存快照,找到內(nèi)存溢出的對象
- 嘗試在開發(fā)環(huán)境中重現(xiàn)問題,分析代碼中問題產(chǎn)生的原因
- 修改代碼
- 測試并驗證結(jié)果
- MAT使用技巧:從線程對象入手,找到當前的處理器方法,再右鍵選擇處理器方法的outgoing references,即可快速找到當前線程執(zhí)行的方法。
問題根源
- 文章微服務(wù)中的分頁接口沒有限制最大單次訪問條數(shù),并且單個文章對象占用的內(nèi)存量較大,在業(yè)務(wù)高峰期并發(fā)量較大時這部分從數(shù)據(jù)庫獲取到內(nèi)存之后會占用大量的內(nèi)存空間。
解決思路
- 與產(chǎn)品設(shè)計人員溝通,限制最大的單次訪問條數(shù)
- 分頁接口如果只是為展示文章列表,不需要獲取文章內(nèi)容,可以大大減少對象的大小
- 在高峰期對微服務(wù)進行限流保護
Mybatis導致的內(nèi)存溢出
問題背景
- 小李負責的文章微服務(wù)進行了升級,新增加了一個判斷id是否存在的接口,第二天業(yè)務(wù)高峰期再次出現(xiàn)了內(nèi)存溢出,小李覺得應該和新增加的接口有關(guān)系
- 堆內(nèi)存快照情況如下
問題根源
- Mybatis在使用foreach進行sql拼接時,會在內(nèi)存中創(chuàng)建對象,如果foreach處理的數(shù)組或者集合元素個數(shù)過多,會占用大量的內(nèi)存空間
解決思路
- 限制參數(shù)中最大的id個數(shù)
- 將id緩存到redis或者內(nèi)存緩存中,通過緩存進行校驗
導出大文件內(nèi)存溢出
問題背景
- 小李負責的一個管理系統(tǒng),使用的是k8s將管理系統(tǒng)部署到容器中,這個管理系統(tǒng)支持幾十萬條數(shù)據(jù)的excel文件導出。他發(fā)現(xiàn)系統(tǒng)在運行時如果有幾十個人同時進行大數(shù)據(jù)量的導出,會出現(xiàn)內(nèi)存溢出。
問題根源
- Excel文件導出如果使用POI的XSSFWorkbook,在大數(shù)據(jù)量(幾十萬)的情況下會占用大量的內(nèi)存。
解決思路
-
使用poi的SXSSFWorkbook(不推薦)
@GetMapping("/export")public void export(int size, String path) throws IOException {// 1 、創(chuàng)建工作薄Workbook workbook = new XSSFWorkbook();// 2、在工作薄中創(chuàng)建sheetSheet sheet = workbook.createSheet("測試");for (int i = 0; i < size; i++) {// 3、在sheet中創(chuàng)建行Row row0 = sheet.createRow(i);// 4、創(chuàng)建單元格并存入數(shù)據(jù)row0.createCell(0).setCellValue(RandomStringUtils.randomAlphabetic(1000));}// 將文件輸出到指定文件FileOutputStream fileOutputStream = null;try {fileOutputStream = new FileOutputStream(path + RandomStringUtils.randomAlphabetic(10) + ".xlsx");workbook.write(fileOutputStream);} catch (Exception e) {e.printStackTrace();} finally {if (fileOutputStream != null) {fileOutputStream.close();}if (workbook != null) {workbook.close();}}}
-
hutool提供的BigExcelWriter減少內(nèi)存開銷(推薦)
//http://www.hutool.cn/docs/#/poi/Excel%E5%A4%A7%E6%95%B0%E6%8D%AE%E7%94%9F%E6%88%90-BigExcelWriter@GetMapping("/export_hutool")public void export_hutool(int size, String path) throws IOException {List<List<?>> rows = new ArrayList<>();for (int i = 0; i < size; i++) {rows.add( CollUtil.newArrayList(RandomStringUtils.randomAlphabetic(1000)));}BigExcelWriter writer= ExcelUtil.getBigWriter(path + RandomStringUtils.randomAlphabetic(10) + ".xlsx");// 一次性寫出內(nèi)容,使用默認樣式writer.write(rows);// 關(guān)閉writer,釋放內(nèi)存writer.close();}
-
使用阿里巴巴easy excel,對內(nèi)存進行大量的優(yōu)化(推薦)
//https://easyexcel.opensource.alibaba.com/docs/current/quickstart/write#%E9%87%8D%E5%A4%8D%E5%A4%9A%E6%AC%A1%E5%86%99%E5%85%A5%E5%86%99%E5%88%B0%E5%8D%95%E4%B8%AA%E6%88%96%E8%80%85%E5%A4%9A%E4%B8%AAsheet@GetMapping("/export_easyexcel")public void export_easyexcel(int size, String path,int batch) throws IOException {// 方法1: 如果寫到同一個sheetString fileName = path + RandomStringUtils.randomAlphabetic(10) + ".xlsx";// 這里注意 如果同一個sheet只要創(chuàng)建一次WriteSheet writeSheet = EasyExcel.writerSheet("測試").build();// 這里 需要指定寫用哪個class去寫try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) {// 分100次寫入for (int i = 0; i < batch; i++) {// 分頁去數(shù)據(jù)庫查詢數(shù)據(jù) 這里可以去數(shù)據(jù)庫查詢每一頁的數(shù)據(jù)List<DemoData> datas = new ArrayList<>();for (int j = 0; j < size / batch; j++) {DemoData demoData = new DemoData();demoData.setString(RandomStringUtils.randomAlphabetic(1000));datas.add(demoData);}excelWriter.write(datas, writeSheet);//寫入之后datas數(shù)據(jù)就可以釋放了}}}
ThreadLocal占用大量內(nèi)存
問題背景
- 小李負責了一個微服務(wù),但是他發(fā)現(xiàn)系統(tǒng)在沒有任何用戶使用時,也占用了大量的內(nèi)存。導致可以使用的內(nèi)存大大減少
問題根源
- 很多微服務(wù)會選擇在攔截器preHandle方法中去解析請求頭中的數(shù)據(jù),并放入一些數(shù)據(jù)到ThreadLocal中方便后續(xù)使用。
解決思路
- 在攔截器的afterCompletion方法中,必須要將ThreadLocal中的數(shù)據(jù)清理掉。
import com.itheima.jvmoptimize.practice.demo.common.UserDataContextHolder; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;/*** 攔截器的實現(xiàn),模擬放入數(shù)據(jù)到threadlocal中*/ public class UserInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {UserDataContextHolder.userData.set(new UserDataContextHolder.UserData());return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserDataContextHolder.userData.remove();} }
文章內(nèi)容審核接口的內(nèi)存問題
問題背景
- 文章微服務(wù)中提供了文章審核接口,會調(diào)用阿里云的內(nèi)容安全接口進行文章中文字和圖片的審核,在自測過程中出現(xiàn)內(nèi)存占用較大的問題
設(shè)計1:Async異步審核
- 使用SpringBoot中的@Async注解進行異步的審核
存在問題
- 線程池參數(shù)設(shè)置不當,會導致大量線程的創(chuàng)建或者隊列中保存大量的數(shù)據(jù)。
- 任務(wù)沒有持久化,一旦走線程池的拒絕策略或者服務(wù)宕機、服務(wù)器掉電等情況很有可能會丟失任務(wù)
設(shè)計2:生產(chǎn)者消費者模式
- 使用生產(chǎn)者和消費者模式進行處理,隊列數(shù)據(jù)可以實現(xiàn)持久化到數(shù)據(jù)庫。
- 保存文章服務(wù)層實現(xiàn)代碼
@Override public void saveArticle(ArticleDto article) {BUFFER_QUEUE.add(article);int size = BUFFER_QUEUE.size();if( size > 0 && size % 10000 == 0){System.out.println(size);} }
- 線程池配置代碼
@Configuration @EnableAsync public class ThreadPoolTaskConfig {public static final BlockingQueue<ArticleDto> BUFFER_QUEUE = new LinkedBlockingQueue<>(2000);private static final int corePoolSize = 50; // 核心線程數(shù)(默認線程數(shù))private static final int maxPoolSize = 100; // 最大線程數(shù)private static final int keepAliveTime = 10; // 允許線程空閑時間(單位:默認為秒)private static final int queueCapacity = 200; // 緩沖隊列數(shù)private static final String threadNamePrefix = "Async-Service-"; // 線程池名前綴@Bean("taskExecutor")public ThreadPoolTaskExecutor getAsyncExecutor(){ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(corePoolSize);executor.setMaxPoolSize(Integer.MAX_VALUE);//executor.setMaxPoolSize(maxPoolSize);executor.setQueueCapacity(queueCapacity);executor.setKeepAliveSeconds(keepAliveTime);executor.setThreadNamePrefix(threadNamePrefix);// 線程池對拒絕任務(wù)的處理策略executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());// 初始化executor.initialize();return executor;} }
- 審核文章代碼
@Component public class ArticleSaveTask {@Autowired@Qualifier("taskExecutor")private ThreadPoolTaskExecutor threadPoolTaskExecutor;@PostConstructpublic void pullArticleTask(){for (int i = 0; i < 50; i++) {threadPoolTaskExecutor.submit((Runnable) () -> {while (true){try {ArticleDto data = BUFFER_QUEUE.take();/*** 獲取到隊列中的數(shù)據(jù)之后,調(diào)用第三方接口審核數(shù)據(jù),但是此時網(wǎng)絡(luò)出現(xiàn)問題,* 第三方接口長時間沒有響應,此處使用休眠來模式30秒*/Thread.sleep(30 * 1000);} catch (InterruptedException e) {e.printStackTrace();}}});}} }
存在問題
- 隊列參數(shù)設(shè)置不正確,會保存大量的數(shù)據(jù)。
- 實現(xiàn)復雜,需要自行實現(xiàn)持久化的機制,否則數(shù)據(jù)會丟失
設(shè)計3:Mq消息隊列模式
- 使用mq消息隊列進行處理,由mq來保存文章的數(shù)據(jù)。發(fā)送消息的服務(wù)和拉取消息的服務(wù)可以是同一個,也可以不是同一個
- 生產(chǎn)者代碼
@Autowired private RabbitTemplate rabbitTemplate; @Autowired private ObjectMapper objectMapper;@PostMapping("/demo3/{id}") public void article3(@PathVariable("id") long id, @RequestBody ArticleDto article) throws JsonProcessingException {article.setId(id);rabbitTemplate.convertAndSend("jvm-test",null, objectMapper.writeValueAsString(article)); }
- 消費者代碼
@Component public class SpringRabbitListener {@RabbitListener(queues = "queue1",concurrency = "10")public void listenSimpleQueue(String msg) throws InterruptedException {System.out.println(msg);Thread.sleep(30 * 1000);} }
問題根源和解決思路
- 在項目中如果要使用異步進行業(yè)務(wù)處理,或者實現(xiàn)生產(chǎn)者 – 消費者的模型,如果在Java代碼中實現(xiàn),會占用大量的內(nèi)存去保存中間數(shù)據(jù)。
- 盡量使用Mq消息隊列,可以很好地將中間數(shù)據(jù)單獨進行保存,不會占用Java的內(nèi)存。同時也可以將生產(chǎn)者和消費者拆分成不同的微服務(wù)