亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

分布式選主 -- 利用Mysql ACID和Lease協(xié)議實(shí)現(xiàn)

系統(tǒng) 2369 0

? ? ? 在實(shí)際生產(chǎn)開發(fā)中,遇到一些多節(jié)點(diǎn)共存,需要選主,并且要實(shí)現(xiàn)HA自動(dòng)容錯(cuò)的場景,思考了寫方法拿出來和大家分享一下。

?

  1. Lease協(xié)議,Mysql ACID
  2. 高可用選主方案設(shè)計(jì)
  3. 適用場景
  4. Java語言實(shí)現(xiàn)描述
  5. 進(jìn)一步優(yōu)化

? ? ? 系統(tǒng)中有很多應(yīng)用場景要類似主從架構(gòu),主服務(wù)器(Master)對(duì)外提供服務(wù),從服務(wù)器(Salve)熱備份,不提供服務(wù)但隨時(shí)活著,如果Master出現(xiàn)宕機(jī)或者網(wǎng)絡(luò)問題,Slave即可接替Master對(duì)外服務(wù),并由Slave提升為Master(新主)。典型的多節(jié)點(diǎn)共存, 但只能同時(shí)存在一個(gè)主,并且所有節(jié)點(diǎn)的狀態(tài)能統(tǒng)一維護(hù)

? ? ? 大家一定首先想到了著名的Paxos算法( http://baike.baidu.com/view/8438269.htm )。簡單的說,Paxos通過每個(gè)節(jié)點(diǎn)的投票算法,來決議一個(gè)事情,當(dāng)多余1/2個(gè)節(jié)點(diǎn)都投票通過時(shí),Paxos產(chǎn)生一個(gè)唯一結(jié)果的決議,并通知各個(gè)節(jié)點(diǎn)維護(hù)這個(gè)信息。例如Paxos的選主,首先產(chǎn)生一個(gè)關(guān)于某個(gè)節(jié)點(diǎn)希望當(dāng)Master的投票,然后各個(gè)節(jié)點(diǎn)給出反饋,最終Paxos集群維護(hù)唯一的Master的結(jié)論。Zookeeper就是Paxos的一種實(shí)現(xiàn)。這種場景最適合用zookeeper來選主, 但zookeeper有個(gè)明顯的缺點(diǎn),當(dāng)存活的節(jié)點(diǎn)小于zookeeper集群的1/2時(shí),就不能工作了。比如zk有10各節(jié)點(diǎn),那么必須滿足可用的節(jié)點(diǎn)大于5才可。

? ? ? 在實(shí)際環(huán)境中,如果對(duì)Master要求不是那么嚴(yán)格的話,可以通過某些改進(jìn)和取舍來達(dá)到目的。比如可能在秒級(jí)別允許Master暫時(shí)不能訪問、選主時(shí)間內(nèi)可能存在一定的沖突但通過再次選主即可。本人設(shè)計(jì)了一個(gè)簡易的利用Mysql一致性和簡易版Lease來workaround。

Mysql ACID保證了一條數(shù)據(jù)記錄的一致性、完整性,不會(huì)出現(xiàn)多進(jìn)程讀寫的一致性問題和唯一正確性。Lease協(xié)議(協(xié)議細(xì)節(jié)可以Google之)通過向Master發(fā)送一個(gè)lease(租期)包,Master在這個(gè)lease期之內(nèi)充當(dāng)主角色,如果lease期到了則再次去申請(qǐng)lease,如果lease期到了,但是網(wǎng)絡(luò)除了問題,這時(shí)Master可以i主動(dòng)下線,讓其他節(jié)點(diǎn)去競選Master。舉個(gè)例子,三個(gè)節(jié)點(diǎn)A、B、C經(jīng)過第一輪選主之后,A成為Master,它獲得了10秒的lease,當(dāng)前時(shí)間假設(shè)是00:00:00,那么它Master地位可以用到00:00:10,當(dāng)時(shí)間到達(dá)00:00:10時(shí),A、B、C會(huì)重新進(jìn)行Master選舉,每個(gè)節(jié)點(diǎn)都有可能成為Master(從工程的角度觸發(fā),A繼續(xù)為Master的概率更大),如果這時(shí)候A的網(wǎng)絡(luò)斷了,不能聯(lián)通B、C的集群了, 那么A會(huì)自動(dòng)下線,不會(huì)去競爭,這樣就不會(huì)出現(xiàn)“腦裂”的現(xiàn)象。

? ? ??

? ? ? ?---------------------------------------------- 華麗的分割線 ----------------------------------------------

? ? ??

? ? ? ? 設(shè)計(jì)方案如下:(server代表集群中的一臺(tái)機(jī)器,也可看作一個(gè)進(jìn)程,server之間是平等的)

?

  1. 各個(gè)server之間用ntpserver時(shí)間同步(保證服務(wù)器之間秒級(jí)同步即可)
  2. 各個(gè)server持有一個(gè)唯一ID號(hào)(ip+進(jìn)程號(hào)),通過此id唯一標(biāo)識(shí)一個(gè)server實(shí)例
  3. 各個(gè)server定義一個(gè)lease租期,單位為秒
  4. Mysql唯一表唯一一條記錄維護(hù)全局Master的信息,ACID保證一致性
  5. Master Server每半個(gè)lease期向Mysql更新如上的唯一一條記錄,并更新心跳,維護(hù)Master狀態(tài)
  6. Slaver Server每半個(gè)lease周期從mysql獲取Master Server信息,如果數(shù)據(jù)庫中Master的Lease超過了當(dāng)前時(shí)間(heartbeat_time+ lease > current_time),則申請(qǐng)當(dāng)Master。

?

? ? ? 這其中比較棘手的問題是:

? ? ? ? 1、由于數(shù)據(jù)庫訪問和休眠的時(shí)間(lease的一半),有時(shí)延的存在,要處理Mysql異常、網(wǎng)絡(luò)異常。

? ? ? ? 2、可能存在同時(shí)搶占Master的server,這個(gè)時(shí)候就需要一個(gè)驗(yàn)證機(jī)制保證為搶到Master的server自動(dòng)退位為Slaver


? ? ? 下面給出圖實(shí)例 :(10.0.0.1為Master)

分布式選主 -- 利用Mysql ACID和Lease協(xié)議實(shí)現(xiàn)選主和高可用

? ? ?10.0.0.1 crash了。mysql中維護(hù)的10.0.0.1的主信息已過期,其他節(jié)點(diǎn)去搶占

分布式選主 -- 利用Mysql ACID和Lease協(xié)議實(shí)現(xiàn)選主和高可用


? ? ? 各個(gè)節(jié)點(diǎn)再次讀取數(shù)據(jù)庫,查看是否是自己搶占成功了:

分布式選主 -- 利用Mysql ACID和Lease協(xié)議實(shí)現(xiàn)選主和高可用


之后,10.0.0.3作為Master對(duì)外服務(wù)。此時(shí)如果10.0.0.1重啟,可作為Slaver。如果10.0.0.1因?yàn)榫W(wǎng)絡(luò)分化或者網(wǎng)絡(luò)異常而不能維護(hù)心跳,則在超過自身lease時(shí)自動(dòng)停止服務(wù),不會(huì)出現(xiàn)“雙Master”的現(xiàn)象。


