Apprentissage d'introduction RocketMQ (5 * 2) analyse du code source du stockage persistant des messages

Quatre, résumé

L'interview a été posée: comment le courtier persiste-t-il après avoir reçu le message?

Répondant: Il existe deux façons: synchrone et asynchrone. Choisissez généralement asynchrone, l'efficacité synchrone est faible, mais plus fiable. Le principe général du stockage des messages est:

La classe principale MappedFile correspond à chaque fichier commitlog. MappedFileQueue équivaut à un dossier et gère tous les fichiers. Il y a aussi un objet CommitLog gestionnaire, qui est chargé de fournir certaines opérations. Plus précisément, une fois que le courtier a reçu le message, il stocke d'abord le message, la rubrique, la file d'attente, etc. dans le ByteBuffer, puis le persiste dans le fichier commitlog. La taille du fichier commitlog est 1G. Si la taille est dépassée, un nouveau fichier commitlog sera créé pour le stockage, en utilisant la méthode nio.

5. Supplément: brossage synchrone / asynchrone

1. Catégories clés

Nom du cours la description Performances de la brosse
CommitRealTimeService Disque flash asynchrone et tampon d'octets ouvert plus haut
FlushRealTimeService Disque flash asynchrone et fermer le tampon d'octets de mémoire Plus haut
GroupCommitService Clignotement synchrone, le message sera renvoyé après le flashage réussi du disque. le plus bas

2. Graphique

3. Brossage synchrone

3.1, code source

// {@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
    }
}

Cours

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. Résumé

Résumez le processus principal du brossage synchrone:

La classe principale est GroupCommitService et la méthode principale est waitForRunning.

  • Appelez d'abord la méthode putRequest pour changer hasNotified en true, et notifiez, c'est-à-dire waitPoint.countDown().

  • Suivi dans la méthode d'exécution waitForRunning(), waitForRunning()pour déterminer que hasNotified n'est pas vrai, il est vrai, puis retournez le swap pour échanger des données, c'est-à-dire ne pas attendre le retour direct de blocage.

  • Enfin, l'étape précédente revient, et il n'y a pas de blocage, alors il est logique d'appeler doCommit pour effectuer une véritable actualisation.

4. Brossage asynchrone

4.1, code source

La classe principale est: 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);

Cours

// {@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. Résumé

Méthode de la classe principale: FlushRealTimeService # run ()

  • Jugez si c'est flushCommitLogTimedvrai, la valeur par défaut est faux, si c'est vrai, mettez en veille (500 ms) directement puis flashez mappedFileQueue.flush()le disque.

  • S'il est faux, entrez waitForRunning(500). C'est la clé de la différence avec le clignotement synchrone. Avant le clignotement synchrone, hasNotified est changé en true, donc un ensemble de petites astuces est directement: Oui return+doCommit, asynchrone est appelé directement ici, et il n'y a waitForRunning(500)rien juste avant cela.L'opération de hasNotified, donc il ne reviendra pas, mais continuera waitPoint.await(500, TimeUnit.MILLISECONDS);à bloquer pendant 500 millisecondes, se réveillera automatiquement après 500 millisecondes, puis videra le disque. Autrement dit, si le disque est actualisé de manière asynchrone, la valeur par défaut est de 500 ms pour actualiser le disque une fois.

Je suppose que tu aimes

Origine blog.csdn.net/My_SweetXue/article/details/107381514
conseillé
Classement