多線程實戰(一)
最近做了一個多線程的業務場景,對多線程不熟悉的可以直接拿來使用。
1.業務需求: 使用的微服務架構,在做導出數據的時候,需要對主服務的數據查詢出來,然後對主服務中的數據進行遍歷,根據主服務數據外鍵ID去從服務查相關信息,若是數據量大,或者每條數據遍歷都要調多個從服務查詢關聯數據,就會出現後臺處理業務接口時間過長,1.5W條需要1s左右,數據達到50W條時,就需要大量時間,用戶導出Excel,等待時間過長。頁面容易卡死,用戶體驗度不好。響應也超出excel的請求時間。(聲明:因爲是微服務架構,涉及的分服務和分庫的情況,不能在一個服務中寫sql跨另一個服務的表,所以不能在一個服務中查詢所有數據,需要關聯服務去查)
2.解決方案:
點擊導出的時候,做一個後臺的報表生成,生成的excel報表放到圖片服務器上,增加一個報表記錄表,保存生成報表的記錄和下載路徑提供一個tab切換頁面,做生成的excel報表展示。供用戶下載,excel報表生成的時候,用戶去瀏覽其他頁面。頁面展示如下:
後臺的寫入和寫出使用的是IO操作,爲了提高後臺CUP使用效率,需要使用多線程。
3.前臺代碼:
//綁定按鈕點擊事件
var active = {
exportXls: function () { //導出
var f=$("form").serialize() ;
var fileId;
//先生成info,本次導出excel記錄數據,狀態生成中,防止重複生成
var infoAjax= $.ajax({
url: '/dubbo/ord/ordLanedetailsManager/createInfo.json?'+f+'&flag=0&num='+pageTotalCount ,
isAysn: false,
success: function (res) {
var result=$.parseJSON(res);
// var data=re
if(result.code==0){
if(result.record.code!=0){
layer.msg("數據生成中...");
}else{
var value=result.record.data.id;
fileId=value;
}
}else{
layer.msg("失敗");
}
}
});
//當上一個ajax執行完畢,去發送異步請求進行文件上傳到文件服務器,並把路徑存到已經生成的記錄數據表中,狀態更改爲已完成
$.when(infoAjax).done(function(){
if(!fileId){
return false;
}
//生成數據
var time=1000*60*3;
var dayAjax = $.ajax({
url: '/dubbo/ord/ordLanedetailsManager/exportOutboundedList.json?'+f+'&flag=0&fileId='+fileId+'&num='+pageTotalCount ,
isAysn: true,
timeout: time,
success: function (result) {
//異步處理完畢,不處理結果值
}
});
});
}
}
4.後臺代碼:
4.1 第一個ajax對應的後臺方法
//第一個ajax對應的後臺方法
@Override
@ServiceMapping(trancode = "", caption = "導出", log = false)
public Record createInfo(PubContext pubContext,Map<String, String> file){
Record record= new Record();
//保留兩份
Collection<Condition> conditions= new ArrayList<>();
String userId = PubContextUtils.getUserId(pubContext);
conditions.add(ConditionUtils.getCondition("userId", Condition.EQUALS, userId));
conditions.add(ConditionUtils.getCondition("deleteFlag", Condition.EQUALS, "0"));
conditions.add(ConditionUtils.getCondition("type", Condition.EQUALS, OrdExportRecord.TYPE_BHMC));
Collection<Order> orders = new ArrayList<>();
orders.add(new Order("createTime", false));
List<OrdExportRecord> exportRecords = ordExportRecordManager.getExportRecords(conditions, orders);
int i=0;
for (OrdExportRecord ordExportRecord : exportRecords) {
if(i<2){ //判斷是否有生成的
if(OrdExportRecord.STATUS_SAVING.equals(ordExportRecord.getStatus())){
record.put("code", "205");
record.put("msg", "正在生成數據!!!");
return record;
}
}else{ //把多餘的給刪掉
fileStoreService.deleteFile(ordExportRecord.getFilepath());
ordExportRecord.setDeleteFlag("1");
ordExportRecordManager.save(ordExportRecord);
}
i++;
}
//生成新的數據
OrdExportRecord export= new OrdExportRecord();
export.setCreateTime(DateUtils.formatDate(new Date(), "yyyy-MM-dd HH:mm:ss"));
export.setStatus(OrdExportRecord.STATUS_SAVING);
export.setUserId(PubContextUtils.getUserId(pubContext));
// String filePath = file.get("filePath");
// export.setFilepath(filePath);
export.setFilename(TempData.FILE_NAME);
export.setDeleteFlag("0");
export.setType("0");
// export.set
OrdExportRecord save = ordExportRecordManager.save(export);
logger.info("-------生成數據--"+export);
record.put("code", "0");
record.put("data", save);
return record;
}
4.2 第二個ajax對應的後臺方法, 此時使用多線程處理,防止多用戶同時操作,出現IO阻塞。
4.2.1 先聲明線程類
若要獲取線程執行後的執行結果的話,用FatureTask接口,不需要返回
值的話,使用常見的繼承Thred類或實現Runnable接口就OK
本次異步寫出Excel表格,只要繼承Thred類即可, OrdExportRecordExcutor.java
package com.gsoft.yoreach.ord.executor;
/***
* BHMC導出報表線程
* @author kaifa-08
*
*/
public class OrdExportRecordExcutor extends Thread {
private Log logger = LogFactory.getLog(OrdExportRecordExcutor.class);
private OrdLanedetailsManager ordLanedetailsManager ;
private OrdExportRecordDao ordExportRecordDao;
private PubContext pubContext;
private Pager pager;
private Map<String, Object> map;
public OrdExportRecordExcutor(OrdLanedetailsManager ordLanedetailsManager, OrdExportRecordDao ordExportRecordDao,
PubContext pubContext, Pager pager, Map<String, Object> map) {
this.ordLanedetailsManager =ordLanedetailsManager;
this.ordExportRecordDao =ordExportRecordDao;
this.pubContext =pubContext;
this.pager=pager;
this.map=map;
logger.info("----接收參數"+this.ordLanedetailsManager);
}
@Override
public void run() {
Map<String, Object> mapdata=new HashMap<String, Object>();
try {
logger.info("----主線程開啓");
//1.查詢數據並上傳到圖片服務器
mapdata= ordLanedetailsManager.getDataAndUpload(pubContext, pager, map);
//2.保存記錄,把圖片服務器上的路徑和名字更新到數據記錄表
Record createInfo = this.createInfo(mapdata);
logger.info("----主線程結束"+createInfo);
//
} catch (Exception e) {
e.printStackTrace();
logger.info("----BHMC報表生成失敗--執行報表記錄回退--");
String fileId = MapParamsUtils.getParam(map, "fileId");
OrdExportRecord info = ordExportRecordDao.findOne(fileId);
info.setDeleteFlag("1");
OrdExportRecord save = ordExportRecordDao.save(info);
throw new BusException("BHMC報表生成失敗!");
} finally {
}
}
}
4.2.2 在對應的業務層聲明一個線程池,用來執行線程
private ExecutorService executorService = Executors.newFixedThreadPool(30);
4.2.3 第二個ajax對應的後臺接口
@Override
@ServiceMapping(trancode = "", caption = "導出", log = true)
public void exportOutboundedList
(PubContext pubContext, Pager pager, Map<String, Object> map) {
//1.執行生成報表的線程
executorService.submit(new OrdExportRecordExcutor(this, exportDao, pubContext, pager, map));
}