Java后台服務慢優化雜談
你是否遇到過這樣的場景,當我們點擊頁面某個按鈕后,頁面一直loading,要等待好幾分鍾才出結果的畫面,有時直接502或504,作為一個后台開發,看到自己開發的系統是這個樣子,就問你慚愧嗎。
這種問題其實是性能問題,當用戶量少數據少的時候,處理還是很快的,數據量一旦大起來,后台處理時間就會延長,前端大部分直接超時或無限等待直接死掉。
解決數據量大的性能問題,要根據實際業務場景來針對分析。但歸根結底,只有一條最終方案,即減少與數據庫交互次數,尤其是在for循環里面。我們所有慢的場景,按這個方式優化,之前好幾分鍾,十幾分鍾的功能都優化到了3秒內。
我們很多場景如新增,修改,上傳,下載報表,都會有for循環內查詢數據庫的操作,而且一次循環有的與數據庫要交互好次操作,這樣下來如果循環是以用戶列表來處理的,一個租戶下1000個用戶,每個用戶要與數據庫交互幾次,再乘以1000,想想得多耗時。
這種場景的優化很簡單,直接將循環體內與數據庫的交互全部拿到循環外,通過業務邏輯,表結構關系是可以做到的,然后在循環體內只做內存計算,實驗證明,循環體內與數據庫交互的次數越少,耗時越小,可能有人會那你循環體內Java處理也慢啊,非也,Java純內存處理其實一點都不慢。
//聲明for循環處理結果集 List<?> list = new ArrayList<>(); //循環前提前向數據庫查詢要用到的數據 List<?> userList = userMapper.selectList(xxx); for(User user: userList){ //處理單個用戶業務邏輯,避免多次查庫 list.add(xxx); }
上面說的是循環體內查詢數據庫,這里要說的場景就是循環體內插入或修改數據庫記錄,還是1000個用戶循環插入或修改關聯表,與數據庫的交互次數也龐大的,這里的優化也很簡單,將要變更到數據庫對象全部封裝到集體中,循環處理完成后 ,再一次性或分批將集合數據插入數據庫,這也會大大提高修改操作的性能的。
//定義待插入數據庫的集合 List<User> list = new ArrayList(); for(User u : userList){ //處理待插入數據庫的User對象 list.add(u); } //批量(也可以設置分批次)插入數據庫 userMapper.batchInsert(list);
增加線程池這種優化方案就不用多說了吧
threadPoolTaskExecutor.submit(()->{ try { //業務邏輯處理 } catch (Exception e) { } });
在以上業務代碼優化完畢,如果還要再提升性能,那就可以對多查詢的業務加緩存處理,如Redis,Memcached。
緩存也加了,並發性能還上不來,就可以使用異步處理了,將請求放入消息隊列MQ,然后多線程異步消費隊列處理請求。
在生產環境一般都不會是單節點部署,要保障系統穩定性肯定是要多節點部署,這也能在優化代碼代碼的基礎上提高並發性能,尤其是當使用中間件如緩存,MQ,更是要使用多節點集群,才能發揮中間件的作用,如上篇 Redis+Kafka異步提高並發,可以將接口並發能力提升到3000-5000QPS。
作者介紹:小林,狐小E資深開發工程師,專注移動協同辦公平台的SAAS軟件開發以及輕應用開發
最近開發了一款移動辦公軟件 狐小E