Ceph 網絡模塊(4)——SimpleMessenger數據結構及代碼流程分析

Ceph 網絡模塊(4)——SimpleMessenger數據結構及代碼流程分析

這裏寫圖片描述

架構上採用 Publish/subscribe(發佈/訂閱) 的設計模式.

模塊說明:


Messenger
該類作爲消息的發佈者, 各個 Dispatcher 子類作爲消息的訂閱者, Messenger 收到消息之後,通過 Pipe 讀取消息,然後轉給 Dispatcher 處理

SimpleMessenger
Messenger 接口的實現

Dispatcher
該類是訂閱者的基類,具體的訂閱後端繼承該類,初始化的時候通過 Messenger::add_dispatcher_tail/head 註冊到 Messenger::dispatchers. 收到消息後,通知改類處理

Accepter
監聽 peer 的請求, 有新請求時, 調用 SimpleMessenger::add_accept_pipe() 創建新的 Pipe 到 SimpleMessenger::pipes 來處理該請求

Pipe
用於消息的讀取和發送,該類主要有兩個組件,Pipe::Reader 和 Pipe::Writer, 分別用來處理 消息的讀取和發送. 這兩個類都是 class Thread 的子類,意味這每次處理消息都會有兩個 線程被分別創建.

消息被 Pipe::Reader 讀取後,該線程會通知註冊到 Messenger::dispatchers 中的某一個 Dispatcher(如 Monitor) 處理, 處理完成之後將回復的消息放到 SimpleMessenger::Pipe::out_q 中,供 Pipe::Writer 來處理髮送

DispatchQueue
該類用來緩存收到的消息, 然後喚醒 DispatchQueue::dispatch_thread 線程找到後端的 Dispatch 處理消息

詳細解析:


這裏寫圖片描述

下面的代碼涉及到的訂閱子類以 Monitor 爲例:

初始化

int main(int argc, char *argv[])
{
    // 創建一個 Messenger 對象,由於 Messenger 是抽象類,不能直接實例化,提供了一個
    // ::create 的方法來創建子類,目前 Ceph 所有模塊使用 SimpleMessenger
    Messenger *messenger = Messenger::create(g_ceph_context,
                                             entity_name_t::MON(rank),
                                             "mon",
                                             0);

    /**
     * 執行 socket() -> bind() -> listen() 等一系列動作, 執行流程如下:
     SimpleMessenger::bind()
         --> Accepter::bind()
             socket() -> bind() -> listen()
    */
    err = messenger->bind(ipaddr);

    // 創建一個 Dispatch 的子類對象, 這裏是 Monitor
    mon = new Monitor(g_ceph_context, g_conf->name.get_id(), store, 
                      messenger, &monmap);

    // 啓動 Reaper 線程
    messenger->start();

    /**
     * a). 初始化 Monitor 模塊
     * b). 通過 SimpleMessenger::add_dispatcher_tail() 註冊自己到
     * SimpleMessenger::dispatchers 中, 流程如下:
     * Messenger::add_dispatcher_tail()
     *      --> ready()
     *        --> dispatch_queue.start()(新 DispatchQueue 線程)
              --> Accepter::start()(啓動start線程)
     *            --> accept
     *                --> SimpleMessenger::add_accept_pipe
     *                    --> Pipe::start_reader
     *                        --> Pipe::reader()
     * 在 ready() 中: 通過 Messenger::reader(),
     * 1) DispatchQueue 線程會被啓動,用於緩存收到的消息消息
     * 2) Accepter 線程啓動,開始監聽新的連接請求.
     */
    mon->init();

    // 進入 mainloop, 等待退出
    messenger->wait();
    return 0;
}

消息處理

收到連接請求

請求的監聽和處理由 SimpleMessenger::ready –> Accepter::entry 實現

void SimpleMessenger::ready()
{
    // 啓動 DispatchQueue 線程
    dispatch_queue.start();

    lock.Lock();
    // 啓動 Accepter 線程監聽客戶端連接, 見下面的 Accepter::entry
    if (did_bind)
        accepter.start();
    lock.Unlock();
}

