SpringBoot中@SchedulerLock注解实现定时任务中分布式锁的使用

背景

在SpringBoot项目中经常会去写一些定时任务,但是当我们的服务的实例部署多个的情况下,那么每个实例中的定时任务都会执行一遍,这显然不是我们想要的,我们只想让它执行一次。在没有引入像xxl-job之类的分布式任务调度框架的前提下,并且我们也不想对业务代码进行侵入,那么可以选择shedlock进行尝试使用。

  1. 第一步:引入相关依赖
		<dependency>
            <groupId>net.javacrumbs.shedlock</groupId>
            <artifactId>shedlock-spring</artifactId>
            <version>4.14.0</version>
        
<think>好的,我现在需要帮助用户在Spring Boot实现定时任务的设备离线检测。首先,我得理解用户的需求。用户希望通过定时任务来定期检查设备是否离线,当设备离线时触发相应的处理逻辑。根据提供的引用资料,特别是引用[2]提到的环形队列和引用[4]关于定时任务技术选型的内容,我需要结合这些信息来设计解决方案。 首先,用户提到的Spring Boot定时任务,我应该考虑使用Spring自带的@Scheduled注解,因为它简单易用,适合轻量级的定时任务需求。根据引用[4],Spring Task/ScheduledExecutor是推荐的选项之一,所以这应该是首选。 接下来,设备离线检测的逻辑。通常,设备会定期发送心跳包或者状态更新到服务器。如果一段时间内没有收到心跳,就认为设备离线。这里需要维护设备的上次活跃时间,并定时扫描这些时间来判断是否超时。引用[2]提到使用环形队列来优化检测效率,这可能是一种更高效的方法,但用户可能暂时需要一个基础实现,所以可以先用简单的定时扫描方法,后续再优化。 然后,存储设备状态的方式。可以使用内存存储,比如ConcurrentHashMap,或者数据库存储。内存存储适合设备数量不大且不需要持久化的场景,而数据库适合需要持久化和复杂查询的情况。根据用户提供的引用[3],涉及到授权平台,可能需要数据库来存储设备信息,所以这里可能需要结合数据库操作。 另外,引用[2]中提到当设备离线时要发送通知,所以在检测到离线后,需要触发通知逻辑,比如调用消息服务或者记录日志。同时,还需要考虑并发问题,确保在检测过程中设备的状态不会被其他线程修改,导致数据不一致。 步骤分解: 1. 使用@Scheduled创建定时任务,设定执行周期。 2. 维护设备的上次活跃时间,可以在设备发送心跳时更新这个时间。 3. 定时任务中遍历所有设备,检查当前时间与上次活跃时间的差值是否超过阈值。 4. 如果超时,标记设备为离线,并执行相应的处理逻辑,如发送通知。 可能的优化点:使用环形队列来分批次检测设备,减少每次扫描的数量,提高效率,如引用[2]所述。但基础版本可以先实现简单扫描。 现在需要考虑如何存储设备信息。假设使用数据库,设备表可能包含设备ID、最后活跃时间、状态等字段。在定时任务中,可以查询所有设备,检查最后活跃时间是否超过阈值,并更新状态。同时,为了减少数据库压力,可以分批查询或者使用缓存。 代码实现方面,需要编写一个定时任务方法,使用@Scheduled注解,例如每5分钟执行一次。在方法中,调用服务层的方法来检测离线设备。服务层方法会从数据库获取设备列表,遍历判断,更新状态,并触发通知。 异常处理方面,需要考虑定时任务执行时的异常,避免任务中断。可以使用try-catch块捕获异常,并记录日志。 另外,引用[1]提到的Spring Boot Admin的健康检查机制,可能可以用来监控定时任务本身的运行状态,确保检测任务正常执行。虽然用户的问题不直接涉及健康检查,但可以建议用户结合Admin进行监控。 最后,根据用户提供的引用[3],如果需要防止绕过授权,可能需要确保定时任务的检测逻辑与授权机制结合,避免未授权设备被错误处理。不过这可能属于另一个层面的问题,当前问题主要关注离线检测。 总结步骤: 1. 配置Spring Boot定时任务。 2. 维护设备心跳更新时间。 3. 定时扫描并检测离线设备。 4. 处理离线状态,发送通知。</think>在Spring Boot实现定时任务检测设备离线的步骤如下: ### 一、基础实现方式 #### 1. 启用定时任务功能 在Spring Boot启动类添加`@EnableScheduling`注解: ```java @SpringBootApplication @EnableScheduling public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` #### 2. 定义设备状态存储 使用ConcurrentHashMap记录设备最后活跃时间: ```java @Component public class DeviceStatusHolder { private final ConcurrentHashMap<String, Long> lastActiveMap = new ConcurrentHashMap<>(); // 更新设备心跳 public void updateHeartbeat(String deviceId) { lastActiveMap.put(deviceId, System.currentTimeMillis()); } public Map<String, Long> getAllDevices() { return new HashMap<>(lastActiveMap); } } ``` #### 3. 实现定时检测任务 ```java @Component @RequiredArgsConstructor public class DeviceOfflineChecker { private final DeviceStatusHolder statusHolder; private static final long OFFLINE_THRESHOLD = 300_000; // 5分钟 @Scheduled(fixedRate = 60_000) // 每分钟执行一次 public void checkDevices() { Map<String, Long> devices = statusHolder.getAllDevices(); long currentTime = System.currentTimeMillis(); devices.forEach((deviceId, lastActive) -> { if (currentTime - lastActive > OFFLINE_THRESHOLD) { handleOffline(deviceId); } }); } private void handleOffline(String deviceId) { // 实现离线处理逻辑 System.out.println("设备离线告警: " + deviceId); // 可集成消息通知组件[^1][^2] } } ``` ### 二、高级优化方案 #### 1. 环形队列检测法(时间轮算法) ```java @Component public class RingBufferDetector { private final List<Set<String>> ringBuffer = new ArrayList<>(60); private int currentSlot = 0; public RingBufferDetector() { for (int i = 0; i < 60; i++) { ringBuffer.add(new ConcurrentHashSet<>()); } } @Scheduled(fixedRate = 60_000) public void rotate() { Set<String> expiredDevices = ringBuffer.get(currentSlot); handleOffline(expiredDevices); currentSlot = (currentSlot + 1) % 60; } public void updatePosition(String deviceId) { int targetSlot = (currentSlot + 1) % 60; ringBuffer.get(targetSlot).add(deviceId); } } ``` 该方法通过环形队列分时段管理设备状态,时间复杂度为$O(1)$ #### 2. 数据库方案 ```sql CREATE TABLE device_status ( id VARCHAR(64) PRIMARY KEY, last_heartbeat DATETIME, status INT DEFAULT 0 ); ``` ```java @Scheduled(cron = "0 */5 * * * ?") public void dbCheckTask() { LocalDateTime threshold = LocalDateTime.now().minusMinutes(5); List<Device> offlineDevices = deviceRepository.findByLastHeartbeatBefore(threshold); offlineDevices.forEach(device -> { device.setStatus(OFFLINE); notificationService.sendAlert(device); }); deviceRepository.saveAll(offlineDevices); } ``` ### 三、关键配置建议 1. **线程池配置**:在`application.properties`中配置任务线程池 ```properties spring.task.scheduling.pool.size=5 spring.task.scheduling.thread-name-prefix=dev-checker- ``` 2. **分布式锁**(集群环境需添加): ```java @Scheduled(fixedDelay = 30000) @SchedulerLock(name = "deviceCheck") public void distributedCheck() { // 使用Redis或数据库锁实现 } ``` ### 四、注意事项 1. 设备心跳更新需要与业务接口集成: ```java @RestController @RequiredArgsConstructor public class DeviceController { private final DeviceStatusHolder statusHolder; @PostMapping("/heartbeat") public void heartbeat(@RequestParam String deviceId) { statusHolder.updateHeartbeat(deviceId); } } ``` 2. 建议结合Spring Boot Admin进行任务监控[^1] 3. 生产环境推荐使用XXL-Job等分布式任务调度平台[^4]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值