[Diseño a partir del código fuente] Tarea periódica del intervalo de ajuste automático de Ant Financial SOFARegistry

[Diseño a partir del código fuente] Tarea periódica del intervalo de ajuste automático de Ant Financial SOFARegistry

0x00 resumen

SOFARegistry es un registro de servicios de nivel de producción, eficiente en el tiempo y altamente disponible de código abierto de Ant Financial.

Esta serie de artículos se centra en el análisis del diseño y la arquitectura, es decir, el uso de varios artículos para resumir de manera inversa el mecanismo de implementación y las ideas de arquitectura de DataServer o SOFARegistry desde múltiples perspectivas, para que todos puedan aprender a diseñar Ali.

Este artículo es el noveno capítulo y presenta la realización de la tarea periódica del intervalo de ajuste automático de SOFARegistry.

0x01 área de negocios

Las principales necesidades comerciales de Ant Financial aquí son:

  • Inicie una tarea de bucle sin fin y realice tareas de forma irregular;
  • Inicie una serie de tareas periódicas de retraso;
  • Algunas tareas periódicas necesitan realizar la función de intervalo de ajuste automático : una vez que el programa encuentra una excepción de tiempo de espera, el tiempo de intervalo aumentará. Si el tiempo de espera es continuo, el tiempo de intervalo se duplicará cada vez hasta que alcance el límite superior establecido por el parámetro externo. Hasta ahora, una vez que la nueva tarea ya no tiene una excepción de tiempo de espera, el tiempo de intervalo volverá automáticamente al valor inicial

0x02 solución Ali

Ali adoptó:

  • ExecutorService implementa tareas de bucle sin fin;
  • ScheduledExecutorService implementa tareas periódicas;
  • TimedSupervisorTask realiza la tarea periódica de ajustar automáticamente el intervalo;

Podemos referirnos a la implementación de TimedSupervisorTask al diseñar tareas retrasadas / periódicas

Programador 0x03

La clase Scheduler es la encarnación de este esquema.

Primero, necesitamos mirar el código del programador.

public class Scheduler {
    
    

    private final ScheduledExecutorService scheduler;
    public final ExecutorService           versionCheckExecutor;
    private final ThreadPoolExecutor       expireCheckExecutor;

    @Autowired
    private AcceptorStore                  localAcceptorStore;

    public Scheduler() {
    
    
        scheduler = new ScheduledThreadPoolExecutor(4, new NamedThreadFactory("SyncDataScheduler"));

        expireCheckExecutor = new ThreadPoolExecutor(1, 3, 0, TimeUnit.SECONDS,
            new SynchronousQueue<>(), new NamedThreadFactory("SyncDataScheduler-expireChangeCheck"));

        versionCheckExecutor = new ThreadPoolExecutor(2, 2, 0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<>(), new NamedThreadFactory(
                "SyncDataScheduler-versionChangeCheck"));

    }

    public void startScheduler() {
    
    
        scheduler.schedule(
                new TimedSupervisorTask("FetchDataLocal", scheduler, expireCheckExecutor, 3,
                        TimeUnit.SECONDS, 10, () -> localAcceptorStore.checkAcceptorsChangAndExpired()),
                30, TimeUnit.SECONDS);

        versionCheckExecutor.execute(() -> localAcceptorStore.changeDataCheck());
    }

    public void stopScheduler() {
    
    
        if (scheduler != null && !scheduler.isShutdown()) {
    
    
            scheduler.shutdown();
        }
        if (versionCheckExecutor != null && !versionCheckExecutor.isShutdown()) {
    
    
            versionCheckExecutor.shutdown();
        }
    }
}

A continuación, analizaremos su implementación o las opciones de diseño una por una.

0x04 tarea de bucle infinito

Ali aquí usa ExecutorService para lograr tareas de bucle infinito y completar negocios de manera irregular.

4.1 ExecutorService

Executor : Una interfaz JAVA que define un ejecutor de método que recibe un objeto Runnable, su firma de método es ejecutor (comando Runnable) Este método recibe una instancia de Runable para ejecutar una clase que implementa la interfaz Runnable.

ExecutorService : Es una interfaz de subclase que se usa más ampliamente que Executor.

Proporciona métodos para la gestión del ciclo de vida, la devolución de un objeto Future y un método que puede rastrear el estado de ejecución de una o más tareas asincrónicas y devolver un Future;