void *Accepter::entry()
{
    struct pollfd pfd;
    // listen_sd 是 Accepter::bind() 中創建綁定的 socket
    pfd.fd = listen_sd;
    pfd.events = POLLIN | POLLERR | POLLNVAL | POLLHUP;
    while (!done) {
        int r = poll(&pfd, 1, -1);
        if (pfd.revents & (POLLERR | POLLNVAL | POLLHUP))
            break;
        if (done) break;
        entity_addr_t addr;
        socklen_t slen = sizeof(addr.ss_addr());
        int sd = ::accept(listen_sd, (sockaddr*)&addr.ss_addr(), &slen);
        if (sd >= 0) {
            // 調用 SimpleMessenger::add_accept_pipe() 處理這個連接
            msgr->add_accept_pipe(sd);
        } 
    }
    return 0;
}

隨後創建 Pipe() 開始消息的處理

Pipe *SimpleMessenger::add_accept_pipe(int sd)
{
    lock.Lock();
    Pipe *p = new Pipe(this, Pipe::STATE_ACCEPTING, NULL);
    p->sd = sd;
    p->pipe_lock.Lock();
    // 
    /**
     * 調用 Pipe::start_reader() 開始讀取消息, 將會創建一個讀線程開始處理.
     * Pipe::start_reader() --> Pipe::reader
     */
    p->start_reader();
    p->pipe_lock.Unlock();
    pipes.insert(p);
    accepting_pipes.insert(p);
    lock.Unlock();
    return p;
}

創建消息讀取和發送線程

處理消息由 Pipe::start_reader() –> Pipe::reader() 開始,此時已經是在 Reader 線程中. 首先會調用 accept() 做一些簡答的處理然後創建 Writer() 線程,等待發送回復 消息. 然後讀取消息, 讀取完成之後, 將收到的消息封裝在 Message 中,交由 dispatch_queue() 處理.

dispatch_queue() 找到註冊者,將消息轉交給它處理,處理完成喚醒 Writer() 線程發送回覆消息.

void Pipe::reader()
{
    /**
     * Pipe::accept() 會調用 Pipe::start_writer() 創建 wirter 線程, 進入 writer 線程
     * 後,會 cond.Wait() 等待被激活,激活的流程看下面的說明. Writer 線程的創建見後後面
     * Pipe::accept() 的分析
     */
    if (state == STATE_ACCEPTING) {
        accept();
    }

    while (state != STATE_CLOSED &&
           state != STATE_CONNECTING) {
        // 讀取消息類型,某些消息會馬上激活 writer 線程先處理
        if (tcp_read((char*)&tag, 1) < 0) {
            continue;
        }
        if (tag == CEPH_MSGR_TAG_KEEPALIVE) {
            continue;
        }
        if (tag == CEPH_MSGR_TAG_KEEPALIVE2) {
            continue;
        }
        if (tag == CEPH_MSGR_TAG_KEEPALIVE2_ACK) {
            continue;
        }
        if (tag == CEPH_MSGR_TAG_ACK) {
            continue;
        }
        else if (tag == CEPH_MSGR_TAG_MSG) {
            // 收到 MSG 消息
            Message *m = 0;
            // 將消息讀取到 new 到的 Message 對象
            int r = read_message(&m, auth_handler.get());

            // 先激活 writer 線程 ACK 這個消息
            cond.Signal();  // wake up writer, to ack this

            // 如果該次請求是可以延遲處理的請求,將 msg 放到 Pipe::DelayedDelivery::delay_queue, 
            // 後面通過相關模塊再處理
            // 注意,一般來講收到的消息分爲三類:
            // 1. 直接可以在 reader 線程中處理,如上面的 CEPH_MSGR_TAG_ACK
            // 2. 正常處理, 需要將消息放入 DispatchQueue 中,由後端註冊的消息處理,然後喚醒發送線程發送
            // 3. 延遲發送, 下面的這種消息, 由定時時間決定什麼時候發送
            if (delay_thread) {
                utime_t release;
                if (rand() % 10000 < msgr->cct->_conf->ms_inject_delay_probability * 10000.0) {
                    release = m->get_recv_stamp();
                    release += msgr->cct->_conf->ms_inject_delay_max * (double)(rand() % 10000) / 10000.0;
                    lsubdout(msgr->cct, ms, 1) << "queue_received will delay until " << release << " on " << m << " " << *m << dendl;
                }
                delay_thread->queue(release, m);
            } else {
// 正常處理的消息,放到 Pipe::DispatchQueue *in_q 中, 以下是整個消息的流程
// DispatchQueue::enqueue()
//     --> mqueue.enqueue() -> cond.Signal()(激活喚醒                         DispatchQueue::dispatch_thread 線程) 
//         --> DispatchQueue::dispatch_thread::entry() 該線程得到喚醒
//             --> Messenger::ms_deliver_XXX
//                 --> 具體的 Dispatch 實例, 如 Monitor::ms_dispatch()
//                     --> Messenger::send_message()
//                         --> SimpleMessenger::submit_message()
//                             --> Pipe::_send()
//                                 --> Pipe::out_q[].push_back(m) -> cond.Signal 激活 writer 線程
//                                     --> ::sendmsg()//發送到 socket
                in_q->enqueue(m, m->get_priority(), conn_id);
            }
        } 

        else if (tag == CEPH_MSGR_TAG_CLOSE) {
            cond.Signal();
            break;
        }
        else {
            ldout(msgr->cct,0) << "reader bad tag " << (int)tag << dendl;
            pipe_lock.Lock();
            fault(true);
        }
    }
}

