多线程实战(一)
最近做了一个多线程的业务场景,对多线程不熟悉的可以直接拿来使用。
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));

    }
Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