分布式锁

92 阅读1分钟
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分钟该锁没有被删除,则自动过期。