Pipe::accept() 做一些簡單的協議檢查和認證處理,之後創建 Writer() 線程: Pipe::start_writer() –> Pipe::Writer

int Pipe::accept()
{
    ldout(msgr->cct,10) << "accept" << dendl;
    // 檢查自己和對方的協議版本等信息是否一致等操作
    // ......

    while (1) {
        // 協議檢查等操作
        // ......

        /**
         * 通知註冊者有新的 accept 請求過來,如果 Dispatcher 的子類有實現
         * Dispatcher::ms_handle_accept(),則會調用該方法處理
         */
        msgr->dispatch_queue.queue_accept(connection_state.get());

        // 發送 reply 和認證相關的消息
        // ......

        if (state != STATE_CLOSED) {
            /**
             * 前面的協議檢查,認證等都完成之後,開始創建 Writer() 線程等待註冊者
             * 處理完消息之後發送
             * 
             */
            start_writer();
        }
        ldout(msgr->cct,20) << "accept done" << dendl;

        /**
         * 如果該消息是延遲發送的消息, 且相關的發送線程沒有啓動,啓動之
         * Pipe::maybe_start_delay_thread()
         *     --> Pipe::DelayedDelivery::entry()
         */
        maybe_start_delay_thread();
        return 0;   // success.
    }
}

隨後 Writer 線程等待被喚醒發送回覆消息

void Pipe::writer()
{
    while (state != STATE_CLOSED) {// && state != STATE_WAIT) {
        if (state != STATE_CONNECTING && state != STATE_WAIT && state != STATE_STANDBY &&
            (is_queued() || in_seq > in_seq_acked)) {

            // 對 keepalive, keepalive2, ack 包的處理
            // ......

            // 從 Pipe::out_q 中得到一個取出包準備發送
            Message *m = _get_next_outgoing();
            if (m) {
                // 對包進行一些加密處理
                m->encode(features, !msgr->cct->_conf->ms_nocrc);

                // 包頭
                ceph_msg_header& header = m->get_header();
                ceph_msg_footer& footer = m->get_footer();

                // 取出要發送的二進制數據
                bufferlist blist = m->get_payload();
                blist.append(m->get_middle());
                blist.append(m->get_data());

                // 發送包: Pipe::write_message() --> Pipe::do_sendmsg --> ::sendmsg()
                ldout(msgr->cct,20) << "writer sending " << m->get_seq() << " " << m << dendl;
                int rc = write_message(header, footer, blist);
                m->put();
            }
            continue;
        }

        // 等待被 Reader 或者 Dispatcher 喚醒
        ldout(msgr->cct,20) << "writer sleeping" << dendl;
        cond.Wait(pipe_lock);
    }
}