ExecutorService se cerrará cuando se ejecuten todas las tareas enviadas. Por lo tanto, generalmente usamos esta interfaz para implementar y administrar subprocesos múltiples.

Aunque ExecutorService no puede proporcionar funciones periódicas, localAcceptorStore.changeDataCheckes un ciclo while (verdadero) en sí mismo , que puede depender de DelayQueue para completar funciones periódicas similares .

versionCheckExecutor = new ThreadPoolExecutor(2, 2, 0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<>(), new NamedThreadFactory(
                "SyncDataScheduler-versionChangeCheck"));

versionCheckExecutor.execute(() -> localAcceptorStore.changeDataCheck());

public void changeDataCheck() {
    
    
        while (true) {
    
    
            try {
    
    
                DelayItem<Acceptor> delayItem = delayQueue.take();
                Acceptor acceptor = delayItem.getItem();
                removeCache(acceptor); // compare and remove
            } catch (InterruptedException e) {
    
    
                break;
            } catch (Throwable e) {
    
    
                LOGGER.error(e.getMessage(), e);
            }
        }
}

0x05 tarea periódica

Ali aquí usa ScheduledExecutorService para implementar tareas periódicas.

5.1 ScheduledExecutorService

ScheduledExecutorService es un grupo de subprocesos. ScheduledExecutorService agrega la función de retrasar y ejecutar tareas regularmente además de las funciones proporcionadas por ExecutorService.

Su método de programación crea tareas con varios retrasos y devuelve objetos de tarea que se pueden usar para cancelar o verificar la ejecución.

Un temporizador ordinario tiene solo un subproceso dentro, y si hay varias tareas, se ejecutará secuencialmente, por lo que nuestro tiempo de retardo y tiempo de ciclo será problemático, y el subproceso se abortará si no se marca la excepción.

ScheduledExecutorService es un grupo de subprocesos, y el grupo de subprocesos maneja las excepciones para que no haya impacto entre las tareas. Cuando existen requisitos estrictos para tareas retrasadas y tareas cíclicas, debe considerar el uso de ScheduledExecutorService.

0x06 Selección de cola

6.1 Cola de ThreadPoolExecutor 的

La firma del método de construcción completo de ThreadPoolExecutor es la siguiente

ThreadPoolExecutor
(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,RejectedExecutionHandler handler)12

Entre ellos, los parámetros workQueue se introducen de la siguiente manera:

workQueue task queue): una cola de bloqueo utilizada para almacenar tareas en espera de ser ejecutadas. Puede elegir las siguientes colas de bloqueo.

  • ArrayBlockingQueue: es una cola de bloqueo limitada basada en la estructura de la matriz , esta cola ordena los elementos de acuerdo con el principio FIFO (primero en entrar, primero en salir);
  • LinkedBlockingQueue: una cola de bloqueo basada en una estructura de lista enlazada , esta cola clasifica los elementos de acuerdo con FIFO (primero en entrar, primero en salir), y el rendimiento suele ser mayor que el de ArrayBlockingQueue. El método de fábrica estático Executors.newFixedThreadPool () usa esta cola;
  • SynchronousQueue: una cola de bloqueo que no almacena elementos . Cada operación de inserción debe esperar hasta que otro subproceso llame a la operación de eliminación, de lo contrario, la operación de inserción se ha bloqueado y el rendimiento suele ser mayor que LinkedBlockingQueue. El método de fábrica estático Executors.newCachedThreadPool usa esta cola;
  • PriorityBlockingQueue: una cola de bloqueo infinita con prioridad ;

6.2 Elección de registro SOFA

Aquí se utilizan dos colas.

expireCheckExecutor = new ThreadPoolExecutor(1, 3, 0, TimeUnit.SECONDS,
    new SynchronousQueue<>(), new NamedThreadFactory("SyncDataScheduler-expireChangeCheck"));

versionCheckExecutor = new ThreadPoolExecutor(2, 2, 0L, TimeUnit.MILLISECONDS,
    new LinkedBlockingQueue<>(), new NamedThreadFactory(
        "SyncDataScheduler-versionChangeCheck"));

6.3 LinkedBlockingQueue

LinkedBlockingQueue es una cola de bloqueo.

LinkedBlockingQueue implementa internamente la interfaz BlockingQueue mediante una lista enlazada individualmente , que solo puede obtener elementos del encabezado y agregar elementos del final.