? ? ? 每個(gè)Server遵循如下流程:

分布式選主 -- 利用Mysql ACID和Lease協(xié)議實(shí)現(xiàn)選主和高可用


? ? ? ? 數(shù)據(jù)庫設(shè)計(jì):

分布式選主 -- 利用Mysql ACID和Lease協(xié)議實(shí)現(xiàn)選主和高可用

? ? ? ? 某一時(shí)刻,數(shù)據(jù)庫中Master的信息:

? 分布式選主 -- 利用Mysql ACID和Lease協(xié)議實(shí)現(xiàn)選主和高可用

?

? ? ? ?當(dāng)前時(shí)間: 45分15秒

? ? ? ?當(dāng)前Master Lease :6秒

? ? ? ?當(dāng)前Master Lease可用到: 45分21秒

??

? ? ? ?---------------------------------------------- 華麗的分割線?----------------------------------------------

? ? ? ?3、適用的場景

? ? ? ? 一 、生命周期內(nèi)可使用Mysql、并且各個(gè)server之間時(shí)間同步。

? ? ? ? 二、需要集群中選出唯一主對(duì)外提供服務(wù),其他節(jié)點(diǎn)作為slaver做standby,主lease過期時(shí)競爭為Master

? ? ? ? 三、對(duì)比zookeeper,可滿足如果集群掛掉一半節(jié)點(diǎn),也可正常工作的情況,比如只有一主一備。

? ? ? ? 四、允許選主操作在秒級(jí)容錯(cuò)的系統(tǒng),選主的時(shí)候可能有l(wèi)ease/2秒的時(shí)間窗口,此時(shí)服務(wù)可能不可用。

?

? ? ? ? 五、允許lease/2秒內(nèi)出現(xiàn)極限雙Master情況,但是概率很小。


? ? ? ? ---------------------------------------------- 華麗的分割線?----------------------------------------------

? ? ? ? 4、Java語言實(shí)現(xiàn)描述

?

          一些配置信息和時(shí)間相關(guān)、休眠周期相關(guān)的時(shí)間變量
  
            final long interval = lease / intervalDivisor;

        long waitForLeaseChallenging = 0L; 

        lease = lease / 1000L;



        long challengeFailTimes = 0L; 

        long takeRest = 0L; 

        long dbExceptionTimes = 0L; 

        long offlineTime = 0L; 

        Random rand = new Random();

        Status stateMechine = Status.START;



        long activeNodeLease = 0L; 

        long activeNodeTimeStamp = 0L; 
  


? ? ? ? 數(shù)據(jù)庫異常的處理:

?

?

                KeepAlive keepaliveNode = null;

            try {

                /* first of all get it from mysql */

                keepaliveNode = dbService.accquireAliveNode();

                if (stateMechine != Status.START && keepaliveNode==null)

                    throw new Exception();

                // recount , avoid network shake

                dbExceptionTimes = 0L;

            } catch (Exception e) {

                log.fatal("[Scanner] Database Exception with times : " + dbExceptionTimes++);

                if (stateMechine == Status.OFFLINE) {

                    log.warn("[Scanner] Database Exception , OFFLINE ");

                } else if (dbExceptionTimes >= 3) {

                    log.fatal("[Scanner] Database Exception , Node Offline Mode Active , uniqueid : " + uniqueID);

                    stateMechine = Status.OFFLINE;

                    dbExceptionTimes = 0L;

                    offlineTime = System.currentTimeMillis();

                    online = false;

                } else

                    continue;

            }
  


? ? ? ? 總的循環(huán)和狀態(tài)機(jī)的變遷:

?

?

            while (true) {



            SqlSession session = dbConnecction.openSession();

            ActionScanMapper dbService = session.getMapper(ActionScanMapper.class);



            KeepAlive keepaliveNode = null;

            try {

                /* first of all get it from mysql */

                keepaliveNode = dbService.accquireAliveNode();

                if (stateMechine != Status.START && keepaliveNode==null)

                    throw new Exception();

                // recount , avoid network shake

                dbExceptionTimes = 0L;

            } catch (Exception e) {

                log.fatal("[Scanner] Database Exception with times : " + dbExceptionTimes++);

                if (stateMechine == Status.OFFLINE) {

                    log.warn("[Scanner] Database Exception , OFFLINE ");

                } else if (dbExceptionTimes >= 3) {

                    log.fatal("[Scanner] Database Exception , Node Offline Mode Active , uniqueid : " + uniqueID);

                    stateMechine = Status.OFFLINE;

                    dbExceptionTimes = 0L;

                    offlineTime = System.currentTimeMillis();

                    online = false;

                } else

                    continue;

            }



            try {



                activeNodeLease = keepaliveNode!=null ? keepaliveNode.getLease() : activeNodeLease;

                activeNodeTimeStamp = keepaliveNode!=null ? keepaliveNode.getTimestamp() : activeNodeTimeStamp;

                takeRest = interval;



                switch (stateMechine) {

                    case START:

                        if (keepaliveNode == null) {

                            log.fatal("[START] Accquire node is null , ignore ");

                            // if no node register here , we challenge it

                            stateMechine = Status.CHALLENGE_REGISTER;

                            takeRest = 0;

                        } else {

                            // check the lease , wether myself or others 

                            if (activeNodeLease < timestampGap(activeNodeTimeStamp)) {

                                log.warn("[START] Lease Timeout scanner for uniqueid : " + uniqueID + ", timeout : "

                                            + timestampGap(activeNodeTimeStamp));

                                if (keepaliveNode.getStatus().equals(STAT_CHALLENGE))

                                    stateMechine = Status.HEARTBEAT;

                                else {

                                    stateMechine = Status.CHALLENGE_MASTER;

                                    takeRest = 0;

                                }

                            } else if (keepaliveNode.getUniqueID().equals(uniqueID)) {

                                // I'am restart

                                log.info("[START] Restart Scanner for uniqueid : " + uniqueID

                                                + ", timeout : " + timestampGap(activeNodeTimeStamp));

 stateMechine = Status.HEARTBEAT;

                            } else {

                                log.info("[START] Already Exist Keepalive Node with uniqueid : " + uniqueID);

                                stateMechine = Status.HEARTBEAT;

                            }

                        }

                        break;

                    case HEARTBEAT:

                        /* uniqueID == keepaliveNode.uniqueID */

                        if (keepaliveNode.getUniqueID().equals(uniqueID)) {

                            if (activeNodeLease < timestampGap(activeNodeTimeStamp)) {

                                // we should challenge now , without nessesary to checkout Status[CHALLENGE]

                                log.warn("[HEARTBEAT] HEART BEAT Lease is timeout for uniqueid : " + uniqueID

                                                + ", time : " + timestampGap(activeNodeTimeStamp));

                                stateMechine = Status.CHALLENGE_MASTER;

                                takeRest = 0;

                                break;

                            } else {

                                // lease ok , just update mysql keepalive status

                                dbService.updateAliveNode(keepaliveNode.setLease(lease));

                                online = true;

                                log.info("[HEARTBEAT] update equaled keepalive node , uniqueid : " + uniqueID

                                        + ", lease : " + lease + "s, remain_usable : " +

                                        ((activeNodeTimeStamp * 1000L + lease * 1000L) - System.currentTimeMillis()) + " ms");

                            }

                        } else {

                            /* It's others , let's check lease */

                            if (activeNodeLease < timestampGap(activeNodeTimeStamp)) {

                                if (keepaliveNode.getStatus().equals(STAT_CHALLENGE)) {

                                    waitForLeaseChallenging = (long) (activeNodeLease * awaitFactor);

                                    if ((waitForLeaseChallenging) < timestampGap(activeNodeTimeStamp)) {

                                        log.info("[HEARTBEAT] Lease Expired , Diff[" + timestampGap(activeNodeTimeStamp) + "] , Lease[" + activeNodeLease + "]");

                                        stateMechine = Status.CHALLENGE_MASTER;

                                        takeRest = 0;

                                    } else {

                                        log.info("[HEARTBEAT] Other Node Challenging , We wait for a moment ...");

                                    }

                                } else {

                                    log.info("[HEARTBEAT] Lease Expired , Diff[" + timestampGap(activeNodeTimeStamp) + "] , lease[" + activeNodeLease + "]");

                                    stateMechine = Status.CHALLENGE_MASTER;

                                    takeRest = 0;

                                }

                            } else {

                                online = false;

                                log.info("[HEARTBEAT] Exist Active Node On The Way with uniqueid : "

                                                + keepaliveNode.getUniqueID() + ", lease : " + keepaliveNode.getLease());

                            }

                        }

                        break;

                    case CHALLENGE_MASTER:

                        dbService.challengeAliveNode(new KeepAlive().setUniqueID(uniqueID).setLease(lease));

                        online = false;

                        // wait for the expired node offline automatic

                        // and others also have changce to challenge

takeRest = activeNodeLease;

                        stateMechine = Status.CHALLENGE_COMPLETE;

                        log.info("[CHALLENGE_MASTER] Other Node is timeout["

                                        + timestampGap(activeNodeTimeStamp) + "s] , I challenge with uniqueid : " + uniqueID

                                        + ", lease : " + lease + ", wait : " + lease);

                        break;

                    case CHALLENGE_REGISTER:

                        dbService.registerNewNode(new KeepAlive().setUniqueID(uniqueID).setLease(lease));

                        online = false;

                        // wait for the expired node offline automatic 

                        // and others also have changce to challenge

                        takeRest = activeNodeLease;

                        stateMechine = Status.CHALLENGE_COMPLETE;

                        log.info("[CHALLENGE_REGISTER] Regiter Keepalive uniqueid : " + uniqueID + ", lease : " + lease);

                        break;

                    case CHALLENGE_COMPLETE :

                        if (keepaliveNode.getUniqueID().equals(uniqueID)) {

                            dbService.updateAliveNode(keepaliveNode.setLease(lease));

                            online = true;

                            log.info("[CHALLENGE_COMPLETE] I Will be the Master uniqueid : " + uniqueID);

                            // make the uptime correct

                            stateMechine = Status.HEARTBEAT;

                        } else {

                            online = false;

                            log.warn("[CHALLENGE_COMPLETE] So unlucky , Challenge Failed By Other Node with uniqueid : " + keepaliveNode.getUniqueID());

                            if (challengeFailTimes++ >= (rand.nextLong() % maxChallenge) + minChallenge) {

                                // need't challenge anymore in a long time

                                takeRest=maxChallengeAwaitInterval;

                                stateMechine = Status.HEARTBEAT;

                                challengeFailTimes = 0L;

                                log.info("[CHALLENGE_COMPLETE] Challenge Try Times Used Up , let's take a long rest !");

                            } else {

stateMechine = Status.HEARTBEAT;

                                log.info("[CHALLENGE_COMPLETE] Challenge Times : " + challengeFailTimes + ", Never Give Up , to[" + stateMechine + "]");

                            }

                        }

                        break;

                    case OFFLINE :

                        log.fatal("[Scanner] Offline Mode Node with uniqueid : " + uniqueID);

                        if (System.currentTimeMillis() - offlineTime >= maxOfflineFrozen) {

                            // I am relive forcely

                            log.info("[Scanner] I am relive to activie node  , uniqueid : " + uniqueID);

                            stateMechine = Status.HEARTBEAT;

                            offlineTime = 0L;

                        } else if (keepaliveNode != null) {

                            // db is reconnected

                            stateMechine = Status.HEARTBEAT;

                            offlineTime = 0L;

                            log.info("[Scanner] I am relive to activie node  , uniqueid : " + uniqueID);

                        }

                        break;



                    default :

                        System.exit(0);

                }



                session.commit();

                session.close();



                if (takeRest != 0)

                    Thread.sleep(takeRest);



                log.info("[Scanner] State Stage [" + stateMechine + "]");



            } catch (InterruptedException e) {

                log.fatal("[System] Thread InterruptedException : " + e.getMessage());

            } finally {

                log.info("[Scanner] UniqueID : " + uniqueID + ", Mode : " + (online?"online":"offline"));

            }

        }



    }



    enum Status {

        START, HEARTBEAT, CHALLENGE_MASTER, CHALLENGE_REGISTER, CHALLENGE_COMPLETE, OFFLINE

    }
  

?

?

5 進(jìn)一步的優(yōu)化
?
? ? ? ? 一、在各個(gè)系統(tǒng)競爭Master時(shí),可能因?yàn)楣?jié)點(diǎn)太多,沖突概率較大,可以通過在數(shù)據(jù)庫中增加字段Status狀態(tài)字段,標(biāo)識(shí)是否有其他節(jié)點(diǎn)正在爭搶Master,如果是,則可以暫停等一下,然后在嘗試,如果那個(gè)節(jié)點(diǎn)成功搶到了Master,則會(huì)省去很多節(jié)點(diǎn)沖突的概率。
? ? ? ??
? ? ? ? 二、由于出現(xiàn)很極端的情況,因?yàn)楦偁嶮aster的時(shí)間和lease時(shí)間都是固定的,則可能出現(xiàn)”時(shí)間軸共振“的現(xiàn)象,最典型的如一直在競爭Master但是一直失敗,然后一直重試。所有的server在同一時(shí)刻都在趕同樣的事情。可以通過增加時(shí)間隨機(jī)性解決問題,如嘗試搶占Master連續(xù)失敗,則通過random產(chǎn)生隨機(jī)數(shù)然后sleep,抵消共振。

?


?

分布式選主 -- 利用Mysql ACID和Lease協(xié)議實(shí)現(xiàn)選主和高可用


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號(hào)聯(lián)系: 360901061

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊,切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對(duì)您有幫助就好】

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會(huì)非常 感謝您的哦!!!

發(fā)表我的評(píng)論
最新評(píng)論 總共0條評(píng)論
主站蜘蛛池模板: 天海翼精品久久中文字幕 | 国产日产欧美精品一区二区三区 | 999国产精品视频 | 爱我久久国产精品 | 热久久最新地址 | 91私拍| 免费视频精品一区二区三区 | 婷婷激情在线 | 久久99精品久久久久久国产人妖 | 久久中文字幕日韩精品 | 精品久久久中文字幕 | 女人十八一级毛片 | 99久久精品费精品国产一区二 | 国产欧美一区二区久久 | 伊人精品 | 亚洲视频手机在线观看 | 午夜国产精品色福利视频 | 99视频精品全国免费 | 国产精品66在线观看 | 日本吻胸抓胸激烈视频网站 | 精品综合久久久久97 | 呦女www| 久久精品国产精品亚洲红杏 | 久久青草免费97线频观 | 狠狠色狠狠色综合网 | 99久久一区| 欧美成人se01短视频在线看 | 亚洲综合色网 | 黑人和黑人激情一级毛片 | 国产亚洲精品自在久久不卡 | 色综合久久久久久久久五月性色 | 国产精品一二区 | 亚洲一区二区在线免费观看 | 亚洲精品啪啪一区二区三区 | 亚洲精品久久99久久一 | 日本一级特黄毛片免费视频 | 国产日产精品_国产精品毛片 | 国产精品久久久久久久久齐齐 | 5151四虎永久在线精品免费 | 成年黄页免费大全网站 | 国产99re |