记一次使用java实现请求排队的实现逻辑

记一次使用java实现请求排队的实现逻辑

业务需求:刚接到的需求是需要导出大数据量的excel表格,一次大概30-100多万不等,然后接口一次只能处理一个请求,如果多个请求要实现请求排队,意思就是你请求的时候如果这个接口在处理别的请求,那么你这个请求要在接口处理完后再处理你这个请求,当然,这也是为了防止高并发导致oom.导出excel表格很简单,用easyExcel就行了,可以自行搜索,关于这个请求排队还是第一次做,所以也想到了很多方案.比如用mq的队列,消费者只能一个一个消费,消费完发ack,然后再消费,这个方案之前也搜索了相关资料,做起来比较复杂就pass了,然后又想到了线程等待,然后处理完后在notify,这样太费性能了,而且容易数据丢失,想来想去其实有时候看着复杂的东西往往很简单,最后决定采取的方案是接口加分布式锁,谁拿锁谁执行,线程在拿锁执行期间别的请求先记录,等数据处理完后在发mq就行消费就可以了,具体实现如下:

第一步,每一个请求先记录

这里也用了reids对用户做了访问次数的限制:
@Override
public void allExport(VehicleCardAllExportDTO vehicleCardAllExportDTO) {

    //根据用户id查询缓存,今日点击量是否大于三次
    String key = BIND_CLICK_COUNT_KEY+":"+vehicleCardAllExportDTO.getOperationPersonId();
    Boolean aBoolean = invokeExceededTimes(key,CLICK_COUNT);
    if (Boolean.FALSE.equals(aBoolean)) {
        throw new BadRequestException(ErrorCode.V_REQUEST_IS_LIMIT);
    }

    //生成压缩文件名,保存mongo,状态处理中
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat(Constants.DATE_FORMAT);
    String format = simpleDateFormat.format(new Date());

    String fileName = Constants.ALL_DATA_NAME_FORMAT+Constants.FILE_NAME_SIGN+format;
    //生成mongoDB导出文件记录
    String fileId = UUID.randomUUID().toString().replace("-", "").toLowerCase();
    VehicleCardAllExportFile vehicleCardAllExportFile = new VehicleCardAllExportFile();
    vehicleCardAllExportFile.setFileId(fileId);
    vehicleCardAllExportFile.setFileName(fileName+ZIP_FORMAT);
    vehicleCardAllExportFile.setOperationPersonId(vehicleCardAllExportDTO.getOperationPersonId());
    vehicleCardAllExportFile.setOperationPersonName(vehicleCardAllExportDTO.getOperationPersonName());
    vehicleCardAllExportFile.setAdmin(vehicleCardAllExportDTO.getAdmin());
    vehicleCardAllExportFile.setExportStatus(Constants.VEHICLE_CARD_FILE_EXECUTE);
    vehicleCardAllExportFile.setCreatedTime(new Date());
    vehicleCardAllExportFile.setUpdatedTime(new Date());
    vehicleCardAllExportFile.setType(ZIP_FORMAT);
    vehicleCardAllExportDTO.setFileId(fileId);
    vehicleCardAllExportDTO.setFileName(fileName);
    vehicleCardAllExportFile.setVehicleCardAllExportDTO(vehicleCardAllExportDTO);
    mongoTemplate.insert(vehicleCardAllExportFile, Constants.MONGO_VEHICLE_CARD_EXPORT_FILE);
    vehicleCardResourcesExport.exportExportCard(vehicleCardAllExportDTO);
}

第二步,开辟异步线程拿锁就行处理:

这里仅展现拿锁的逻辑了,剩下的就是业务逻辑代码,就不展示了:

@Async
public void exportExportCard(VehicleCardAllExportDTO vehicleCardAllExportDTO) {

    RLock allExportLock = null;
    //查询初始化页数
    int pageNum = 1;
    FileOutputStream out = null;
    InputStream fileInputStream = null;
    try {
        //此处加分布式锁   拿到锁处理
        allExportLock = redisson.getLock(Constants.REDIS_ALL_EXPORT_KEY);
        if (!allExportLock.tryLock()) {
            log.info("************全量导出,获取分布式锁失败!任务结束执行end************");
            return;
        }

第三步:业务处理完成,释放分布式锁,发送mq

下面的代码要写在finally代码块里:

finally {
// 关闭流
if (null != out) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != fileInputStream) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
try {
//删除本地临时文件
File file1 = new File(Constants.TEMP_PATH + vehicleCardAllExportDTO.getFileName());
if (file1.exists()) {
FileDeleteUtil.delFolder(Constants.TEMP_PATH + vehicleCardAllExportDTO.getFileName() + “/”);
}
File file = new File(Constants.TEMP_ZIP_PATH + vehicleCardAllExportDTO.getFileName() + ZIP_FORMAT);
if (file.isFile() && file.exists()) {
Files.delete(file.toPath());
}
} catch (Exception e) {
log.info(“删除本地文件失败:{}”, e.getMessage());
}

        if (null != allExportLock && allExportLock.isLocked() && allExportLock.isHeldByCurrentThread()) {
            allExportLock.unlock();
            log.info("全量导出结束-锁已释放------------》");
        }
        //发送mq消息  实现顺序消费
        SendResult sendResult = producer.send(Constants.ROCKET_TOPIC, Constants.VEHICLE_CARD_RESOURCES_ALL_EXPORT, vehicleCardAllExportDTO, null);

    }

第四部,消费mq,实现顺序消费

接收到mq后就可以查询待处理的数据了,也就是等待处理的数据,可以根据时间排序,然后再次调用开辟的异步方法,以实现顺序消费

  • 13
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值