LinkedBlockingQueue usa takeLock y putLock internamente para controlar la concurrencia, lo que significa que LinkedBlockingQueue está separado de lectura y escritura . Las operaciones de agregar y eliminar no son operaciones mutuamente excluyentes y se pueden realizar en paralelo, lo que puede mejorar en gran medida el rendimiento.

LinkedBlockingQueue es diferente de ArrayBlockingQueue. Si no especifica la capacidad, el valor predeterminado es Integer.MAX_VALUEuna cola ilimitada. Si hay un momento en el que la velocidad de adición es mayor que la velocidad de borrado, la memoria puede desbordarse. Por lo tanto, para evitar la situación de que la cola sea demasiado grande y haga que la máquina se cargue o la memoria esté llena, recomendamos pasar manualmente el tamaño de una cola cuando se usa.

Además, LinkedBlockingQueue proporciona una condición para que cada bloqueo suspenda y active otros subprocesos.

6.4 SynchronousQueue

A diferencia de ArrayBlockingQueue o LinkedListBlockingQueue, no hay espacio de búfer de datos dentro de SynchronousQueue.

No puede llamar al método peek () para ver si hay elementos de datos en la cola, porque los elementos de datos solo pueden existir cuando intenta quitarlos. No es bueno simplemente querer echar un vistazo sin quitarlos. Por supuesto, la operación de atravesar la cola tampoco está permitida. El encabezado del elemento de la cola es el primer hilo en la cola para insertar datos , no los datos que se intercambiarán.

Los datos se pasan directamente entre los subprocesos emparejados del productor y del consumidor, y los datos no se almacenan en la cola. Se puede entender así: los productores y los consumidores se esperan, se dan la mano y luego se van juntos .

Un escenario de uso de SynchronousQueue es en el grupo de subprocesos. Executors.newCachedThreadPool () usa SynchronousQueue. Este grupo de subprocesos crea nuevos subprocesos según sea necesario (cuando llegan nuevas tareas). Si hay subprocesos inactivos, se reutilizarán. Los subprocesos se reciclarán después de estar inactivos durante 60 segundos.

0x07 Tarea periódica con intervalo de ajuste automático

TimedSupervisorTask es una tarea periódica que ajusta automáticamente el intervalo. Esto se basa básicamente en la implementación de Eureka del mismo nombre, pero SOFA ha eliminado "parte de la lógica de manejo de excepciones" aquí .

En general, TimedSupervisorTask es una tarea periódica con un intervalo fijo. Una vez que encuentra un tiempo de espera, el intervalo del siguiente ciclo se incrementará. Si el tiempo de espera es continuo, el tiempo de intervalo se duplicará cada vez hasta que alcance el parámetro externo Hasta el límite superior especificado, una vez que la nueva tarea ya no se agote, el tiempo de intervalo volverá automáticamente al valor inicial, y también hay CAS para controlar la sincronización de múltiples subprocesos.

La lógica principal es la siguiente:

  • Ejecute el método submit () para enviar la tarea;
  • Ejecute el método future.get (), si el valor de retorno no se obtiene dentro del tiempo especificado o la tarea es anormal, ingrese el bloque de código de captura de manejo de excepciones;
  • Si no hay ninguna anomalía, vuelva a establecer el timeoutMillis de la tarea retrasada;
  • Si ocurre una excepción:
    • Cuando ocurre una TimeoutException, ejecute la Math.min(maxDelay, currentDelay x 2)tarea para obtener el valor mínimo del tiempo de retardo de la tarea x 2 y el tiempo de retardo máximo, y luego cambie el tiempo de retardo de la tarea timeoutMillis;
    • Cuando ocurre RejectedExecutionException, SOFA simplemente imprime el registro. Eureka rechazará el valor del contador +1;
    • Cuando ocurre una excepción Throwable, SOFA simplemente imprime el registro. Eureka arrojará un valor de contador +1;
  • Ingrese el bloque de código finalmente
    • Si future no es nulo, ejecute future.cancel (true), interrumpa el hilo para detener la tarea;
    • Si el grupo de subprocesos no se apaga, cree una nueva tarea cronometrada; lo más importante está en la última línea de código anterior: scheduler.schedule(this, delay.get(), TimeUnit.MILLISECONDS)Después de que se ejecute la tarea, se llamará nuevamente al método de programación y se ejecutará la misma tarea una vez después del tiempo especificado. Este intervalo El tiempo está relacionado con si la tarea más reciente ha agotado el tiempo de espera. Si se ha agotado el tiempo de espera, el intervalo será más largo;

