<var id="k6qs5"><sup id="k6qs5"></sup></var>

  • 全棧小白的gravatar頭像
    全棧小白 2023-11-06 14:28:03
    SpringBoot整合定時任務遇到的多實例問題

    嘮嗑部分

    是這樣,前幾日完善了定時任務的日志記錄,今日切換了服務器,多部署了一個節點,使用nginx負載均衡,但是查看日志卻發現了如下情況

    SpringBoot整合定時任務遇到的多實例問題

    那糟糕了,傳說中的多實例問題出現了,今天我們就來聊聊項目實戰中定時任務如何做,首先我們看如下問題

    1、什么是定時任務,能幫我們解決什么實際問題?

    見名知意,定時任務就是讓程序指定時間去執行某段代碼,例如,每日8點給女朋友發早安祝福

    那么能給我們開發中解決什么問題呢?

    在實際開發中,有許多需要定時任務的場景,如,每日定時去同步數據、緩存的預熱、定時清理日志文件、定時統計榜單...

     

    2、項目實戰中哪些場景需要使用到定時任務?

    需求一:產品經理要求實現系統的3天內熱搜榜,每日0點更新數據

    需求二:系統需要依賴第三方系統的數據,而且請求并發較大,第三方數據是每日更新的

    需求三:系統每天都會有大量操作日志,產品經理要求只保留一個月的數據

    需求四:對于系統主頁數據,每日9-12點并發最大,需要定時對緩存預熱

    ......

    以上需求都可以用定時任務實現

    3、推薦使用的定時任務組件有哪些?

    Spring整合了Scheduled,輕量級而且很好用,無UI展示

    xxl-Job,xxl是xxl-job的開發者大眾點評的許雪里名稱的拼音開頭,主要用于處理分布式的定時任務,其主要由調度中心和執行器組成,有良好的UI界面。

    elastic-Job,Elastic-Job是當當網推出的分布式任務調度框架,用于解決分布式任務的協調調度問題,保證任務不重復不遺漏地執行;無UI展示,需要分布式協調工具Zookeeper的支持

    ......

    4、如何實現分布式定時任務,避免多實例問題?

    首先我們來說說什么是多實例問題,在我們的項目開發中,我們在部署定時任務時,通常只部署一臺機器,如果部署多臺機器時,同一個任務會執行多次(每個機器都會執行,互不影響),那如果有一些給用戶計算收益定時任務,每天定時給用戶計算收益,如果部署了多臺,同一個用戶將重復計算多次收益,那就芭比Q了,那如果只部署一臺,則會有單點故障問題,可用性無法保證

    以上所說的xxl-job,elastic-Job均可以解決多實例問題,保證任務不重復不遺漏地執行

    那我們使用Spring自帶的Scheduled,如何避免多實例問題呢,我們可以使用redis鎖來保證,具體邏輯如下

    每個實例調用setnx命令插入一條數據,插入成功后返回1的實例執行job,返回0的不執行

    言歸正傳

    首先我們看下之前的代碼邏輯,我這里是整合的Scheduled,自行封裝的定時任務,在執行時,沒有解決多實例問題

    SpringBoot整合定時任務遇到的多實例問題

    那我們的邏輯是,在此段代碼執行時加入redis鎖,保證執行一次

    1、redis加鎖方法封裝

    /**
    * 加鎖
    * @param key
    * @param timeStamp
    * @return
    */
    public Boolean lock(String key, String timeStamp){
        if (redisTemplate.opsForValue().setIfAbsent(getKey(key), timeStamp)) {
            return true;
        }
        String currentLock = (String) redisTemplate.opsForValue().get(getKey(key));
        if (StringUtils.hasLength(currentLock) && Long.parseLong(currentLock) < System.currentTimeMillis()) {
            String preLock = (String) redisTemplate.opsForValue().getAndSet(getKey(key), timeStamp);
    ?
            if (StringUtils.hasLength(preLock) && preLock.equals(currentLock)) {
                return true;
            }
        }
        return false;
    }
    ?
    /**
    * 解鎖
    * @param key
    * @param timeStamp
    */
    public void unLock(String key, String timeStamp){
        try {
            String currentValue = (String) redisTemplate.opsForValue().get(getKey(key));
            if (StringUtils.hasLength(currentValue) && currentValue.equals(timeStamp)) {
                redisTemplate.opsForValue().getOperations().delete(getKey(key));
            }
        } catch (Exception e) {
            log.error("解鎖異常");
        }
    }

    2、多實例解決實現邏輯

    public void run() {
        long startTime = System.currentTimeMillis();
        Map<String, Scheduled> scheduledMap = scheduledTaskService.getScheduledMap();
        ScheduledLog scheduledLog = new ScheduledLog();
        Scheduled scheduled = scheduledMap.get(beanName);
        Boolean flag = Boolean.TRUE;
        String timeStamp = String.valueOf(System.currentTimeMillis() + 300L);
        try {
            Boolean lock = redisUtil.lock(redisUtil.getCacheKey(CachePrefixContent.LOCK_PREFIX, beanName), timeStamp);
            if (lock) {
                BaseResult result = BaseResult.ok();
                scheduledLog.setTaskId(scheduled.getTaskId());
                scheduledLog.setExecuteTime(LocalDateTime.now());
                // 執行定時任務處理邏輯
                execute(result);
                if (result.resOk()) {
                    scheduledLog.setExecuteStatus(Boolean.TRUE);
                } else {
                    scheduledLog.setExecuteStatus(Boolean.FALSE);
                }
                scheduledLog.setExecuteDesc(result.getMsg());
                redisUtil.unLock(redisUtil.getCacheKey(CachePrefixContent.LOCK_PREFIX, beanName), timeStamp);
            } else {
                flag = Boolean.FALSE;
            }
        } catch (Exception e) {
            log.error("定時任務:{}執行失敗,{}", scheduled.getTaskName(), e);
            scheduledLog.setExecuteStatus(Boolean.FALSE);
            scheduledLog.setExecuteDesc(e.getMessage());
        } finally {
            long endTime = System.currentTimeMillis();
            log.info("【{}】【】【{}ms】", "定時任務", scheduled.getTaskName(), endTime - startTime);
            if (flag) {
                completableFutureService.runAsyncTask(() -> {
                    scheduledLogMapper.insert(scheduledLog);
                });
            }
        }
    }

    3、效果展示

    每30秒兩個示例只有單臺節點執行成功

    SpringBoot整合定時任務遇到的多實例問題

    結語

    1、以上問題就解決了,快去給你的代碼加上吧!

    2、制作不易,一鍵四連再走吧,您的支持永遠是我最大的動力!


    打賞

    已有1人打賞

    最代碼官方的gravatar頭像
    最近瀏覽
    java小書童  LV17 1月15日
    best2018  LV46 1月4日
    fff2003  LV6 1月2日
    fengzf  LV16 2023年12月7日
    yuan666 2023年12月4日
    暫無貢獻等級
    暫無貢獻等級
    走丟的小怪獸  LV8 2023年12月1日
    2410068425  LV23 2023年11月29日
    葉兒飛  LV4 2023年11月29日
    YafengLiang  LV15 2023年11月29日
    頂部 客服 微信二維碼 底部
    >掃描二維碼關注最代碼為好友掃描二維碼關注最代碼為好友
    国产 国产 高清|护士奶头又白又大又好摸|中文字幕av一区二区三区|午夜亚洲国产理论片2020
    <var id="k6qs5"><sup id="k6qs5"></sup></var>