プロジェクトをクラスターにデプロイする場合の Spring Task の落とし穴

1. Spring Task を通じてスケジュールされたタスクを実行する

1. スケジュールされたタスクを作成する

次のコードでは、5 秒ごとに情報を出力するタスクを実装したいと考えています。

package com.qfedu.day85.task;

import org.redisson.api.RedissonClient;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.Date;

@Component
public class PrintTask {

    // 任务默认同步执行的
    @Async  // 表示异步执行的注解
    // 每分钟的第5秒触发定时任务
    @Scheduled(cron = "5 * * * * ?")
    public void printInfo() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS");
        System.out.println(sdf.format(new Date()) + ": print info test");
    }
}

2. 同じプロジェクトの複数のインスタンスを開始します
。 まず、「構成の編集」を通じて実行中のインスタンス構成を追加できます。


次に、次の図に示すように、プロジェクトの複数のインスタンスを開始します。

3. 実行結果の表示
次に、以下に示すように、上記のコードの実行結果を表示できます。


実行結果を比較すると、2 つのインスタンスがタスクを「同時に」実行していることがわかります。実際の開発では、プロジェクトをクラスタにデプロイすることが多いですが、Spring Task を使用してスケジュールされたタスクを実行する場合、タスクを実行するインスタンスは 1 つだけで済みます。
では、複数のインスタンスで同時にタスクを実行したくない場合は、どうすればよいでしょうか?
一般に、分散ロックを使用してタスクをロックし、複数のインスタンスが同時にタスクを実行するのを防ぐことができます。
しかし、本当にそんなことが可能なのでしょうか?

2. スケジュールされたタスクで分散ロックを使用する

1. Redisson を通じてタスクをロックします。
ロック後のコードは次のとおりです。

package com.qfedu.day85.task;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;

@Component
public class PrintTask {
    // 注入RedissonClient 对象
    @Resource
    private RedissonClient redissonClient;

    @Async
    @Scheduled(cron = "0/5 * * * * ?")
    public void printInfo2() {
        // 获取锁对象,参数表示redis中key值
        RLock taskLock = redissonClient.getLock("taskLock");

        try {
            // 尝试加锁,
            // 第一个参数,表示加锁的重试时间,如果锁被占用,在指定时间内尝试加锁
            // 第二个参数,表示锁过期时间
            // 返回true/false
            // 本例中,第一个参数是0,表示如果没有加上锁,直接返回false
            if (taskLock.tryLock(0, 10, TimeUnit.SECONDS)) {
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
                System.out.println(sdf.format(new Date()) + ": print info test");
                // Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (taskLock.isLocked()) {
                // 判断是否当前线程拥有该锁
                if (taskLock.isHeldByCurrentThread()) {
                    // 释放锁
                    taskLock.unlock();
                }
            }
        }
    }
}

この例では、Redisson の使用については説明しません。

2. 実行結果


上図の実行結果から、分散ロックが使用されているにもかかわらず、「17:38:15」などのある時点で、2 つのインスタンスが同じタスクを「同時に」実行していることがわかりました。
分散ロックは機能しないのでしょうか? もちろん違います、真実はこんな感じです...
私たちの任務はあまりにも早く実行されました!
Day85Application2 のインスタンスでは、タスクは 17:38:15.008 にロックされて実行され、タスクは迅速に実行されます。Day85Application のインスタンスでは、17:38:15.029 にタスクが実行され、ロックを追加したところ、ロックが占有されていないことが判明したため、タスクが再度実行されました。
上記の分析により、タスクの実行時間が非常に短い場合、複数のインスタンスが 1 秒以内に複数回タスクをロックして実行する可能性があるため、同じ秒内にタスクが「同時に」実行される現象が発生することがわかります。 。
 


3. 解決策
上記の問題を解決するには、コードを少し変更します。
 


変更スキームは上記のコードに示されており、タスクは 1 秒間強制的にスリープされます。再度実行すると、同じ秒内にタスクが「同時」に実行される現象が起こらなくなることがわかります。
しかし、このアプローチは本当に品位を低下させます。プロジェクトでクラスターのデプロイメントが必要な場合、スケジュールされたタスクを使用する場合は、quartz、xxl-job などのサードパーティの分散タスク フレームワークを使用するのが最善です。
上記はあくまで家族の意見ですので、不適切な点がございましたら、退役軍人の方はコメント欄にメッセージを残していただくか、私にプライベートメッセージを送っていただければ幸いです。

ビデオチュートリアルポータ​​ル:Springチュートリアルの入門から習得まで、Spring一式(ソースコード実践解説)

 

おすすめ

転載: blog.csdn.net/GUDUzhongliang/article/details/131845617