消息的處理

Reader 線程將消息交給 dispatch_queue 處理,流程如下:

Pipe::reader() –> Pipe::in_q->enqueue()

void DispatchQueue::enqueue(Message *m, int priority, uint64_t id)
{
    Mutex::Locker l(lock);
    ldout(cct,20) << "queue " << m << " prio " << priority << dendl;
    add_arrival(m);
    // 將消息按優先級放入 DispatchQueue::mqueue 中
    if (priority >= CEPH_MSG_PRIO_LOW) {
        mqueue.enqueue_strict(
            id, priority, QueueItem(m));
    } else {
        mqueue.enqueue(
            id, priority, m->get_cost(), QueueItem(m));
    }
    // 喚醒 DispatchQueue::entry() 處理消息
    cond.Signal();
}

void DispatchQueue::entry()
{
    while (true) {
        while (!mqueue.empty()) {
            QueueItem qitem = mqueue.dequeue();
            Message *m = qitem.get_message();
            /**
             * 交給 Messenger::ms_deliver_dispatch() 處理,後者會找到
             * Monitor/OSD 等的 ms_deliver_dispatch() 開始對消息的邏輯處理
             * Messenger::ms_deliver_dispatch()
             *     --> Monitor::ms_dispatch()
             */
            msgr->ms_deliver_dispatch(m);
        }
        if (stop)
            break;

        // 等待被 DispatchQueue::enqueue() 喚醒
        cond.Wait(lock);
    }
    lock.Unlock();
}

下面簡單看一下在訂閱者的模塊中消息是怎樣被放入 Pipe::out_q 中的:

Messenger::ms_deliver_dispatch()
    --> Monitor::ms_dispatch()
        --> Monitor::_ms_dispatch
            --> Monitor::dispatch
                --> Monitor::handle_mon_get_map
                    --> Monitor::send_latest_monmap
                        --> SimpleMessenger::send_message()
                            --> SimpleMessenger::_send_message()
                                --> SimpleMessenger::submit_message()
                                    --> Pipe::_send()
bool Monitor::_ms_dispatch(Message *m)
{
    ret = dispatch(s, m, src_is_mon);

    if (s) {
        s->put();
    }

    return ret;
}

bool Monitor::dispatch(MonSession *s, Message *m, const bool src_is_mon)
{
    switch (m->get_type()) {
    case CEPH_MSG_MON_GET_MAP:
        handle_mon_get_map(static_cast<MMonGetMap*>(m));
        break;
    // ......
    default:
        ret = false;
    }
    return ret;
}

void Monitor::handle_mon_get_map(MMonGetMap *m)
{
    send_latest_monmap(m->get_connection().get());
    m->put();
}

void Monitor::send_latest_monmap(Connection *con)
{
    bufferlist bl;
    monmap->encode(bl, con->get_features());
    /**
     * SimpleMessenger::send_message()
     *     --> SimpleMessenger::_send_message()
     *         --> SimpleMessenger::submit_message()
     *             --> Pipe::_send()
     */
    messenger->send_message(new MMonMap(bl), con);
}

void Pipe::_send(Message *m)
{
    assert(pipe_lock.is_locked());
    out_q[m->get_priority()].push_back(m);
    // 喚醒 Writer 線程
    cond.Signal();
}

總結


由上面的所有分析,除了訂閱者/發佈者設計模式,對網絡包的處理上採用的是古老的 生產者消費者問題 線程模型,每次新的請求就會有創建一對收/發線程用來處理消息的接受 發送,如果有大規模的請求,線程的上下文切換會帶來大量的開銷,性能可能產生瓶頸。

不過在較新的 Ceph 版本中,新增加了兩種新的消息模型: AsyncMessenger 和 XioMessenger 讓 Ceph 消息處理得到改善.

本文轉載自:http://mathslinux.org/?p=664

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章