MongoDB連接池的實現

幾乎每一種數據庫都會有連接池, 來減少頻繁的創建刪除連接的開銷, 在MongoDB裏面是通過信號量線程同步方式來對創建、銷燬進行管理。

信號量基礎

int sem_init(sem_t *sem, int pshared, unsigned int value)

sem是要初始化的信號量,pshared表示此信號量是在進程間共享(=1)還是線程間共享(=0),value是信號量的初始值。

int sem_destroy(sem_t *sem);

其中sem是要銷燬的信號量。只有用sem_init初始化的信號量才能用sem_destroy銷燬。

int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

sem_wait:等待信號量,如果信號量的值大於0, 將信號量的值減1,立即返回。如果信號量的值爲0,則線程阻塞。相當於P操作。成功返回0,失敗返回-1。
sem_trywait:與sem_wait類似, 只是如果遞減不能立即執行,調用將返回錯誤(errno 設置爲EAGAIN)而不是阻塞。
sem_timewait:sem_wait() 類似,只不過 abs_timeout 指定一個阻塞的時間上限,如果調用因不能立即執行遞減而要阻塞。

int sem_post(sem_t *sem); 

釋放信號量,讓信號量的值加1。相當於V操作。

連接池的實現

使用TicketHoder來封裝類線程控制信號量函數:

TicketHolder::TicketHolder(int num) : _outof(num) {
    _check(sem_init(&_sem, 0, num));
}

TicketHolder::~TicketHolder() {
    _check(sem_destroy(&_sem));
}

bool TicketHolder::tryAcquire() {
    while (0 != sem_trywait(&_sem)) {
        switch (errno) {
            case EAGAIN:
                return false;
            case EINTR:
                break;
            default:
                _check(-1);
        }
    }
    return true;
}

void TicketHolder::waitForTicket() {
    while (0 != sem_wait(&_sem)) {
        switch (errno) {
            case EINTR:
                break;
            default:
                _check(-1);
        }
    }
}

void TicketHolder::release() {
    _check(sem_post(&_sem));
}

在MongoDB啓動的時候, 會先創建一個PortMessageServer,並在裏面指定一個有配置參數或者命令行參數指定的最大的連接數, 然後通過setupSockets創建一個socket並綁定, 並將其加入到Listener裏面的std::vector _socks;

static void _initAndListen(int listenPort) { 
    ...
    auto handler = std::make_shared<MyMessageHandler>();
    MessageServer* server = createServer(options, std::move(handler));
    server->setAsTimeTracker();

    if (!server->setupSockets()) {
        error() << "Failed to set up sockets during startup.";
        return;
    }
    ...
    server->run();
 }

MessagePortServer裏面, 通過Listener::initAndListen函數,最終在 PortMessageServer::accepted裏面來創建新的線程處理本次的操作。
這裏可以看到, 每一次新的操作, TicketHoder::tryAcquire會試圖進入信號量的代碼區, 如果信號量的連接數大於1, 就會獲得信號量鎖, 並且將連接數減少1; 如果此時的連接數爲0 , tryAcquire會返回失敗, 表明連接數已滿, 無法連接。

獲得信號量鎖之後, 會建立一下新的處理線程, 指定其處理函數爲:
PortMessageServer::handleIncomingMsg.

class PortMessageServer : public MessageServer, public Listener {
    virtual void accepted(std::shared_ptr<Socket> psocket, long long connectionId) {
        ScopeGuard sleepAfterClosingPort = MakeGuard(sleepmillis, 2);
        std::unique_ptr<MessagingPortWithHandler> portWithHandler(
            new MessagingPortWithHandler(psocket, _handler, connectionId));

        if (!Listener::globalTicketHolder.tryAcquire()) {
            log() << "connection refused because too many open connections: "
                  << Listener::globalTicketHolder.used() << endl;
            return;
        }

        {
                stdx::thread thr(stdx::bind(&handleIncomingMsg, portWithHandler.get()));
                thr.detach();
        }
  }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章