淘寶超時確認收貨 是 如何實現?

大家好,我是 Tom 哥

今天跟大家聊下定時任務,很多人都喜歡網購,選擇商品、下單、付款,然後在家坐等拆包裹,很少有人主動去點 確認收貨,那豈不是結束不了訂單,賣家也收不到貨款。

其實,平臺早已想到這個問題,所以會有定時任務,我們只需要設定好 目標執行時間,到了時間後,系統會自動執行 確認收貨

接下來,我們來看下,常見的定時任務有哪些?

單點定時任務

1、JDK 原生

自從 JDK1.5 之後,提供了ScheduledExecutorService代替TimerTask來執行定時任務,提供了不錯的可靠性。

public class SomeScheduledExecutorService {
    public static void main(String[] args) {
        // 創建任務隊列,共 10 個線程
        ScheduledExecutorService scheduledExecutorService =
                Executors.newScheduledThreadPool(10);
        // 執行任務: 1秒 後開始執行,每 30秒 執行一次
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            System.out.println("執行任務:" + new Date());
        }, 10, 30, TimeUnit.SECONDS);
    }
}

2、Spring Task

Spring Framework 自帶定時任務,提供了 cron 表達式來實現豐富定時任務配置。

新手推薦使用 https://cron.qqe2.com / 這個網站來匹配你的cron表達式

@Configuration
@EnableScheduling
public class SomeJob {
    private static final Logger LOGGER = LoggerFactory.getLogger(SomeJob.class);

    /**
     * 每分鐘執行一次(例:18:01:00,18:02:00)
     * 秒 分鐘 小時 日 月 星期 年
     */
    @Scheduled(cron = "0 0/1 * * * ? *")
    public void someTask() {
       //...
    }
}

單點的定時服務在目前微服務的大環境下,應用場景越來越侷限,所以嚐鮮一下分佈式定時任務吧。

3、基於 Redis 實現

相較於之前兩種方式,這種基於 Redis 的實現可以通過多點來增加定時任務,多點消費。但是要做好防範重複消費的準備。

3.1 通過 ZSet 的方式

將定時任務存放到 ZSet 集合中,並且將過期時間存儲到 ZSet 的 Score 字段中,然後通過一個循環來判斷當前時間內是否有需要執行的定時任務,如果有則進行執行。

具體實現代碼如下:

@Configuration
@EnableScheduling
public class RedisJob {
    public static final String JOB_KEY = "redis.job.task";
    private static final Logger LOGGER = LoggerFactory.getLogger(RedisJob.class);
    @Autowired private StringRedisTemplate stringRedisTemplate;

    /**
     * 添加任務.
     *
     * @param task
     */
    public void addTask(String task, Instant instant) {
        stringRedisTemplate.opsForZSet().add(JOB_KEY, task, instant.getEpochSecond());
    }

    /**
     * 定時任務隊列消費
     * 每分鐘消費一次(可以縮短間隔到1s)
     */
    @Scheduled(cron = "0 0/1 * * * ? *")
    public void doDelayQueue() {
        long nowSecond = Instant.now().getEpochSecond();
        // 查詢當前時間的所有任務
        Set<String> strings = stringRedisTemplate.opsForZSet().range(JOB_KEY, 0, nowSecond);
        for (String task : strings) {
            // 開始消費 task
            LOGGER.info("執行任務:{}", task);
        }
        // 刪除已經執行的任務
        stringRedisTemplate.opsForZSet().remove(JOB_KEY, 0, nowSecond);
    }
}

適用場景如下:

優勢是:

3.2 鍵空間通知的方式

我們可以通過 Redis 的鍵空間通知來實現定時任務,它的實現思路是給所有的定時任務設置一個過期時間,等到了過期之後,我們通過訂閱過期消息就能感知到定時任務需要被執行了,此時我們執行定時任務即可。

默認情況下 Redis 是不開啓鍵空間通知的,需要我們通過config set notify-keyspace-events Ex的命令手動開啓。開啓之後定時任務的代碼如下:

自定義監聽器

public class KeyExpiredListener extends KeyExpirationEventMessageListener {
    public KeyExpiredListener(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
    }

    @Override
    public void onMessage(Message message, byte[] pattern) {
        // channel
        String channel = new String(message.getChannel(), StandardCharsets.UTF_8);
        // 過期的key
        String key = new String(message.getBody(), StandardCharsets.UTF_8);
        // todo 你的處理
    }
}

設置該監聽器

@Configuration
public class RedisExJob {
    @Autowired private RedisConnectionFactory redisConnectionFactory;
    @Bean
    public RedisMessageListenerContainer redisMessageListenerContainer() {
        RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer();
        redisMessageListenerContainer.setConnectionFactory(redisConnectionFactory);
        return redisMessageListenerContainer;
    }

    @Bean
    public KeyExpiredListener keyExpiredListener() {
        return new KeyExpiredListener(this.redisMessageListenerContainer());
    }
}

Spring 會監聽符合以下格式的 Redis 消息

private static final Topic TOPIC_ALL_KEYEVENTS = new PatternTopic("__keyevent@*");

基於 Redis 的定時任務能夠適用的場景也比較有限,但實現上相對簡單,但對於功能冪等有很大要求。從使用場景上來說,更應該叫做延時任務。

場景舉例:

優劣勢是:

分佈式定時任務

引入分佈式定時任務組件 or 中間件

將定時任務作爲單獨的服務,遏制了重複消費,獨立的服務也有利於擴展和維護。

1、quartz

依賴於 MySQL,使用相對簡單,可多節點部署,通過競爭數據庫鎖來保證只有一個節點執行任務。沒有圖形化管理頁面,使用相對麻煩。

2、elastic-job-lite

依賴於 Zookeeper,通過 zookeeper 的註冊與發現,可以動態的添加服務器。

3、LTS

依賴於 Zookeeper,集羣部署, 可以動態的添加服務器。可以手動增加定時任務,啓動和暫停任務。

4、xxl-job

國產,依賴於 MySQL, 基於競爭數據庫鎖保證只有一個節點執行任務,支持水平擴容。可以手動增加定時任務,啓動和暫停任務。

總結

微服務下,推薦使用 xxl-job 這一類組件服務將定時任務合理有效的管理起來。而單點的定時任務有其侷限性,適用於規模較小、對未來擴展要求不高的服務。

相對而言,基於 spring task 的定時任務最簡單快捷,而 xxl-job 的難度主要體現在集成和調試上。無論是什麼樣的定時任務,你都需要確保:

中間件可以將服務解耦,但增加了複雜度

來源: 

juejin.cn/post/693091287005832801

關於我:Tom 哥,前阿里 P7 技術專家,offer 收割機,參加多次淘寶雙 11 大促活動。歡迎關注,我會持續輸出更多經典原創文章,爲你晉級大廠助力

目前微信羣已開放,想進交流羣的小夥伴請添加 Tom 哥微信,暗號「進羣」,嘮嗑聊天, 技術交流,圍觀朋友圈,人生打怪不再寂寞

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/8MM2-_3KifMFS6SxV3zslw