package com.wafersystems.virsical.iotpkg.job;
import com.alibaba.fastjson.JSON;
import com.wafersystems.virsical.common.core.constant.enums.MsgActionEnum;
import com.wafersystems.virsical.common.core.constant.enums.MsgTypeEnum;
import com.wafersystems.virsical.common.core.dto.MessageDTO;
import com.wafersystems.virsical.common.core.rocketmq.MessageManager;
import com.wafersystems.virsical.iotpkg.dto.AlarmDataStructure;
import com.wafersystems.virsical.iotpkg.entity.IotTsKvLatest;
import com.wafersystems.virsical.iotpkg.entity.SpaceDevice;
import com.wafersystems.virsical.iotpkg.global.VapCommonConstants;
import com.wafersystems.virsical.iotpkg.global.VapMqConstants;
import com.wafersystems.virsical.iotpkg.mapper.IotTsKvLatestMapper;
import com.wafersystems.virsical.iotpkg.mapper.SpaceDeviceMapper;
import jdk.security.jarsigner.JarSigner;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Objects;
@Component
@Slf4j
public class DeviceStatusChecker {
@Autowired
private IotTsKvLatestMapper iotTsKvLatestMapper;
@Autowired
private SpaceDeviceMapper spaceDeviceMapper;
@Autowired
private MessageManager messageManager;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Value("${device.disconnectTime}")
private long disconnectTime;
@Scheduled(fixedRateString = "${device.checkInterval}")
public void checkDeviceStatus() {
ValueOperations<String, String> valueOps = redisTemplate.opsForValue();
// 尝试获取分布式锁
Boolean locked = valueOps.setIfAbsent(VapCommonConstants.VAP_DEVICE_STATUS, "locked");
try {
if (locked != null && locked) {
// 成功获取锁,执行定时任务代码
SpaceDevice spaceDevice = new SpaceDevice();
spaceDevice.setTypeCode(VapCommonConstants.MEETING_BOARD_TYPE_CODE);
List<SpaceDevice> spaceDevices = spaceDeviceMapper.selectListByEntity(spaceDevice);
if (!CollectionUtils.isEmpty(spaceDevices)) {
spaceDevices.forEach(
spaceDevice1 -> {
IotTsKvLatest iotTsKvLatest = iotTsKvLatestMapper.selectLastData(spaceDevice1.getDeviceNumber());
if (Objects.nonNull(iotTsKvLatest)) {
AlarmDataStructure alarmDataStructure = new AlarmDataStructure();
long telemetryLastTime = System.currentTimeMillis() - disconnectTime;
alarmDataStructure.setDeviceId(spaceDevice1.getDeviceNumber());
alarmDataStructure.setDeviceType(spaceDevice1.getTypeCode());
if (iotTsKvLatest.getTs() < telemetryLastTime && VapCommonConstants.DEVICE_IS_ACTIVE.equals(spaceDevice1.getStatus())) {
//exceed offline time,device offline
spaceDeviceMapper.updateStatusByDeviceNumber(spaceDevice1.getDeviceNumber(), VapCommonConstants.DEVICE_IS_INACTIVE);
alarmDataStructure.setActive("false");
MessageDTO messageDTO = new MessageDTO(MsgTypeEnum.ONE.name(), MsgActionEnum.ADD.name(),spaceDevice1);
messageManager.sendFanout(VapMqConstants.TOPIC_DEVICE_ALARM,messageDTO);
}
if(iotTsKvLatest.getTs() > telemetryLastTime && VapCommonConstants.DEVICE_IS_INACTIVE == (spaceDevice1.getStatus())){
spaceDeviceMapper.updateStatusByDeviceNumber(spaceDevice1.getDeviceNumber(), VapCommonConstants.DEVICE_IS_ACTIVE);
alarmDataStructure.setActive("true");
MessageDTO messageDTO = new MessageDTO(MsgTypeEnum.ONE.name(), MsgActionEnum.ADD.name(),alarmDataStructure);
messageManager.sendFanout(VapMqConstants.TOPIC_DEVICE_ALARM,messageDTO);
log.info("send meeting board online status to meeting:{}", JSON.toJSON(messageManager));
}
}
}
);
}
} else {
// 未获取到锁,说明其他实例正在执行任务
log.info("Other instances are executing and the current execution is canceled");
}
} finally {
// 释放锁
if (locked != null && locked) {
redisTemplate.delete(VapCommonConstants.VAP_DEVICE_STATUS);
}
}
}
}
valueOps.setIfAbsent(VapCommonConstants.VAP_DEVICE_STATUS, "locked"); 该方法作用:redis中没有该键值对,则写入,并且返回true。 集群服务中,定时任务执行时,某一个服务第一个执行此方法,写入键值对到redis中,并返回true,判断执行结果是返回为true,执行下面的逻辑。 如果返回false,则表明redis中已经有该键值对存在,其他服务实例已经执行了该定时任务,则其他实例不执行。
存在问题: 在k8s中构建多个实例,一次环境挂掉了,正好是某个服务执行了写入redis键值对的操作,没有来的及执行finally来删除键值对,环境就挂掉了,导致,后面的定时任务都不在执行。
解决办法: 加一个过期时间,因为定时任务每分钟执行一次,redis锁加一个过期时间,如果2分钟该锁没有被删除,则自动过期。