多線程實戰(一)

多線程實戰(一)
最近做了一個多線程的業務場景,對多線程不熟悉的可以直接拿來使用。
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));

    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章