多实例下定时任务执行多次chat 和实践

这篇具有很好参考价值的文章主要介绍了多实例下定时任务执行多次chat 和实践。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

问题

定时任务在同一时间被执行了多次。发现是该微服务有多个实例。每个实例互不干扰都执行了。

原因

  1. 任务执行时间过长,导致多个线程同时执行任务。这可能会发生在一个任务的执行时间大于任务执行周期的情况下。如果是这种情况,可以考虑将任务的执行时间缩短或者使用分布式锁来解决多线程并发执行的问题。

  2. 服务器时间不准确。如果服务器时间不准确,那么定时任务的执行时间也会不准确。可以通过命令行查看服务器时间,然后将其设置为与世界标准时间一致。

  3. 操作系统时间同步不准确。如果操作系统时间同步不准确,那么定时任务的执行时间也会不准确。可以通过操作系统的时间同步功能来解决这个问题。

  4. 定时任务的时间设置有误,导致在每个执行时间点会执行多次。检查cron表达式的配置是否正确,尤其是秒数是否被指定为0。如果cron表达式没有问题,检查程序没有被多次启动或者存在多个线程在执行相同的任务,这也有可能导致定时任务执行多次的问题。

解决问题

启发
另一种思路
解决实践举例–分布式锁
要避免定时任务被集群中的多个实例多次执行,可以使用分布式锁来解决这个问题。分布式锁是一种在集群环境下保证只有一个进程/线程执行指定操作的机制。使用分布式锁可以避免多个实例同时执行同一个定时任务的问题,从而确保任务只会被执行一次。
在Java中,可以使用Redisson或ZooKeeper等分布式锁实现库来实现分布式锁。这些库提供了一些简单的API,可以方便地实现分布式锁的获取和释放,从而保证定时任务的顺序执行。
使用分布式锁的一般步骤如下:

  1. 获取分布式锁
  2. 执行定时任务
  3. 释放分布式锁
    以下是使用Redisson实现分布式锁的示例代码:
@Component
public class MyTask {
    private final RedissonClient redissonClient;
    private final String lockKey = "myTaskLock";
     @Autowired
    public MyTask(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }
     @Scheduled(cron = "0 0 8 * * ?")
    public void runTask() throws InterruptedException {
        RLock lock = redissonClient.getLock(lockKey);
        boolean isLocked = lock.tryLock(0, 10, TimeUnit.SECONDS); // 等待10秒尝试获取锁
        if (!isLocked) {
            // 如果获取锁失败,则不执行任务
            return;
        }
         try {
            // 执行定时任务
            // ...
        } finally {
            lock.unlock();
        }
    }
}

在上面的示例代码中,使用Redisson获取一个名为"myTaskLock"的分布式锁,在任务执行前尝试获取锁,如果获取失败则不执行任务,如果获取成功则执行任务并在执行完毕后释放锁。这样就可以保证同一时刻只有一个实例在执行此任务。

另一个思路–rocketMQ

如果你使用了RocketMQ作为消息中间件,可以使用RocketMQ的消息队列来解决多实例执行定时任务的问题。使用消息队列的方式,每个实例都会向消息队列发送一个消息,只有一个实例会收到消息并执行任务,其他实例则不会执行任务。
以下是使用RocketMQ的示例代码:

@Component
public class MyTask {
    @Autowired
    private DefaultMQProducer defaultMQProducer;
     @PostConstruct
    public void registerTask() throws MQClientException {
        // 注册定时任务
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = schedulerFactory.getScheduler();
        JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
                .withIdentity("myTask")
                .build();
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("myTaskTrigger")
                .withSchedule(CronScheduleBuilder.cronSchedule("0 0 8 * * ?"))
                .build();
        scheduler.scheduleJob(jobDetail, trigger);
        scheduler.start();
    }
     public void executeTask() throws Exception {
        // 发送消息到RocketMQ
        Message message = new Message("myTaskTopic", "myTaskTag", "myTaskKey", "myTaskContent".getBytes());
        SendResult sendResult = defaultMQProducer.send(message);
        System.out.printf("%s%n", sendResult);
    }
}
 @Component
