關於為了一時方便,使用@Scheduled注解定時踩的坑


摘要:

        事情是這樣的前兩周在做項目的時候碰到一個需求---要求每天晚上執行一個任務,公司統一使用的是 xxl-job 寫定時任務的,我當時為了方便自己,然后就簡單的使用了Spring的那個@Scheduled來定時,當時寫完覺得這也太方便了吧,以后我就只使用這個方法定時了,方便又快捷,用什么 xxl-job 呢(要什么自行車呢😂),哈哈。

需求:

        要求在當天11:58的時候需要生成當天任務的報告,然后在第二天12:10得時候生成第二天得任務,-----本來我使用了兩個@Scheduled注解,想用兩個定時任務來做的,結果生成任務的那一個定時任務居然不起作用。。。然后剛好起作用的這個任務在不起作用的那個之前,所以我就在第一個任務執行時發消息到MQ,使用MQ的延時隊列來做了。

換一種閱讀體驗方式吧,嘿嘿

一、第一版代碼

    @Scheduled(cron = "0 58 23 */1 * ?")
    @Override
    public void createReprotTimer() {
        // 發送到 MQ 12分鍾后(00:10)--- 生成第二天的任務
        String activeProfile = SpringUtil.getActiveProfile();
        String tag = "prod".equals(activeProfile) ? "CREATE_TASK" : "CREATE_TASK_DEV";
        producerUtil.sendTimeMsg(
                "TID_COMMON",
                tag,
                "生成任務".getBytes(),
                "CREATE_TASK",
                System.currentTimeMillis() + 720 * 1000
        );
        // 每天晚上 11:58 生成報告
        this.createReprot();
    }

是不是賊簡單,哈哈,一切看着沒什么毛病,當然在測試環境測試時也沒有任何問題,但是后面上了生產問題就暴露出來了

到生產發現的問題:測試環境一切正常,在生產環境一直會出現報告和任務重復生成的情況

原因:我們測試環境只有一台服務器,所以這個執行完全沒毛病,但是到了正式環境時每個服務都是以集群的形式部署的,當時我這個服務部署在兩台服務器上,所以每天到了11:58的時候兩台服務器都會檢測到我的定時,所以它們會執行兩次。

二、第二版代碼

方案:基於這個問題我立刻就想到了跨服務器肯定的使用第三方工具了,於是就使用了Redis來解決。

思路:執行這個方法的時候,先判斷Redis中是否存在我存放的指定的業務Key,如果里面已經存在了這個Key,那么就說明已經生成過任務--執行過這個方法了,那么就直接跳過,如果判斷里面沒有Key---說明今天還沒有執行過定時任務,則直接執行,然后再把Key壓到Redis中並且定時一分鍾后過期。

    @Scheduled(cron = "0 58 23 */1 * ?")
    @Override
    public void createReprotTimer() {
        if (!redisUtil.hasKey("CREATE_TASK_TIME")) {
            // // 發送到 MQ 12分鍾后(00:10)--- 生成第二天的任務
            String activeProfile = SpringUtil.getActiveProfile();
            String tag = "prod".equals(activeProfile) ? "CREATE_TASK" : "CREATE_TASK_DEV";
            producerUtil.sendTimeMsg(
                    "TID_COMMON",
                    tag,
                    "生成任務".getBytes(),
                    "CREATE_TASK",
                    System.currentTimeMillis() + 720 * 1000
            );
            // 每天晚上 11:58 生成報告
            this.createReprot();
            redisUtil.setEx("CREATE_TASK_TIME", "CREATE_TASK_TIME", 1, TimeUnit.MINUTES);
        }
    }

第二版代碼出現的問題:不起作用,依然會重復執行🤡

分析:貌似代碼沒問題,思路也沒問題了。。。

原因:看似代碼沒任何問題----當然對於一般業務性質的問題是沒有太大的問題的,但是我們這個場景是定時場景,那就意味着兩台服務器肯定是同一時刻執行到這段代碼的----他們同時判斷Redis中是否有這個業務Key,那么這個它們肯定得到的結果就是Redis中沒有這個Key,它們兩個就會執行這段代碼,導致生成重復的任務;

解決方法:問題原因找到了,那么也就意味着問題已經幾乎解決了,很明顯,這里只需要加一個分布式鎖就可以了,保證在這個時間點上這段代碼是順序執行的就可以了。

三、第三版代碼

分布式鎖我這里使用的是Redisson,用法很簡單,開箱即用--------算了寫一下吧

1、引入依賴:
<!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.16.3</version>
</dependency>
 2、業務代碼:
    @Scheduled(cron = "0 58 23 */1 * ?")
    @Override
    public void createReprotTimer() {
        // 冪等處理,防止生成重復報告和發送重復MQ消息, 加鎖:防止兩台服務同時執行這段代碼
        RLock lock = redissonClient.getLock("CREATE_REPORT");
        lock.lock(1, TimeUnit.MINUTES);
        try {
            if (!redisUtil.hasKey("CREATE_TASK_TIME")) {
                // // 發送到 MQ 12分鍾后(00:10)--- 生成第二天的任務
                String activeProfile = SpringUtil.getActiveProfile();
                String tag = "prod".equals(activeProfile) ? "CREATE_TASK" : "CREATE_TASK_DEV";
                producerUtil.sendTimeMsg(
                        "TID_COMMON",
                        tag,
                        "生成任務".getBytes(),
                        "CREATE_TASK",
                        System.currentTimeMillis() + 720 * 1000
                );
                // 每天晚上 11:58 生成報告
                this.createReprot();
                redisUtil.setEx("CREATE_TASK_TIME", "CREATE_TASK_TIME", 1, TimeUnit.MINUTES);
            }
        } catch (Exception e) {
            throw new ServiceException(OrderExceptionEnum.ORDER_FILE_CANCEL);
        } finally {
            lock.unlock();
        }
    }

 稍微再解釋一下吧:首先我們將這一段代碼鎖住,並且設置鎖的超時時間為1分鍾,防止出現所無法釋放的情況,然后執行時去Redis中獲取這個唯一的業務Key,如果沒有就直接執行這個代碼,並且執行完成之后將Key壓入Redis中;如果已經有了我們要找的這個唯一的業務Key,那么就直接跳過即可。

完美解決---又是一個愉快的周末

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM