Aprendizaje introductorio de RocketMQ (5 * 2) análisis del código fuente del almacenamiento persistente de mensajes

Cuatro, resumen

A la entrevista se le preguntó: ¿Cómo persiste el Broker después de recibir el mensaje?

Demandado: Hay dos formas: sincrónica y asincrónica. En general, elija asincrónico, la eficiencia sincrónica es baja, pero más confiable. El principio general del almacenamiento de mensajes es:

La clase principal MappedFile corresponde a cada archivo de commitlog. MappedFileQueue es equivalente a una carpeta y administra todos los archivos. También hay un objeto administrador CommitLog, que se encarga de proporcionar algunas operaciones. Específicamente, después de que el Broker recibe el mensaje, primero almacena el mensaje, el tema, la cola, etc. en ByteBuffer y luego lo persiste en el archivo de registro de confirmación. El tamaño del archivo de registro de confirmación es 1G. Si se excede el tamaño, se creará un nuevo archivo de registro de confirmación para su almacenamiento, utilizando el método nio.

5. Suplemento: cepillado sincrónico / asincrónico

1. Categorías clave

Nombre de la clase descripción Rendimiento del cepillo
CommitRealTimeService Disco flash asíncrono y búfer de bytes abiertos mas alto
FlushRealTimeService Disco flash asíncrono y cierre del búfer de bytes de memoria Más alto
GroupCommitService Parpadeo sincrónico, el mensaje se devolverá después de actualizar el disco con éxito. más bajo

2. Gráfico

3. Cepillado sincrónico

3.1, código fuente

// {@link org.apache.rocketmq.store.CommitLog#submitFlushRequest()}
// Synchronization flush
if (FlushDiskType.SYNC_FLUSH == this.defaultMessageStore.getMessageStoreConfig().getFlushDiskType()) {
    // 同步刷盘service -> GroupCommitService
    final GroupCommitService service = (GroupCommitService) this.flushCommitLogService;
    if (messageExt.isWaitStoreMsgOK()) {
        // 数据准备
        GroupCommitRequest request = new GroupCommitRequest(result.getWroteOffset() + result.getWroteBytes(),
                                                 this.defaultMessageStore.getMessageStoreConfig().getSyncFlushTimeout());
        // 将数据对象放到requestsWrite里
        service.putRequest(request);
        return request.future();
    } else {
        service.wakeup();
        return CompletableFuture.completedFuture(PutMessageStatus.PUT_OK);
    }
}

putRequest

public synchronized void putRequest(final GroupCommitRequest request) {
    synchronized (this.requestsWrite) {
        this.requestsWrite.add(request);
    }
    // 这里很关键!!!,给他设置成true。然后计数器-1。下面run方法的时候才会进行交换数据且return
    if (hasNotified.compareAndSet(false, true)) {
        waitPoint.countDown(); // notify
    }
}

correr

public void run() {
    while (!this.isStopped()) {
        try {
            // 是同步还是异步的关键方法,也就是说组不阻塞全看这里。
            this.waitForRunning(10);
            // 真正的刷盘逻辑
            this.doCommit();
        } catch (Exception e) {
            CommitLog.log.warn(this.getServiceName() + " service has exception. ", e);
        }
    }
}

waitForRunning

protected volatile AtomicBoolean hasNotified = new AtomicBoolean(false);
// 其实就是CountDownLatch
protected final CountDownLatch2 waitPoint = new CountDownLatch2(1);

protected void waitForRunning(long interval) {
    // 如果是true,且给他改成false成功的话,则onWaitEnd()且return,但是默认是false,也就是默认情况下这个if不会进。
    if (hasNotified.compareAndSet(true, false)) {
        this.onWaitEnd();
        return;
    }

    //entry to wait
    waitPoint.reset();

    try {
        // 等待,默认值是1,也就是waitPoint.countDown()一次后就会激活这里。
        waitPoint.await(interval, TimeUnit.MILLISECONDS);
    } catch (InterruptedException e) {
        log.error("Interrupted", e);
    } finally {
        // 给状态值设置成false
        hasNotified.set(false);
        this.onWaitEnd();
    }
}

3.2. Resumen

Resuma el proceso principal del cepillado sincrónico:

La clase principal es GroupCommitService y el método principal es waitForRunning.

  • Primero llame al método putRequest para cambiar hasNotified a true, y notifique, eso es waitPoint.countDown().

  • Seguido en el método de ejecución waitForRunning(), waitForRunning()para determinar hasNotified no es cierto, es cierto, luego devuelve swap para intercambiar datos, es decir, no esperar el bloqueo del retorno directo.

  • Finalmente, vuelve el paso anterior, y no hay bloqueo, entonces es lógico llamar a doCommit para realizar una actualización real.

4. Cepillado asincrónico

4.1, código fuente

La clase principal es: FlushRealTimeService

// {@link org.apache.rocketmq.store.CommitLog#submitFlushRequest()}
// Asynchronous flush
if (!this.defaultMessageStore.getMessageStoreConfig().isTransientStorePoolEnable()) {
    flushCommitLogService.wakeup();
} else  {
    commitLogService.wakeup();
}
return CompletableFuture.completedFuture(PutMessageStatus.PUT_OK);

correr

// {@link org.apache.rocketmq.store.CommitLog.FlushRealTimeService#run()}

class FlushRealTimeService extends FlushCommitLogService {
    @Override
    public void run() {
        while (!this.isStopped()) {
            try {
    // 每隔500ms刷一次盘
                if (flushCommitLogTimed) {
                    Thread.sleep(500);
                } else {
                    // 根上面同步刷盘调用的是同一个方法,区别在于这里没有将hasNotified变为true,也就是还是默认的false,那么waitForRunning方法内部的第一个判断就不会走,就不会return掉,就会进行下面的await方法阻塞,默认阻塞时间是500毫秒。也就是默认500ms刷一次盘。
                    this.waitForRunning(500);
                }
                // 调用mappedFileQueue的flush方法
                CommitLog.this.mappedFileQueue.flush(flushPhysicQueueLeastPages);
            } catch (Throwable e) {
            }
        }
    }
}

4.2. Resumen

Método de clase principal #: FlushRealTimeService # run ()

  • Juzgue si flushCommitLogTimedes cierto, el valor predeterminado es falso, si es cierto, duerma (500ms) directamente y luego mappedFileQueue.flush()flashee el disco.

  • Si es falso, ingrese waitForRunning(500). Esta es la clave para la diferencia con el parpadeo sincrónico. Antes del parpadeo sincrónico, hasNotified se cambia a verdadero, por lo que un conjunto de pequeños trucos es directamente: Sí return+doCommit, asincrónico se llama directamente aquí, y no hay waitForRunning(500)nada justo antes de esto. La operación de hasNotified, por lo que no regresará, pero continuará bloqueándose waitPoint.await(500, TimeUnit.MILLISECONDS);durante 500 milisegundos, se activará automáticamente después de 500 milisegundos y luego vaciará el disco. Es decir, si el disco se actualiza de forma asincrónica, el valor predeterminado es 500 ms para actualizar el disco una vez.

Supongo que te gusta

Origin blog.csdn.net/My_SweetXue/article/details/107381514
Recomendado
Clasificación