大資料量查詢優化
問題描述:涉及到大資料量,多迴圈查詢的時候,往往查詢的速度會變慢,影響系統的使用效能。該問題,在測試環境尚不明顯,因為測試環境的資料量畢竟是有限的。
但是,一旦將程式碼更新到線上的真實系統,因為資料量一下子增大,會造成資料查詢的緩慢,所造成的嚴重遲滯,就不能被忽略了。
業務場景:雲端計算系統。底層會將採集過來的宿主機流量資料,進行儲存。後臺管理系統,需要定時計算從底層傳遞過來的宿主機的頻寬的總體實際使用量,並且用實際使用量,除以節點的總頻寬,得到相關的出網、入網的頻寬使用率。
方案分析:
1.在儲存資料的時候,事實計算出資料使用量,後續查詢時,通過sql語句的sum方法直接彙總。
2.開啟多執行緒併發查詢。
一、利用Callable多執行緒,實現查詢優化的例子
原理同一個Callable的簡單例子。核心在於運用多執行緒,建立帶有返回值的查詢。
1.準備查詢引數
2.根據每頁資料量,去判斷應該建立的執行緒的數量。
3.根據資料的總數量,平均分配每個執行緒處理的數量。
4.將查詢返回的Future,儲存在list中(注意,這樣做可以提升查詢速度,先批量儲存結果,再處理的方式,要快於拿到值就處理的方式)
5. latch.await();
6.遍歷List,資料處理+分頁,返回資料值。
public String services(){ List <Future<Map<String,Object>>> list=new ArrayList<Future<Map<String,Object>>>(); int total=0; //1.準備需要傳遞的引數 String condition = dealContent(); List<CompanyEntity> companyList = companyService.list(condition, -1, -1); List<Node> nodeList = new ArrayList<Node>(); int nodeId = getNodeId(); if(nodeId != 0){ Node node = nodeService.getById(nodeId); nodeList.add(node); }else{ nodeList = nodeService.getAll(); } List<JSONObject> comList = new ArrayList<JSONObject>(); for(CompanyEntity company : companyList){ for(Node node : nodeList){ JSONObject jsonObj = new JSONObject(); jsonObj.put("companyName", company.getCompanyName()); jsonObj.put("companyId", company.getCompanyId()); jsonObj.put("companyType", company.getType()); jsonObj.put("nodeName",node.getNode_name()); jsonObj.put("nodeId",node.getNode_id()); jsonObj.put("nodeIp", node.getNode_ip()); comList.add(jsonObj); } } //2.資料分頁處理 int pagesize=Integer.parseInt(super.getPagesize()); int nopage=Integer.parseInt(super.getNowpage()); int ThrearNum=0; if(pagesize>ThreadUtil.CORE_POOL_SIZE){ ThrearNum=ThreadUtil.CORE_POOL_SIZE; }else{ ThrearNum=pagesize; } try { //計算資料總量: int sumDataCount=comList.size(); List<JSONObject> dataList=new ArrayList<JSONObject>(); final CountDownLatch latch = new CountDownLatch(ThrearNum); boolean flag=false; int dataEnd=0; int addData=0; int j=1; for (int i = 0; i < ThrearNum; i++) { List<JSONObject> addList=new ArrayList<JSONObject>(); List<JSONObject> tempList=new ArrayList<JSONObject>(comList.size()); tempList=comList; List<JSONObject> dealList=new ArrayList<JSONObject>(); if(sumDataCount%ThrearNum!=0){ addData=sumDataCount%ThrearNum; flag=true; } //計算每個執行緒要處理的資料總量: int dataCountPerThread=sumDataCount/ThrearNum; //計算當前執行緒資料處理的起始索引: int dataStart=i*dataCountPerThread; //計算當前執行緒資料處理的結束索引: dataEnd=(i+1)*dataCountPerThread; //擷取List,作為當前執行緒所需要處理的全部資料: if(flag){ if(j<=addData){ //多餘的資料,從最後的索引開始平均分配 addList.add(tempList.get(tempList.size()-j)); j++; } dealList=subData(tempList,dataStart, dataEnd); if(addList.size()!=0){ dealList.add(addList.get(0)); addList.remove(0); } }else{ dealList=subData(tempList,dataStart, dataEnd); //如果這裡不使用subData,而是使用subList,則會報錯:java.util.ConcurrentModificationException //dealList=comList.subList(dataStart, dataEnd); } Callable<Map<String,Object>> c1 = new CallableCountThread(condition,dealList,VpcFlag,slbFlag,blockFlag,vpnFlag,latch); Future<Map<String,Object>> f1=pool.submit(c1); list.add(f1); } latch.await(); for(int i=0;i<list.size();i++){ Future<Map<String,Object>> f1=list.get(i); total+=(Integer)f1.get().get("count"); if(null!=f1.get().get("data")){ dataList.addAll((List<JSONObject>)f1.get().get("data")); } } doPage_special(total); //3.查詢分頁開始和結束的索引位 int nowpage2 = (Integer) dataMap.get("nowpage"); int pagesize2 = (Integer) dataMap.get("pagesize"); List<JSONObject> showData=new ArrayList<JSONObject>(); if(dataList.size()==0){ }else{ if(nopage==1){ if(dataList.size()<=pagesize2){ showData=dataList.subList(0,dataList.size()-1); showData.add(dataList.get(dataList.size()-1)); }else{ showData=dataList.subList(0,pagesize2); } }else{ if(dataList.size()<=(nopage)*pagesize2){ showData=dataList.subList((nopage-1)*pagesize2,dataList.size()-1); showData.add(dataList.get(dataList.size()-1)); }else{ showData=dataList.subList((nopage-1)*pagesize2,nopage*pagesize2); } } } generateSuccessListResponse(showData); } catch (InterruptedException | ExecutionException e) { // TODO Auto-generated catch block e.printStackTrace(); } return JSON; }
二、查詢效率
沒有使用duox多執行緒:
查詢時間在16s左右
使用多執行緒查詢:
查詢時間在1.7s左右
查詢速度提升了十倍。
三、心得總結:
1.寫程式碼的時候,不能僅僅只考慮功能的實現。特別是涉及到資料量比較大的時候,任何一次查詢的精簡,帶來的效率提升都是顯著的。
2.多執行緒在具體場景中的使用是非常重要的。在涉及到返回值的時候,就需要使用到Callable介面。
3.任何時候,寫完程式碼,都要自己給自己做code review。回過頭去看看自己寫的程式碼。能不能優化,能不能再優化。