public class MyJob implements Job {
    @Autowired
    private DefaultMQPushConsumer defaultMQPushConsumer;
     @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // 接收消息并执行任务
        try {
            defaultMQPushConsumer.subscribe("myTaskTopic", "myTaskTag");
            MessageListenerConcurrently messageListener = (msgs, context1) -> {
                for (MessageExt msg : msgs) {
                    try {
                        // 执行定时任务
                        // ...
                        System.out.printf("Task executed by %s%n", RocketMQUtil.getLocalAddress());
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            };
            defaultMQPushConsumer.registerMessageListener(messageListener);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在上面的示例代码中,MyTask类注册了一个定时任务,并在任务执行时发送一个消息到RocketMQ的"myTaskTopic"主题。MyJob类则监听"myTaskTopic"主题,并在接收到消息时执行定时任务。使用RocketMQ的消息队列,可以确保任务只会被一个实例执行,从而避免了多个实例同时执行同一个定时任务的问题。

最终解决问题:使用redis标记

在多实例的情况下,可以通过在Redis中设置标记来保证定时任务只被执行一次。可以使用Redis的setnx命令来实现这个功能。

在每个实例执行定时任务之前,先使用setnx命令尝试在Redis中设置一个标记,如果设置成功则说明当前实例可以执行任务,如果设置失败则说明当前任务已经被其他实例执行,当前实例就不再执行任务。当任务执行完毕之后,再使用del命令删除Redis中的标记。

这样可以保证在分布式环境下,同一个任务只会被执行一次
以下是通过Redis标记来保证定时任务只被执行一次的示例代码:
.setIfAbsent方法

@Component
public class MyTask {
    private final RedisTemplate<String, String> redisTemplate;
    private final String lockKey = "myTaskLock";
     @Autowired
    public MyTask(RedisTemplate<String, String> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
     @Scheduled(cron = "0 0 8 * * ?")
    public void runTask() throws InterruptedException {
        // 尝试获取redis中的key
        Boolean isLocked = redisTemplate.opsForValue().setIfAbsent(lockKey, "true");
        if (isLocked != null && isLocked) {
            // 如果获取key成功,则执行任务
            // ...
             // 任务执行完毕后删除锁
            redisTemplate.delete(lockKey);
        }
    }
}

在上面的示例代码中,在任务执行前,使用setIfAbsent方法尝试在Redis中设置名为"myTaskLock"的标记,如果设置成功则说明当前实例可以执行任务。任务执行完毕之后,使用delete方法删除Redis中的标记,这样其他实例就可以尝试获取redis中的key并执行任务。

最终解决加点料

多实例的定时任务使用redis标志为当天的年月日,控制只执行一次,并设置过期时间为3秒
可以使用Redis来实现多实例的定时任务,具体实现方式如下:

  1. 在定时任务开始时,先获取当前日期的年月日,并将其作为 Redis 的 key,然后使用 setnx 命令(只有 key 不存在时才设置值)来设置一个值为 1 的标志,表示当前实例将执行该定时任务。
  2. 然后,再使用 expire 命令为该 key 设置过期时间为 3 秒,确保只有 3 秒内才能执行该定时任务。
  3. 定时任务执行完毕后,使用 del 命令删除该 key,释放标志,以便其他实例可以执行该定时任务。
    下面是一个示例代码:
@Component
public class MyTask {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
     @Scheduled(cron = "0 0 8 * * ?")
    public void executeTask() {
        // 获取当前日期的年月日,作为 Redis 的 key
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
        String key = dateFormat.format(new Date());
         // 使用 setnx 命令设置标志
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1");
         // 如果设置成功,则执行定时任务
        if (flag != null && flag) {
            stringRedisTemplate.expire(key, 3, TimeUnit.SECONDS);
            // 执行定时任务
            // ...
             // 任务执行完毕后,删除标志
            stringRedisTemplate.delete(key);
        }
    }
}

在上面的示例代码中,我们使用了 Spring 的 Scheduled 注解来定义定时任务的执行时间。在 executeTask 方法中,先使用 SimpleDateFormat 获取当前日期的年月日,并将其作为 Redis 的 key。然后使用 opsForValue() 方法获取 StringRedisTemplate 的操作接口,并使用 setnx 命令设置一个值为 1 的标志。如果设置成功,则使用 expire 命令为该 key 设置过期时间为 3 秒,表示该实例将执行该定时任务。任务执行完毕后,删除该 key,释放标志。
这样,就可以很方便地使用 Redis 实现多实例的定时任务,并确保每个定时任务只会被一个实例执行。

注意

这个前提是定时任务使用@Scheduled注解,并且多实例是同一个机器的。
spring scheduled单线程和多线程使用过程中的大坑!!不看到时候绝对后悔!!

@Scheduled注解的使用

由于spring scheduled默认是所有定时任务都在一个线程中执行!!这是个大坑!!!
也就是说定时任务1一直在执行,定时任务2一直在等待定时任务1执行完成。这就导致了生产上定时任务全部卡死的现象。

另一个解决办法

补充小知识

余生大大
我见过最好的讲解进程与线程
结合堆栈存储

注意 吃柚子不吐葡萄皮

**发现在线程数较少的情况下,并不会分配到多个CPU上,而是在单CPU中执行!**而我们在操作系统相关课程中学过,CPU在执行多个进程(线程可以看作轻量级进程)时,是把一段时间分成很多个时间片来分配的,在这个例子中,我们的多线程创建了4个子线程,CPU把资源分配给这个Java程序后,程序内部有4个线程共享这个时间片,也即子线程轮流被分配更小的时间片来执行,而一个时间片不足以把单个线程跑完,于是要等待下一个时间片的到来。而在单线程模式下,程序获得时间片后,由于程序内部只有1个线程,CPU的时间片全部分给这个主线程,从头到尾执行完毕。期间节约了等待获取时间片和线程切换带来的时间损耗,所以总耗时比多线程更少。
还有一种解释,就是Java中创建的线程并不等价于操作系统中的进程,并不是由操作系统直接进行调用的。要知道,Java程序运行在JVM上,而JVM又是运行在OS之上的一个进程,所以我们创建的线程都包含在JVM这个进程当中。

除非指定在 多CPU的物理内核上进行文章来源地址https://www.toymoban.com/news/detail-428251.html

到了这里,关于多实例下定时任务执行多次chat 和实践的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Crontab(定时任务)使用: Linux-Centos7使用crontab制定定时任务,定时执行某任务

    参考:https://blog.csdn.net/m0_49605975/article/details/120701771 1.用yum命令安装Crontab 2.启动,关闭,重启 3.设置开机启动 1.设置定时任务-进入编辑模式 -和vim操作一至 2.查看定时任务列表 3.添加定时任务 样例: 添加一个定时任务,定时清空某个文件夹里面日志文件的内容 3.1 编写一个清

    2024年02月01日
    浏览(75)
  • @Scheduled 定时任务不执行

    启动类上加 @EnableScheduling 注解 定时任务类上加@Component 定时方法上加@Scheduled 解决:进行try…catch异常抛出 原因是: @Scheduled注解会在默认情况下以单线程的方式执行定时任务。 这个“单线程”指两个方面: 如果一个定时任务执行时间大于其任务间隔时间,那么下一次将会等

    2024年02月05日
    浏览(70)
  • Ubuntu定时执行任务

    cron一个Linux定时执行工具,可以定时执行一些任务。 如果显示“no crontab for xxx” 说明没有启动cron。 这样就启动cron了。 服务相关命令: 查看当前定时任务: 添加修改删除定时任务: crontab最后一行,提示添加定时任务的格式: 示例:每天8点,执行test.sh. 示例:每5分钟,写

    2024年02月06日
    浏览(47)
  • 【openWrt】设置执行定时任务

    遇到一个问题,使用openWrt软路由搭建服务器,在docker装了一个maccmsV10,需要每天执行cj信息定时任务,但是maccmsV10本身不支持执行定时任务的配置的。  看了下,openWrt是支持本身是linux系统,所以是可以设置定时任务的。只需要装了cron服务。 设置计划任务,保存 重启cron服务

    2024年02月16日
    浏览(73)
  • 使用shedlock实现分布式定时任务锁【防止task定时任务重复执行】

    第一步:引入shedlock相关依赖 ShedLock还可以使用Mongo,Redis,Hazelcast,ZooKeeper等外部存储进行协调,例如使用redis则引入下面的包 第二步:创建数据库表结构,数据库表的脚本如下: 第三步:添加shedlock配置类 (定时任务防重复执行的配置类) 第四步:在启动类上添加启动注

    2024年02月10日
    浏览(42)
  • python 定时任务执行命令行

    1.使用场景: 定时执行jmeter脚本,通过python定时器隔一段时间执行命令行命令。 2.库: os、datetime、threading (1)利用threading.Timer()定时器实现定时任务 Timer方法 说明 Timer(interval, function, args=None, kwargs=None) 创建定时器 cancel() 取消定时器 start() 使用线程方式执行 join(self, timeout

    2023年04月18日
    浏览(49)
  • linux-crontab每分钟定时执行/定时任务调度

    本文讲解linux上如何调用定时任务,如每分钟打印日志,每日24点执行日志切割脚本等等。 在Linux系统中,crontab命令是一个用于执行定时任务的命令, crond(crontab)是系统默认自带的定时服务 。我们可以通过编辑crontab文件来设置定时任务,使系统可以自动按照设定的时间和频率

    2024年02月06日
    浏览(59)
  • 【Windows】定时任务执行bat文件失败

    bat双击正常,放在Windows服务器的任务计划程序中执行失败 直接执行bat文件,正常, 运行windows定时任务来执行该bat文件,执行失败 1、copy命令前加 “C:Windowssystem32cmd.exe” /C 2、共享盘需要建立连接 3、不要使用最高权限运行 声明:我猜的 1、大概就是用定时任务没有指定执

    2024年02月15日
    浏览(49)
  • 青龙面版 定时任务执行python文件

    打开菜单:定时任务 第一步:新建任务 第二步:输入任务名称, 命令为:task 文件名称 (如果是根目录,直接写文件名称) 举例: 把依赖先安装好, 获取 下个节假日.py 代码

    2024年01月22日
    浏览(47)
  • mysql navicat 自动执行定时任务/事件

    1.查看是否开启定时任务 查看event_scheduler如果为OFF或0就表示关闭  2.设置重启服务器(重启mysql服务)继续执行 提醒:虽然这里用set global event_scheduler = on语句开启了事件,但是每次重启电脑。或重启mysql服务后,会发现,事件自动关闭(event_scheduler=OFF),所以想让事件一直保持

    2024年02月13日
    浏览(32)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包