Su implementación es la siguiente:

public class TimedSupervisorTask extends TimerTask {
    
    
    private final ScheduledExecutorService scheduler;
    private final ThreadPoolExecutor       executor;
    private final long                     timeoutMillis;
    private final Runnable                 task;
    private String                         name;
    private final AtomicLong               delay;
    private final long                     maxDelay;

    public TimedSupervisorTask(String name, ScheduledExecutorService scheduler,
                               ThreadPoolExecutor executor, int timeout, TimeUnit timeUnit,
                               int expBackOffBound, Runnable task) {
    
    
        this.name = name;
        this.scheduler = scheduler;
        this.executor = executor;
        this.timeoutMillis = timeUnit.toMillis(timeout);
        this.task = task;
        this.delay = new AtomicLong(timeoutMillis);
        this.maxDelay = timeoutMillis * expBackOffBound;

    }

    @Override
    public void run() {
    
    
        Future future = null;
        try {
    
    
            //使用Future,可以设定子线程的超时时间,这样当前线程就不用无限等待了
            future = executor.submit(task);
            //指定等待子线程的最长时间
            // block until done or timeout
            future.get(timeoutMillis, TimeUnit.MILLISECONDS);
            // 每次执行任务成功都会将delay重置
            delay.set(timeoutMillis);
        } catch (TimeoutException e) {
    
    

            long currentDelay = delay.get();
            // 如果出现异常,则将时间*2,然后取 定时时间 和 最长定时时间 中最小的为下次任务执行的延时时间
            long newDelay = Math.min(maxDelay, currentDelay * 2);
            // 设置为最新的值,考虑到多线程,所以用了CAS
            delay.compareAndSet(currentDelay, newDelay);

        } catch (RejectedExecutionException e) {
    
    
            // 线程池的阻塞队列中放满了待处理任务,触发了拒绝策略
            LOGGER.error("{} task supervisor rejected the task: {}", name, task, e);
        } catch (Throwable e) {
    
    
           // 出现未知的异常
            LOGGER.error("{} task supervisor threw an exception", name, e);
        } finally {
    
    
           //这里任务要么执行完毕,要么发生异常,都用cancel方法来清理任务;
            if (future != null) {
    
    
                future.cancel(true);
            }
            //这里就是周期性任务的原因:只要没有停止调度器,就再创建一次性任务,执行时间时dealy的值,
            //假设外部调用时传入的超时时间为30秒(构造方法的入参timeout),最大间隔时间为50秒(构造方法的入参expBackOffBound)
            //如果最近一次任务没有超时,那么就在30秒后开始新任务,
            //如果最近一次任务超时了,那么就在50秒后开始新任务(异常处理中有个乘以二的操作,乘以二后的60秒超过了最大间隔50秒)
            scheduler.schedule(this, delay.get(), TimeUnit.MILLISECONDS);
        }
    }
}

Referencia 0xFF

Serie Eureka (6) Análisis de clase de tarea TimedSupervisor

Clase TimedSupervisorTask de Eureka (tarea periódica con intervalo de ajuste automático)

Uso detallado de la clase ThreadPoolExecutor del grupo de subprocesos java

Análisis del principio de realización del grupo de subprocesos de Java ThreadPoolExecutor

Conocimiento profundo del grupo de subprocesos de Java: ThreadPoolExecutor

Investigación sobre el principio del grupo de subprocesos ThreadPoolExecutor en Java

Principio de implementación de SynchronousQueue en la concurrencia de Java

La diferencia entre ScheduledExecutorService y Timer

Principio de implementación de SynchronousQueue SynchronousQueue en Java Concurrent Package

Tres implementaciones del análisis del grupo de subprocesos ThreadPoolExecutor y BlockingQueue

[Hablar sobre la concurrencia de Java] Hablar sobre LinkedBlockingQueue

LinkedBlockingQueue de cola de bloqueo

★★★★★★ Pensando en la vida y la tecnología ★★★★★★

Cuenta pública de WeChat: el pensamiento de Rossi

Si desea recibir las noticias de los artículos escritos personalmente a tiempo, o desea ver la información técnica recomendada por las personas, puede escanear el código QR a continuación (o mantener presionado para identificar el código QR) y seguir su cuenta oficial personal).

Inserte la descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/weixin_47364682/article/details/111399874
Recomendado
Clasificación