springcloud的重试机制(retry)有很大的用处,譬如在请求某个服务时候请求失败了(请求超时、连接超时、连接服务故障等),它会重新尝试发出请求,已达到成功的目的。
但是重试机制也给我带来了很大的麻烦。这里有一个定时任务,每天的凌晨10分从MongoDB统计出前一天里所有的目标数量(数据量很大),然后再将统计出来的数据插入到MongoDB里以供后期查阅。
业务指引
定时任务系统发出任务指令-------->到系统服务执行业务
这个时候问题出现了,每次插入的数据都是理应插入数据时的两倍,也就是重复插入了。起初并不知道问题的所在,真是绞尽脑汁也没有想到是springcloud的重试机制导致的。
解决这个问题的方法
第一种解决方案:(先不解决MongoDB里的双份数据)因为统计出来的数据在MongoDB里是double份,页面上展示出来两份需要去重。这里我就用到了hashset存放不重复的元素的特性、重写hashcode()、equals()方法。(问题解决了、但是并不美滋滋)
@Override
public boolean equals(Object arg0) {
CountAimAmountVo count = (CountAimAmountVo) arg0;
return cameraID.equals(count.cameraID);
}
@Override
public int hashCode() {
Integer i = cameraID;
return i.hashCode();
}
第二种解决方案:(解决MongoDB里的重复数据,就用不到第一种解决方法了)起初一直以为是MongoDB的配置等等问题导致的。反正花了很长的时间。
刚开始我们并没有注意schedule项目的日志,因为它只是发出一个定时任务的工作,最后看到这样的日志明白了是因为请求超时的原因。然后就做了如下配置(十分钟的时间不用重试)
ribbon:
ReadTimeout: 600000
ConnectTimeout: 600000
因为一共有近2000路的设备,需要8000次的查询(不完全统计,一共花了一个多小时进行查询)、将MongoDB的查询改为分组查询,一共就查询4次(face、vehicle、pedestrian、nonMotor),方法有了,开始敲代码喽!
MongoDB分组查询语句如下:
<!--按照cameraID分组查询每个监控点的目标数-->
db.getCollection("pedestrian").aggregate([{"$match":{"timeStamp":
{"$gte":new Date("2020-03-17"),"$lte":new Date("2020-03-18")}}},{$group : {_id : "$cameraID", count : {$sum : 1}}}])
Java代码mongoTemplate分组查询代码如下:
Aggregation agg = Aggregation.newAggregation(
//相当于where条件
Aggregation.match(Criteria.where("timeStamp").lt(endDay).gte(startDay)),
//相当于{$group : {_id : "$cameraID", count : {$sum : 1}}}
Aggregation.group("cameraID").count().as("count")
);
//得到人体的目标统计数(人脸、机动车、非机动车类似)
AggregationResults<JSONObject> aggPedestrian = mongoTemplate.aggregate(agg, Pedestrian.class, JSONObject.class);
List<JSONObject> pedestrianResults = aggPedestrian.getMappedResults();//这边选择用JSONObject接收数据
代码完成继续测试,还是插入两份数据,但是用时确实少了很多,大约三十分钟。分组查询效率是高了,但是问题还是没有解决啊!NND
第三种解决方案:采用异步调用,schedule系统直接定时发送执行不用等它返回是不是成功了(管它执行的怎么样,反正执行指令我发出了,你那边爱咋地,咋地)
system这边springboot启动类加个@EnableAsync注解,方法名这边加个@Async注解定义一个线程任务
搞上去测试一下,完美解决。schedule项目不在等待了,也就不需要重试了,不用重试了,也就表示不会再一次发送请求了,然后MongoDB里的数据就是一份了,美滋滋~~~~~
如下是定时任务的代码:
/**
* 统计监控点目标数服务
*
* @author Created by jovin .
* @date Created on 12:01 2020/3/10.
*/
@Service("CountAimAmountTaskExecutor")
public class CountAimAmountTaskExecutor {
private static final Logger logger = LoggerFactory.getLogger(CountAimAmountTaskExecutor.class);
@Autowired
private TaskClient taskClient;
//每天凌晨00:10:00自动查询前一天的数据,并存放到MongoDB里
@Scheduled(cron = "0 10 0 * * ?")
public void executeAimAmount() {
logger.info("开始统计监控点目标数*********");
taskClient.executeCount();
logger.info("结束统计监控点目标数*********");
}
}
如下是统计数据的代码:
/**
* 凌晨12点统计监控点目标数
*/
@ApiOperation(value = "定时统计监控点目标数")
@GetMapping(value = "/executeCount")
@Async
public void executeCount(){
String nowDate = DateUtil.getNowDate(DateUtil.DATE_SMALL);
String time = nowDate + " 00:00:00";
Date endDay = DateUtil.parse(time, DateUtil.DATE_FULL);
Date startDay = DateUtil.addDays(endDay, -1);
//得到所有可用的设备
long beginTime = System.currentTimeMillis();
long beginTime1 = System.currentTimeMillis();
List<Camera> cameraList = cameraService.getCameraList();
long endTime = System.currentTimeMillis();
logger.info(" 查找所有可用设备时长===>" + (endTime - beginTime));
logger.info("================可用的设备是:" + cameraList.size());
CountAimAmount countAimAmount = new CountAimAmount();
Aggregation agg = Aggregation.newAggregation(
Aggregation.match(Criteria.where("timeStamp").lt(endDay).gte(startDay)),
Aggregation.group("cameraID").count().as("count")
);
beginTime = System.currentTimeMillis();
//得到人体的目标统计数
AggregationResults<JSONObject> aggPedestrian = mongoTemplate.aggregate(agg, Pedestrian.class, JSONObject.class);
List<JSONObject> pedestrianResults = aggPedestrian.getMappedResults();
Map<Integer, Integer> pedestrianMap = new HashMap<>();
for (JSONObject jsonObject : pedestrianResults) {
pedestrianMap.put(jsonObject.getInteger("_id"),jsonObject.getInteger("count"));
}
//得到人脸的
AggregationResults<JSONObject> aggFace = mongoTemplate.aggregate(agg, Face.class, JSONObject.class);
List<JSONObject> faceResults = aggFace.getMappedResults();
Map<Integer, Integer> faceMap = new HashMap<>();
for (JSONObject jsonObject : faceResults) {
faceMap.put(jsonObject.getInteger("_id"), jsonObject.getInteger("count"));
}
//得到机动车的
AggregationResults<JSONObject> aggVehicle = mongoTemplate.aggregate(agg, Vehicle.class, JSONObject.class);
List<JSONObject> vehicleResults = aggVehicle.getMappedResults();
Map<Integer, Integer> vehicleMap = new HashMap<>();
for (JSONObject jsonObject : vehicleResults) {
vehicleMap.put(jsonObject.getInteger("_id"), jsonObject.getInteger("count"));
}
//得到非机动车的
AggregationResults<JSONObject> aggNonMotor = mongoTemplate.aggregate(agg, NonMotor.class, JSONObject.class);
List<JSONObject> nonMotorResults = aggNonMotor.getMappedResults();
Map<Integer, Integer> nonMotorMap = new HashMap<>();
for (JSONObject jsonObject : nonMotorResults) {
nonMotorMap.put(jsonObject.getInteger("_id"), jsonObject.getInteger("count"));
}
endTime = System.currentTimeMillis();
logger.info(" 查找所有设备目标数时长===>" + (endTime - beginTime));
for (Camera camera : cameraList) {
Integer cameraId = camera.getIntid();
Task task = taskService.getTaskById(camera.getId());
countAimAmount.setCameraID(cameraId);
//设备名称可能会变化的
countAimAmount.setCameraName(camera.getName());
countAimAmount.setDeviceCode(camera.getDeviceCode());
countAimAmount.setCountDay(Integer.parseInt(DateUtil.format2String(startDay, DateUtil.DATE_DIGIT_SMALL)));
countAimAmount.setStatus(task.getStatus());
if(pedestrianMap.containsKey(cameraId))
countAimAmount.setPedestrianCount(new Long (pedestrianMap.get(cameraId)));
else
countAimAmount.setPedestrianCount(0L);
if(faceMap.containsKey(cameraId))
countAimAmount.setFaceCount(new Long (faceMap.get(cameraId)));
else
countAimAmount.setFaceCount(0L);
if(vehicleMap.containsKey(cameraId))
countAimAmount.setVehicleCount(new Long (vehicleMap.get(cameraId)));
else
countAimAmount.setVehicleCount(0L);
if(nonMotorMap.containsKey(cameraId))
countAimAmount.setNonMotorCount(new Long (nonMotorMap.get(cameraId)));
else
countAimAmount.setNonMotorCount(0L);
if (StatusEnum.EXCEPTION.match(task.getStatus())) {
countAimAmount.setStatusDesc(task.getFailDesc());
}
mongoTemplate.insert(countAimAmount);
}
endTime = System.currentTimeMillis();
logger.info(" ============查询和插入一共需要时长===>" + (endTime - beginTime1));
}