集羣管理選舉算法實現

一個分佈式服務集羣管理一般須要一個協調服務,提供服務註冊、服務發現、配置管理、組服務等功能,而協調服務自身應是一個高可用的服務集羣,ZooKeeper是普遍應用且衆所周知的協調服務。協調服務自身的高可用須要選舉算法來支撐,本文將講述選舉原理並以分佈式服務集羣NebulaBootstrap的協調服務NebulaBeacon爲例詳細說明協調服務的選舉實現。node

  爲何要選NebulaBeacon來講明協調服務的選舉實現?一方面是我沒有讀過Zookeeper的代碼,更重要的另外一方面是NebulaBeacon的選舉實現只有兩百多行代碼,簡單精煉,很容易講清楚。基於高性能C++網絡框架Nebula實現的分佈式服務集羣NebulaBootstrap是一種用C++快速構建高性能分佈式服務的解決方案。git

  爲何要實現本身的協調服務而不直接用Zookeeper?想造個C++的輪子,整個集羣都是C++服務,由於選了ZooKeeper而須要部署一套Java環境,配置也跟其餘服務不是一個體系,實在不是一個好的選擇。Spring Cloud有Eureka,NebulaBootstrap有NebulaBeacon,將來NebulaBootstrap會支持ZooKeeper,不過暫無時間表,仍是首推NebulaBeacon。github

1. 選舉算法選擇

  Paxos算法 和 ZooKeeper ZAB協議 是兩種較廣爲人知的選舉算法。ZAB協議主要用於構建一個高可用的分佈式數據主備系統,例如ZooKeeper,而Paxos算法則是用於構建一個分佈式的一致性狀態機系統。也有不少應用程序採用本身設計的簡單的選舉算法,這類型簡單的選舉算法一般依賴計算機自身因素做爲選舉因子,好比IP地址、CPU核數、內存大小、自定義序列號等。算法

  Paxos規定了四種角色(Proposer,Acceptor,Learner,以及Client)和兩個階段(Promise和Accept)。   ZAB服務具備四種狀態:LOOKING、FOLLOWING、LEADING、OBSERVING。   NebulaBeacon是高可用分佈式系統的協調服務,採用ZAP協議更爲合適,不過ZAP協議仍是稍顯複雜了,NebulaBeacon的選舉算法實現基於節點的IP地址標識,選舉速度快,實現十分簡單。json

2. 選舉相關數據結構

  NebulaBeacon的選舉相關數據結構很是簡單:網絡

const uint32 SessionOnlineNodes::mc_uiLeader = 0x80000000;   ///< uint32最高位爲1表示leader
const uint32 SessionOnlineNodes::mc_uiAlive  = 0x00000007;   ///< 最近三次心跳任意一次成功則認爲在線
std::map<std::string, uint32> m_mapBeacon;                   ///< Key爲節點標識,值爲在線心跳及是否爲leader標識

  如上數據結構m_mapBeacon保存了Beacon集羣各Beacon節點信息,以Beacon節點的IP地址標識爲key排序,每次遍歷均從頭開始,知足條件(1&&2 或者 1&&3)則標識爲Leader:1. 節點在線;2. 已經成爲Leader; 3. 整個列表中不存在在線的Leader,而節點處於在線節點列表的首位。數據結構

3. Beacon選舉流程

  Beacon選舉基於節點IP地址標識,實現很是簡單且高效。框架

"beacon":["192.168.1.11:16000", "192.168.1.12:16000"]

  進程啓動時首先檢查Beacon集羣配置,若未配置其餘Beacon節點信息,則默認只有一個Beacon節點,此時該節點在啓動時自動成爲Leader節點。不然,向其餘Beacon節點發送一個心跳消息,等待定時器回調檢查並選舉出Leader節點。選舉流程以下圖:分佈式

Beacon選舉流程

  檢查是否在線就是經過檢查兩次定時器回調之間是否收到了其餘Beacon節點的心跳消息。對m_mapBeacon的遍歷檢查判斷節點在線狀況,對已離線的Leader節點置爲離線狀態,若當前節點應成爲Leader節點則成爲Leader節點。ide

4. Beacon節點間選舉通訊

  Beacon節點間的選舉通訊與節點心跳合爲一體,這樣作的好處是當leader節點不可用時,fllower節點馬上能夠成爲leader節點,選舉過程只需每一個fllower節點遍歷本身內存中各Beacon節點的心跳信息便可,無須在發現leader不在線才發起選舉,更快和更好地保障集羣的高可用性。

Beacon間心跳

  Beacon節點心跳信息帶上了leader節點做爲協調服務產生的新數據,fllower節點在接收心跳的同時完成了數據同步,保障任意一個fllower成爲leader時已得到集羣全部需協調的信息並可隨時切換爲leader。除定時器觸發的心跳帶上協調服務產生的新數據以外,leader節點產生新數據的同時會馬上向fllower發送心跳。

5. Beacon選舉實現

  Beacon心跳協議proto:

/**
 * @brief BEACON節點間心跳
 */message Election
{
    int32 is_leader                  = 1;    ///< 是否主節點
    uint32 last_node_id              = 2;    ///< 上一個生成的節點ID
    repeated uint32 added_node_id    = 3;    ///< 新增已使用的節點ID
    repeated uint32 removed_node_id  = 4;    ///< 刪除已廢棄的節點ID
}

  檢查Beacon配置,若只有一個Beacon節點則自動成爲Leader:

