ReplicationExecutor的实现原理

副本集在维护以及更新结构的过程中, 需要执行大量的任务, 这些任务的执行需要满足一定的条件, 例如, 先触发的时间先执行, 或者等待一段时间以后执行某个任务, 又或者有的任务需要全局锁, 有的只需要集合锁等等。 Mongodb 内部通过ReplicationExecutor来实现这些任务的执行。
从功能上面讲,ReplicationExecutor的作用和golang里面的channel是非常类似的, 都是一端负责添加任务, 另外一端负责处理最先收到的任务。

先来看一下replication executor的整体架构图:
这里写图片描述

ReplicationExecutor 继承自TaskExecutor, 它包含了4个主要的类来完成任务: Event, Callback, TaskRunner, OldThreadPool。 接下来分别介绍他们, 以及他们如何相互协作。

Event

Event 是一个时间生成, 通知等功能的类, 他的作用跟他的名字一样, 通知等待事件发生。在每一个节点中, 都有一个event list, 记录了所有的event。 每当我们要改变接点的状态, 就会发出这样的event, 并且通过event可以通知其他节点进行相应的更改。
每当收到一个event, 就要将所有等待该signal的task放进readyQueue里面。

void ReplicationExecutor::Event::_signal_inlock() {
    invariant(!_isSignaled);
    _isSignaled = true;

    if (!_waiters.empty()) {
        _executor->_readyQueue.splice(_executor->_readyQueue.end(), _waiters);
        _executor->_networkInterface->signalWorkAvailable();
    }

    _isSignaledCondition.notify_all();
}

Callback

每当有一个task被加入到某种类型的队列, 都会返回一个该任务的CallbackHandle, 通过它, 可以得到这个task的执行时的callback 函数, 以及这个task结束的event。

StatusWith<ReplicationExecutor::CallbackHandle> ReplicationExecutor::enqueueWork_inlock(
    WorkQueue* queue, const CallbackFn& callbackFn) {
    invariant(callbackFn);
    StatusWith<EventHandle> event = makeEvent_inlock();
    if (!event.isOK())
        return StatusWith<CallbackHandle>(event.getStatus());

    if (_freeQueue.empty())
        _freeQueue.push_front(WorkItem());
    const WorkQueue::iterator iter = _freeQueue.begin();
    WorkItem& work = *iter;

    invariant(!work.callback.isValid());
    setCallbackForHandle(&work.callback,
                         std::shared_ptr<executor::TaskExecutor::CallbackState>(
                             new Callback(this, callbackFn, iter, event.getValue())));

    work.generation++;
    work.finishedEvent = event.getValue();
    work.readyDate = Date_t();
    queue->splice(queue->end(), _freeQueue, iter);
    return StatusWith<CallbackHandle>(work.callback);
}

TaskRunner

这是一个任务的执行器, 它主要是处理DB相关的task 和exclusive task。它的工作流程如下:
这里写图片描述

ReplicationExecutor

这个类里面包含了多个对列:
_freeQueue:
已经执行的task, 放入该队列;

_readyQueue:
已经ready的任务, 通过ReplicationExecutor::scheduleWork 的任务;

_dbWorkInProgressQueue:
通过ReplicationExecutor::scheduleDBWork加入的任务;

_exclusiveLockInProgressQueue:
通过ReplicationExecutor::scheduleWorkWithGlobalExclusiveLock加入的任务;

_networkInProgressQueue:
通过ReplicationExecutor::scheduleRemoteCommand 加入的任务;

_sleepersQueue:
通过 ReplicationExecutor::scheduleWorkAt 加入的任务, 这个队列里面的任务是按照时间排序的, 新加入的任务也要保证时间的有序性。

每一个节点需要起一个叫做ReplicationExecutor的线程, 不停地通过getWork()得到下一个任务, 执行它, 并且发出该任务执行的信号。 这样的过程重复进行, 一直到所有的任务执行。
整个的逻辑大致如下:

void ReplicationExecutor::run() {
    setThreadName("ReplicationExecutor");
    _networkInterface->startup();
    _dblockWorkers.startThreads();
    std::pair<WorkItem, CallbackHandle> work;
    while ((work = getWork()).first.callback.isValid()) {
        {
            stdx::lock_guard<stdx::mutex> lk(_terribleExLockSyncMutex);
            const Callback* callback = _getCallbackFromHandle(work.first.callback);

            makeNoExcept(
                stdx::bind(callback->_callbackFn, CallbackArgs(this, work.second, inStatus)))();
        }
        signalEvent(work.first.finishedEvent);
    }
    finishShutdown();
    _networkInterface->shutdown();
}

这里, 比较复杂的是执行远端命令的处理, 其流程图如下:
这里写图片描述

这里, ReplicationExecutor 通过NetworkInterface来执行远端命令, 其执行的命令通过RemoteCommandRequest来指定, 执行之后, 通过RemoteCommandCompletionFn 来回调调用方。
NextworkInterface里面主要通过RemoteCommandRunner来处理网络事件, 他通过host:port得到来自ConnectionPool里面的一个ConnectPtr, 然后吧RemoteCommandRequest 变成一个Message的对象。
这里, 该节点需要调用其他的节点, 它自己就相当于一个client 被调用方是一个server, 所以他生成一个DBClientReplicasSet对象, 来调用目的端。当目的端返回, RemoteCommandCompletionFn被调用, 然后逐级返回, 到ReplicationExecutor, 本次remote command执行完成。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值