Cómo ejecutar tareas programadas distribuidas de múltiples nodos solo una vez

pregunta:

Cuando tenemos varios servidores y cada servidor tiene el mismo código de tarea programada, como insertar datos regularmente todas las mañanas. Si se ejecutan las tareas programadas en varios servidores, se producirá una duplicación de datos.
Solución: 1. Implementación @SchedulerLock 2. Bloqueo distribuido basado en Redis;

1. Implementación de @SchedulerLock;

La biblioteca Shedlock puede garantizar que sus tareas programadas se ejecuten como máximo una vez al mismo tiempo. Si una tarea se ejecuta en un nodo, adquiere un bloqueo para evitar que la misma tarea se ejecute desde otro nodo (o subproceso). Si una tarea ya se ejecutó en un nodo, la ejecución en otros nodos no espera, simplemente la omite.

Importar coordenadas:

  compile('net.javacrumbs.shedlock:shedlock-provider-jdbc-template:2.1.0')
   compile('net.javacrumbs.shedlock:shedlock-spring:2.2.0')
// Maven方式
   <dependency>
       <groupId>net.javacrumbs.shedlock</groupId>
       <artifactId>shedlock-spring</artifactId>
       <version>2.1.0</version>
   </dependency>
   <dependency>
       <groupId>net.javacrumbs.shedlock</groupId>
       <artifactId>shedlock-provider-jdbc-template</artifactId>
       <version>2.2.0</version>
   </dependency>

Cree una tabla de almacenamiento externo (shedlock) que proporcione bloqueos en la base de datos

CREATE TABLE shedlock(
    name VARCHAR(64) ,
    lock_until TIMESTAMP(3) NULL,
    locked_at TIMESTAMP(3) NULL,
    locked_by  VARCHAR(255),
    PRIMARY KEY (name)
)
Atributos ilustrar
nombre de bloqueo de nombre el nombre debe ser la clave principal
bloquear_hasta liberar el tiempo de bloqueo
bloqueado_en adquirir tiempo de bloqueo
bloqueado_por proveedor de bloqueo

//El nombre de bloqueo declarado por @SchedulerLock crea automáticamente el par clave-valor correspondiente y proporciona el bloqueo. @SchedulerLock(name = "scheduledTaskName") El valor del nombre corresponde a la asignación del nombre en la biblioteca.
El almacenamiento externo en forma de base de datos necesita crear una estructura de tabla, y las plantillas de almacenamiento externo no estructural, como redis, crearán automáticamente los pares clave-valor correspondientes y proporcionarán bloqueos de acuerdo con el nombre de bloqueo declarado por @SchedulerLock.

Clase de configuración:

@Component
public class ShedLockConfig {
    
    

    @Bean
    public LockProvider lockProvider(DataSource dataSource) {
    
    
        return new JdbcTemplateLockProvider(dataSource);
    }

    @Bean
    public ScheduledLockConfiguration scheduledLockConfiguration(LockProvider lockProvider) {
    
    
        return ScheduledLockConfigurationBuilder.withLockProvider(lockProvider)
                .withPoolSize(10)
                .withDefaultLockAtMostFor(Duration.ofMinutes(10))
                .build();
    }

}

Para habilitar SchedulerLock, use la anotación @EnableSchedulerLock para integrar la biblioteca en Spring.

import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;
// 开启定时任务注解
@EnableScheduling
// 开启定时任务锁,默认设置锁最大占用时间为30s
@EnableSchedulerLock(defaultLockAtMostFor = "PT30S")
@MapperScan(basePackages="cn.pilipa.finance.salary.persistence")
@ServletComponentScan
public class SalaryApplication {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run(SalaryApplication.class, args);
    }
}

Agregue la anotación @SchedulerLock a la tarea programada

@Scheduled(cron = "0 */15 * * * *")
@SchedulerLock(name = "scheduledTaskName")
public void scheduledTask() {
    
    
   // do something
}

Parámetro SchedulerLock

  • Solo los métodos anotados de @SchedulerLock
    están bloqueados, la biblioteca ignora todas las demás tareas programadas. También debe especificar el nombre del bloqueo. Solo se puede ejecutar una tarea a la vez.
  • name
    Nombre de bloqueo distribuido, tenga en cuenta que el nombre de bloqueo debe ser único.
  • lockAtMostFor & lockAtMostForString
    especifican cuánto tiempo se debe mantener el bloqueo en la muerte del nodo de ejecución. Esta es solo una opción alternativa, en circunstancias normales, el bloqueo se libera tan pronto como se completa la tarea. Debe establecer lockAtMostFor en un valor que tarde mucho más de lo normal en ejecutarse. Si la tarea lleva más tiempo que lockAtMostFor, el comportamiento resultante puede ser impredecible (más procesos mantendrán el bloqueo de manera efectiva).
    lockAtMostFor en milisegundos
    lockAtMostForString usando "PT14M" significa que estará bloqueado por no más de 14 minutos.
  • lockAtLeastFor & lockAtLeastForString
    Este atributo especifica el tiempo mínimo que se debe mantener el bloqueo. Su objetivo principal es evitar la ejecución desde múltiples nodos si la tarea es corta y los relojes entre nodos son diferentes.
@Scheduled(fixedDelay = 1000*60*10)
@SchedulerLock(name = "queryRechargeBill", lockAtMostFor = 1000*60*15, lockAtLeastFor = 1000*60*5)
public void queryRechargeBill(){
    
    
    // do something
}

El bloqueo se mantendrá durante 5 minutos y se liberará en minutos 5. Cuando el nodo es anormal o muere, el bloqueo se liberará automáticamente después de 15 minutos de forma predeterminada. En circunstancias normales, ShedLock libera el bloqueo tan pronto como se completa la tarea. En realidad, no tenemos que hacer esto porque el valor predeterminado se proporciona en @EnableSchedulerLock, pero elegimos anularlo aquí.

Enlace de referencia 1: https://www.jianshu.com/p/94a0378798e1/
Enlace de referencia 2: https://blog.csdn.net/wang20010104/article/details/127730997
Enlace de referencia 3: https://blog.csdn .net/baidu_41634343/article/detalles/105660941

2. Bloqueo distribuido basado en Redis;

(1) Cada servidor genera un uuid aleatorio
(2) Use el comando Setnx de redis para almacenar el uuid aleatorio como un valor en redis
(3) Obtenga el valor de redis y compárelo con el uuid aleatorio generado;
(4) El valor coincide con el uuid aleatorio generado Si no coincide, no se ejecutará;
la tarea actual adquiere el bloqueo, si se adquiere el bloqueo, se ejecutará la tarea, si no se obtiene el bloqueo, entonces no se hará nada.

@Configuration
@Slf4j
public class ReportSchedule {
    
    

    @Autowired
    private RedissonClient redissonClient;

    @Value("${server.port}")
    private String port;

    private static final String LOCK = "lock";

    @Scheduled(cron = "*/5 * * * * ?")
    public void reportTask() throws Exception {
    
    
        RLock lock = redissonClient.getLock(LOCK);
        if (lock.tryLock()) {
    
    
            try {
    
    
                String hostAddress = InetAddress.getLocalHost().getHostAddress();
                log.info("ip:{},端口:{},在执行上报任务......", hostAddress, port);
                log.error("mark");
            } finally {
    
    
                lock.unlock();
            }
        }
    }
}

Enlace de referencia: https://blog.csdn.net/qq_34125999/article/details/126525626 (Hay dos métodos de mejora avanzados)

Supongo que te gusta

Origin blog.csdn.net/m0_46459413/article/details/130106036
Recomendado
Clasificación