void SessionOnlineNodes::InitElection(const neb::CJsonObject& oBeacon)
{
    neb::CJsonObject oBeaconList = oBeacon;    
    for (int i = 0; i < oBeaconList.GetArraySize(); ++i)
    {
        m_mapBeacon.insert(std::make_pair(oBeaconList(i) + ".1", 0));
    }    if (m_mapBeacon.size() == 0)
    {
        m_bIsLeader = true;
    }    else if (m_mapBeacon.size() == 1
            && GetNodeIdentify() == m_mapBeacon.begin()->first)
    {
        m_bIsLeader = true;
    }    else
    {
        SendBeaconBeat();
    }
}

  發送Beacon心跳:

void SessionOnlineNodes::SendBeaconBeat()
{
    LOG4_TRACE("");
    MsgBody oMsgBody;
    Election oElection;    
    if (m_bIsLeader)
    {
        oElection.set_is_leader(1);
        oElection.set_last_node_id(m_unLastNodeId);        
        for (auto it = m_setAddedNodeId.begin(); it != m_setAddedNodeId.end(); ++it)
        {
            oElection.add_added_node_id(*it);
        }        
        for (auto it = m_setRemovedNodeId.begin(); it != m_setRemovedNodeId.end(); ++it)
        {
            oElection.add_removed_node_id(*it);
        }
    }    
    else
    {
        oElection.set_is_leader(0);
    }
    m_setAddedNodeId.clear();
    m_setRemovedNodeId.clear();
    oMsgBody.set_data(oElection.SerializeAsString());    
    for (auto iter = m_mapBeacon.begin(); iter != m_mapBeacon.end(); ++iter)
    {        
        if (GetNodeIdentify() != iter->first)
        {
            SendTo(iter->first, neb::CMD_REQ_LEADER_ELECTION, GetSequence(), oMsgBody);
        }
    }
}

  接收Beacon心跳:

void SessionOnlineNodes::AddBeaconBeat(const std::string& strNodeIdentify, const Election& oElection)
{    
    if (!m_bIsLeader)
    {        
        if (oElection.last_node_id() > 0)
        {
            m_unLastNodeId = oElection.last_node_id();
        }        
        for (int32 i = 0; i < oElection.added_node_id_size(); ++i)
        {
            m_setNodeId.insert(oElection.added_node_id(i));
        }        
        for (int32 j = 0; j < oElection.removed_node_id_size(); ++j)
        {
            m_setNodeId.erase(m_setNodeId.find(oElection.removed_node_id(j)));
        }
    }    
    auto iter = m_mapBeacon.find(strNodeIdentify);    
    if (iter == m_mapBeacon.end())
    {
        uint32 uiBeaconAttr = 1;        
        if (oElection.is_leader() != 0)
        {
            uiBeaconAttr |= mc_uiLeader;
        }
        m_mapBeacon.insert(std::make_pair(strNodeIdentify, uiBeaconAttr));
    }    
    else
    {
        iter->second |= 1;        
        if (oElection.is_leader() != 0)
        {
            iter->second |= mc_uiLeader;
        }
    }
}

  檢查在線leader,成爲leader:

void SessionOnlineNodes::CheckLeader()
{
    LOG4_TRACE("");    
    std::string strLeader;    
    for (auto iter = m_mapBeacon.begin(); iter != m_mapBeacon.end(); ++iter)
    {        
        if (mc_uiAlive & iter->second)
        {            
            if (mc_uiLeader & iter->second)
            {
                strLeader = iter->first;
            }            
            else if (strLeader.size() == 0)
            {
                strLeader = iter->first;
            }
        }        
        else
        {
            iter->second &= (~mc_uiLeader);
        }
        uint32 uiLeaderBit = mc_uiLeader & iter->second;
        iter->second = ((iter->second << 1) & mc_uiAlive) | uiLeaderBit;        
        if (iter->first == GetNodeIdentify())
        {
            iter->second |= 1;
        }
    }    
    if (strLeader == GetNodeIdentify())
    {
        m_bIsLeader = true;
    }
}

6. Beacon節點切換leader

  經過Nebula集羣的命令行管理工具nebcli能夠很方便的查看Beacon節點狀態,nebcli的使用說明見Nebcli項目的README。下面啓動三個Beacon節點,並反覆kill掉Beacon進程和重啓,查看leader節點的切換狀況。

  啓動三個beacon節點:

nebcli): show beacon
node                        is_leader       is_online
192.168.157.176:16000.1     yes             yes
192.168.157.176:17000.1     no              yes
192.168.157.176:18000.1     no              yes

  kill掉leader節點:

nebcli): show beacon
node                        is_leader       is_online
192.168.157.176:16000.1     no              no
192.168.157.176:17000.1     yes             yes
192.168.157.176:18000.1     no              yes

  kill掉fllower節點:

nebcli): show beacon
node                        is_leader       is_online
192.168.157.176:16000.1     no              no
192.168.157.176:17000.1     yes             yes
192.168.157.176:18000.1     no              no

  重啓被kill掉的兩個節點:

nebcli): show beacon
node                        is_leader       is_online
192.168.157.176:16000.1     no              yes
192.168.157.176:17000.1     yes             yes
192.168.157.176:18000.1     no              yes

  fllower節點在原leader節點不可用後成爲leader節點,且只要不宕機則一直會是leader節點,即便原leader節點從新變爲可用狀態也不會再次切換。

7. 結束

  開發Nebula框架目的是致力於提供一種基於C++快速構建高性能的分佈式服務。若是以爲本文對你有用,別忘了到Nebula的Github碼雲給個star,謝謝。

相關文章
相關標籤/搜索