Comment implémenter la fonction de tâche planifiée SpringBoot
Contexte
Le projet a besoin d'une fonction capable d'ajouter dynamiquement des tâches planifiées. Le projet utilise actuellement le système de planification de tâches planifiées xxl-job. Cependant, après une certaine compréhension de la fonction xxl-job, j'ai trouvé que xxl-job est très utile. . La prise en charge de l'ajout dynamique de tâches planifiées au projet et de la suppression dynamique de tâches planifiées n'est pas très bonne, vous devez donc implémenter manuellement la fonction d'une tâche planifiée par vous-même
Deuxième planification dynamique des tâches planifiées
1 Sélection technologique
Timer code> ou <code>ScheduledExecutorService
Timer
or ScheduledExecutorService
这两个都能实现定时任务调度,先看下Timer的定时任务调度
public class MyTimerTask extends TimerTask { private String name; public MyTimerTask(String name){ this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public void run() { //task Calendar instance = Calendar.getInstance(); System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(instance.getTime())); } } Timer timer = new Timer(); MyTimerTask timerTask = new MyTimerTask("NO.1"); //首次执行,在当前时间的1秒以后,之后每隔两秒钟执行一次 timer.schedule(timerTask,1000L,2000L);
在看下ScheduledThreadPoolExecutor的实现
//org.apache.commons.lang3.concurrent.BasicThreadFactory ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1, new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build()); executorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { //do something } },initialDelay,period, TimeUnit.HOURS);
两个都能实现定时任务,那他们的区别呢,使用阿里p3c会给出建议和区别
多线程并行处理定时任务时,Timer运行多个TimeTask时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,使用ScheduledExecutorService则没有这个问题。
从建议上来看,是一定要选择ScheduledExecutorService
了,我们看看源码看看为什么Timer
出现问题会终止执行
/** * The timer thread. */ private final TimerThread thread = new TimerThread(queue); public Timer() { this("Timer-" + serialNumber()); } public Timer(String name) { thread.setName(name); thread.start(); }
新建对象时,我们看到开启了一个线程,那么这个线程在做什么呢?一起看看
class TimerThread extends Thread { boolean newTasksMayBeScheduled = true; /** * 每一件一个任务都是一个quene */ private TaskQueue queue; TimerThread(TaskQueue queue) { this.queue = queue; } public void run() { try { mainLoop(); } finally { // Someone killed this Thread, behave as if Timer cancelled synchronized(queue) { newTasksMayBeScheduled = false; queue.clear(); // 清除所有任务信息 } } } /** * The main timer loop. (See class comment.) */ private void mainLoop() { while (true) { try { TimerTask task; boolean taskFired; synchronized(queue) { // Wait for queue to become non-empty while (queue.isEmpty() && newTasksMayBeScheduled) queue.wait(); if (queue.isEmpty()) break; // Queue is empty and will forever remain; die // Queue nonempty; look at first evt and do the right thing long currentTime, executionTime; task = queue.getMin(); synchronized(task.lock) { if (task.state == TimerTask.CANCELLED) { queue.removeMin(); continue; // No action required, poll queue again } currentTime = System.currentTimeMillis(); executionTime = task.nextExecutionTime; if (taskFired = (executionTime<=currentTime)) { if (task.period == 0) { // Non-repeating, remove queue.removeMin(); task.state = TimerTask.EXECUTED; } else { // Repeating task, reschedule queue.rescheduleMin( task.period<0 ? currentTime - task.period : executionTime + task.period); } } } if (!taskFired) // Task hasn't yet fired; wait queue.wait(executionTime - currentTime); } if (taskFired) // Task fired; run it, holding no locks task.run(); } catch(InterruptedException e) { } } } }
我们看到,执行了 mainLoop()
,里面是 while (true)
方法无限循环,获取程序中任务对象中的时间和当前时间比对,相同就执行,但是一旦报错,就会进入finally中清除掉所有任务信息。
这时候我们已经找到了答案,timer是在被实例化后,启动一个线程,不间断的循环匹配,来执行任务,他是单线程的,一旦报错,线程就终止了,所以不会执行后续的任务,而ScheduledThreadPoolExecutor是多线程执行的,就算其中有一个任务报错了,并不影响其他线程的执行。
2 使用ScheduledThreadPoolExecutor
从上面看,使用ScheduledThreadPoolExecutor
还是比较简单的,但是我们要实现的更优雅一些,所以选择 TaskScheduler
来实现
@Component public class CronTaskRegistrar implements DisposableBean { private final Map<Runnable, ScheduledTask> scheduledTasks = new ConcurrentHashMap<>(16); @Autowired private TaskScheduler taskScheduler; public TaskScheduler getScheduler() { return this.taskScheduler; } public void addCronTask(Runnable task, String cronExpression) { addCronTask(new CronTask(task, cronExpression)); } private void addCronTask(CronTask cronTask) { if (cronTask != null) { Runnable task = cronTask.getRunnable(); if (this.scheduledTasks.containsKey(task)) { removeCronTask(task); } this.scheduledTasks.put(task, scheduleCronTask(cronTask)); } } public void removeCronTask(Runnable task) { Set<Runnable> runnables = this.scheduledTasks.keySet(); Iterator it1 = runnables.iterator(); while (it1.hasNext()) { SchedulingRunnable schedulingRunnable = (SchedulingRunnable) it1.next(); Long taskId = schedulingRunnable.getTaskId(); SchedulingRunnable cancelRunnable = (SchedulingRunnable) task; if (taskId.equals(cancelRunnable.getTaskId())) { ScheduledTask scheduledTask = this.scheduledTasks.remove(schedulingRunnable); if (scheduledTask != null){ scheduledTask.cancel(); } } } } public ScheduledTask scheduleCronTask(CronTask cronTask) { ScheduledTask scheduledTask = new ScheduledTask(); scheduledTask.future = this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger()); return scheduledTask; } @Override public void destroy() throws Exception { for (ScheduledTask task : this.scheduledTasks.values()) { task.cancel(); } this.scheduledTasks.clear(); } }
TaskScheduler
public interface TaskScheduler { /** * Schedule the given {@link Runnable}, invoking it whenever the trigger * indicates a next execution time. * <p>Execution will end once the scheduler shuts down or the returned * {@link ScheduledFuture} gets cancelled. * @param task the Runnable to execute whenever the trigger fires * @param trigger an implementation of the {@link Trigger} interface, * e.g. a {@link org.springframework.scheduling.support.CronTrigger} object * wrapping a cron expression * @return a {@link ScheduledFuture} representing pending completion of the task, * or {@code null} if the given Trigger object never fires (i.e. returns * {@code null} from {@link Trigger#nextExecutionTime}) * @throws org.springframework.core.task.TaskRejectedException if the given task was not accepted * for internal reasons (e.g. a pool overload handling policy or a pool shutdown in progress) * @see org.springframework.scheduling.support.CronTrigger */ @Nullable ScheduledFuture<?> schedule(Runnable task, Trigger trigger);
@Configuration public class SchedulingConfig { @Bean public TaskScheduler taskScheduler() { ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); // 定时任务执行线程池核心线程数 taskScheduler.setPoolSize(4); taskScheduler.setRemoveOnCancelPolicy(true); taskScheduler.setThreadNamePrefix("TaskSchedulerThreadPool-"); return taskScheduler; } }
public ScheduledExecutorService getScheduledExecutor() throws IllegalStateException { Assert.state(this.scheduledExecutor != null, "ThreadPoolTaskScheduler not initialized"); return this.scheduledExecutor; }
ScheduledExecutorService
. pour voir pourquoi Timer< /code>L'exécution sera terminée si un problème survient<h3><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class='brush:php;toolbar:false;'>public void executeTask(Long taskId) {
if (!redisService.setIfAbsent(String.valueOf(taskId),"1",2L, TimeUnit.SECONDS)) {
log.info("已有执行中定时发送短信任务,本次不执行!");
return;
}</pre><div class="contentsignin">Copier après la connexion</div></div>Lors de la création d'un nouvel objet, on voit qu'un thread est démarré, alors que fait ce thread ? Jetons un coup d'oeil<p>rrreee</p>Nous voyons que <code>mainLoop()
est exécuté, et à l'intérieur se trouve la méthode while (true)
qui boucle à l'infini pour obtenir l'heure et l'heure actuelle dans l'objet tâche du programme Si l'heure est comparée, elle sera exécutée si elle est la même. Cependant, une fois qu'une erreur est signalée, elle entrera définitivement et effacera toutes les informations de la tâche. À l'heure actuelle, nous avons trouvé la réponse. Une fois le minuteur instancié, il démarre un thread et exécute des tâches dans une boucle ininterrompue. Une fois qu'une erreur est signalée, le thread est terminé, il le fera donc. ne sera pas exécuté. Pour les tâches suivantes, ScheduledThreadPoolExecutor est exécuté par plusieurs threads. Même si l'une des tâches signale une erreur, cela n'affectera pas l'exécution des autres threads. 🎜2 Utiliser ScheduledThreadPoolExecutor🎜🎜🎜De ce qui précède, utiliser ScheduledThreadPoolExecutor
est relativement simple, mais nous voulons l'implémenter de manière plus élégante, nous choisissons donc TaskScheduler
pour l'implémenter🎜 rrreee 🎜TaskScheduler
est la classe principale pour cette implémentation de fonction, mais c'est une interface 🎜rrreee🎜Comme vous pouvez le voir dans le code précédent, nous avons injecté cette classe dans la classe, mais c'est une interface. Comment savoir de quelle classe d'implémentation il s'agit ? Dans le passé, lorsque cela se produisait, je devais ajouter @Primany ou @Quality à la classe pour exécuter la classe d'implémentation, mais nous avons vu qu'il n'y avait aucune marque sur mon injection car c'était le cas. implémenté d'une autre manière🎜rrreee 🎜Bean TaskScheduler est enregistré lors de l'initialisation de Spring, et nous pouvons voir que son implémentation est ThreadPoolTaskScheduler. Dans les informations en ligne, certaines personnes disent que ThreadPoolTaskScheduler est la classe d'implémentation par défaut de TaskScheduler. Il reste encore à le spécifier, et de cette façon, lorsque l'on veut remplacer l'implémentation, il suffit de modifier la classe de configuration, qui est très flexible. 🎜🎜Pourquoi est-il considéré comme une implémentation plus élégante ? Parce que son noyau est également implémenté via ScheduledThreadPoolExecutor🎜rrreee🎜Trois problèmes d'exécution de tâches multi-nœuds🎜🎜Dans ce processus d'implémentation, je n'ai pas choisi xxl-job Au lieu de l'implémenter, TaskScheduler est utilisé. Cela crée également un problème. xxl-job est un système de planification de programmes distribués. Lorsqu'une application qui souhaite effectuer des tâches planifiées utilise xxl-job, quel que soit le nombre de tâches déployées dans le nœud d'application, xxl-job le fera uniquement. sélectionnez l'un des nœuds comme nœud pour l'exécution des tâches planifiées, afin que les tâches planifiées ne soient pas exécutées simultanément sur différents nœuds, ce qui entraînerait des problèmes d'exécution répétés. Lorsque vous utilisez TaskScheduler pour l'implémenter, vous devez considérer le problème de l'exécution répétée de plusieurs nœuds. . Bien sûr, puisqu'il y a un problème, il existe une solution🎜🎜· La première option consiste à séparer la fonction de tâche planifiée et à la déployer séparément, et à ne déployer qu'un seul nœud. La deuxième option consiste à utiliser redis setNx pour garantir que seul une tâche est exécutée en même temps🎜🎜moi, j'ai choisi la deuxième option à mettre en œuvre. Bien sûr, il existe également des moyens de garantir qu'elle ne se répète pas, je n'entrerai pas dans les détails ici.Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Outils d'IA chauds

Undresser.AI Undress
Application basée sur l'IA pour créer des photos de nu réalistes

AI Clothes Remover
Outil d'IA en ligne pour supprimer les vêtements des photos.

Undress AI Tool
Images de déshabillage gratuites

Clothoff.io
Dissolvant de vêtements AI

AI Hentai Generator
Générez AI Hentai gratuitement.

Article chaud

Outils chauds

Bloc-notes++7.3.1
Éditeur de code facile à utiliser et gratuit

SublimeText3 version chinoise
Version chinoise, très simple à utiliser

Envoyer Studio 13.0.1
Puissant environnement de développement intégré PHP

Dreamweaver CS6
Outils de développement Web visuel

SublimeText3 version Mac
Logiciel d'édition de code au niveau de Dieu (SublimeText3)

Introduction à Jasypt Jasypt est une bibliothèque Java qui permet à un développeur d'ajouter des fonctionnalités de chiffrement de base à son projet avec un minimum d'effort et ne nécessite pas une compréhension approfondie du fonctionnement du chiffrement. Haute sécurité pour le chiffrement unidirectionnel et bidirectionnel. technologie de cryptage basée sur des normes. Cryptez les mots de passe, le texte, les chiffres, les binaires... Convient pour l'intégration dans des applications basées sur Spring, API ouverte, pour une utilisation avec n'importe quel fournisseur JCE... Ajoutez la dépendance suivante : com.github.ulisesbocchiojasypt-spring-boot-starter2 1.1. Les avantages de Jasypt protègent la sécurité de notre système. Même en cas de fuite du code, la source de données peut être garantie.

Scénario d'utilisation 1. La commande a été passée avec succès mais le paiement n'a pas été effectué dans les 30 minutes. Le paiement a expiré et la commande a été automatiquement annulée 2. La commande a été signée et aucune évaluation n'a été effectuée pendant 7 jours après la signature. Si la commande expire et n'est pas évaluée, le système donne par défaut une note positive. 3. La commande est passée avec succès. Si le commerçant ne reçoit pas la commande pendant 5 minutes, la commande est annulée. 4. Le délai de livraison expire et. un rappel par SMS est envoyé... Pour les scénarios avec des délais longs et de faibles performances en temps réel, nous pouvons utiliser la planification des tâches pour effectuer un traitement d'interrogation régulier. Par exemple : xxl-job Aujourd'hui, nous allons choisir

1. Redis implémente le principe du verrouillage distribué et pourquoi les verrous distribués sont nécessaires. Avant de parler de verrous distribués, il est nécessaire d'expliquer pourquoi les verrous distribués sont nécessaires. Le contraire des verrous distribués est le verrouillage autonome. Lorsque nous écrivons des programmes multithreads, nous évitons les problèmes de données causés par l'utilisation d'une variable partagée en même temps. Nous utilisons généralement un verrou pour exclure mutuellement les variables partagées afin de garantir l'exactitude de celles-ci. les variables partagées. Son champ d’utilisation est dans le même processus. S’il existe plusieurs processus qui doivent exploiter une ressource partagée en même temps, comment peuvent-ils s’exclure mutuellement ? Les applications métier d'aujourd'hui sont généralement une architecture de microservices, ce qui signifie également qu'une application déploiera plusieurs processus si plusieurs processus doivent modifier la même ligne d'enregistrements dans MySQL, afin d'éviter les données sales causées par des opérations dans le désordre, les besoins de distribution. à introduire à ce moment-là. Le style est verrouillé. Vous voulez marquer des points

Springboot lit le fichier, mais ne peut pas accéder au dernier développement après l'avoir empaqueté dans un package jar. Il existe une situation dans laquelle Springboot ne peut pas lire le fichier après l'avoir empaqueté dans un package jar. La raison en est qu'après l'empaquetage, le chemin virtuel du fichier. n’est pas valide et n’est accessible que via le flux Read. Le fichier se trouve sous les ressources publicvoidtest(){Listnames=newArrayList();InputStreamReaderread=null;try{ClassPathResourceresource=newClassPathResource("name.txt");Input

SpringBoot et SpringMVC sont tous deux des frameworks couramment utilisés dans le développement Java, mais il existe des différences évidentes entre eux. Cet article explorera les fonctionnalités et les utilisations de ces deux frameworks et comparera leurs différences. Tout d’abord, découvrons SpringBoot. SpringBoot a été développé par l'équipe Pivotal pour simplifier la création et le déploiement d'applications basées sur le framework Spring. Il fournit un moyen rapide et léger de créer des fichiers exécutables autonomes.

Lorsque Springboot+Mybatis-plus n'utilise pas d'instructions SQL pour effectuer des opérations d'ajout de plusieurs tables, les problèmes que j'ai rencontrés sont décomposés en simulant la réflexion dans l'environnement de test : Créez un objet BrandDTO avec des paramètres pour simuler le passage des paramètres en arrière-plan. qu'il est extrêmement difficile d'effectuer des opérations multi-tables dans Mybatis-plus. Si vous n'utilisez pas d'outils tels que Mybatis-plus-join, vous pouvez uniquement configurer le fichier Mapper.xml correspondant et configurer le ResultMap malodorant et long, puis. écrivez l'instruction SQL correspondante Bien que cette méthode semble lourde, elle est très flexible et nous permet de

1. Personnalisez RedisTemplate1.1, mécanisme de sérialisation par défaut RedisAPI. L'implémentation du cache Redis basée sur l'API utilise le modèle RedisTemplate pour les opérations de mise en cache des données. Ici, ouvrez la classe RedisTemplate et affichez les informations sur le code source de la classe. Déclarer la clé, diverses méthodes de sérialisation de la valeur, la valeur initiale est vide @NullableprivateRedisSe

Dans les projets, certaines informations de configuration sont souvent nécessaires. Ces informations peuvent avoir des configurations différentes dans l'environnement de test et dans l'environnement de production, et peuvent devoir être modifiées ultérieurement en fonction des conditions commerciales réelles. Nous ne pouvons pas coder en dur ces configurations dans le code. Il est préférable de les écrire dans le fichier de configuration. Par exemple, vous pouvez écrire ces informations dans le fichier application.yml. Alors, comment obtenir ou utiliser cette adresse dans le code ? Il existe 2 méthodes. Méthode 1 : Nous pouvons obtenir la valeur correspondant à la clé dans le fichier de configuration (application.yml) via le ${key} annoté avec @Value. Cette méthode convient aux situations où il y a relativement peu de microservices. Méthode 2 : En réalité. projets, Quand les affaires sont compliquées, la logique
