1. 程式人生 > >三分鐘深入TT貓之故障轉移

三分鐘深入TT貓之故障轉移

分布式、微服務、 集群之路

結束了一周繁忙的工作,趁著周末,小編手中的鍵盤早已饑渴難耐了,想知道上期省略號中發生了什麽有趣的故事麽?且聽小編娓娓道來,結尾有彩蛋。

技術分享

風月前場

春風再續,書接上回,春香園的老鴇媽媽,給這位血氣方剛的騷年挑選了一位佳人A,於是乎騷年興致勃勃的進入了閨房,宜言飲酒,與子同歡,琴瑟在禦,莫不靜好,誰知佳人A突然來月事了(這個事先老鴇是不知道的)

我了個擦,春宵一刻值千金啊,趕召喚系老鴇兒,老鴇先是把A從侍客名單中剔除,隨後趕緊給這位騷年換了一位佳人B,歌管樓臺聲細細,秋千院落夜沈沈,哈哈哈,又是一個難忘的夜晚……

技術分享

夢回現實

哎,少年,醒醒,別做夢了,快起來搬磚了

技術分享

其實在實際生產中,我們的負載均衡器可能要更加溫柔體貼智能,不能讓用戶有一絲感覺到服務器也來大姨媽。

  • 還記得我們的TT貓,下單失敗的場景麽?被強行跳轉到登陸頁。

  • 還記得雙十一妹子那幽怨的小眼神麽?可能你已在偷偷樂了。

  • 還記得程序員小明瞎白活了一頓原理麽?其實可能他自己都沒搞明白。

模擬老鴇

在講如何體貼之前,先給大家傳授幾種老鴇經常使用的分配手法,為了讓大家更加形象直觀的了解老鴇的內心,小編決定帶大家扒開來看,當然了鴇媽的內心也是一坨代碼而已。

技術分享

首先我們定義一個OldBird,接著安排四個smallBirds值班。

/**
 * 老鴇
 * 創建時間 2017年9月16日
 */
public class OldBird {
    // Key代表風塵X子,Value代表該風塵X子的受歡迎程度
    public static Map<String, Integer> smallBirds = new ConcurrentHashMap<String, Integer>();
    static {
        smallBirds.put("野雞", 1);
        smallBirds.put("幺二", 2);
        smallBirds.put("長三", 3);
        smallBirds.put("書寓", 4);
    }
}

開張了,開張了,顯然第一位客人並沒有入的了鴇兒的法眼,隨機了一個後繼續嗑她的瓜子。

/**
 * 隨機 
 * 創建時間 2017年9月16日
 */
public class Random {
    public static String getServer() {
        // 獲取值班名單
        Set<String> keySet = ServerMap.servers.keySet();
        ArrayList<String> keyList = new ArrayList<String>();
        keyList.addAll(keySet);
        // 老鴇看人辦事 精打細算了一下、隨即了一個
        java.util.Random random = new java.util.Random();
        int randomPos = random.nextInt(keyList.size());
        // 程序員小明獲取了一個smallBird
        return keyList.get(randomPos);
    }
}

可能是鴇媽的隨機有點看心情,導致後院有些人有點不高興了,於是乎趕緊采取了另一種策略。

/**
 * 輪詢 
 * 創建時間 2017年9月16日
 */
public class RoundRobin {
    private static Integer pos = 0;
    public static String getServer() {
        //獲取今日值班名單
        Set<String> keySet = ServerMap.servers.keySet();
        ArrayList<String> keyList = new ArrayList<String>();
        keyList.addAll(keySet);
        //有些人 活太少 可能會不高興 還是排號來吧
        String server = null;
        synchronized (pos) {
            if (pos >= keySet.size())
                pos = 0;
            server = keyList.get(pos);
            pos++;
        }
        // 程序員小明獲取了一個smallBird
        return server;
    }
}

這時候大茶壺急急忙忙的趕到老鴇身邊,哎,別嗑了,韋爺點名要書寓,趕緊給安排安排,老鴇一想常客啊,不行,我得好好編排一下,省的老被打擾。

/**
 * 源地址哈希
 * 創建時間    2017年9月16日
 */
public class Hash {
    public static String getServer()      
    {      
        //獲取今日值班名單
        Set<String> keySet = ServerMap.servers.keySet();      
        ArrayList<String> keyList = new ArrayList<String>();      
        keyList.addAll(keySet);  
        //韋爺 悠哉的進來的 點名要書寓
        String remoteGuest = "韋爺";      
        //老鴇給韋爺 設置固定編號
        int hashCode = remoteGuest.hashCode();
        int serverListSize = keyList.size();
        int serverPos = hashCode % serverListSize;
        //韋爺獲取到了指定服務
        return keyList.get(serverPos);      
    }      
}

讀到最後,小夥伴們可能要問了,那個Map中的Value並沒有起作用啊,讓老鴇吃了麽?其實,只是感覺用在這裏不妥而已,有些事,你懂我懂大家都懂。

具體到生產架構中,應該是這個樣子的

/**
 * 服務器負載均衡集群組 
 * 創建時間 2017年9月16日
 */
public class ServerMap {
    // Key代表服務器,Value代表該服務的權重
    public static Map<String, Integer> servers = new ConcurrentHashMap<String, Integer>();
    static {
        //這裏有四個服務 權重分別是1234
        servers.put("1核1G-服務器", 1);
        servers.put("2核2G-服務器", 2);
        servers.put("3核3G-服務器", 3);
        servers.put("4核4G-服務器", 4);
    }

能者多勞,權重視服務器的性能而定,下面的算法,服務器4每次有百分之四十的幾率被獲取到。

/**
 * 加權輪詢
 * 創建時間    2017年9月16日
 */
public class WeightRoundRobin {
    private static Integer pos = 0;   
    public static String getServer()   
    {   
        //取得服務器List   
        Set<String> keySet = ServerMap.servers.keySet();   
        Iterator<String> iterator = keySet.iterator();   
        //計算權重總數 累加 比如 4核4G-服務器  權重為4 上述10個服務器中存在4個4核4G-服務器服務  增加隨機或者輪詢幾率
        List<String> serverList = new ArrayList<String>();   
        while (iterator.hasNext())   
        {   
            String server = iterator.next();   
            int weight = ServerMap.servers.get(server);   
            for (int i = 0; i < weight; i++)   
                serverList.add(server);   
        }   
        String server = null;   
        synchronized (pos)   
        {   
            if (pos >= keySet.size())   
                pos = 0;   
            server = serverList.get(pos);   
            pos ++;   
        }   
        return server;   
    }  
}

說了這麽多,以上只是幾種簡單的負載均衡算法,在 記一次JavaWeb網站技術架構總結 中有提到十種負載均衡策略以及其優缺點,有興趣的同學可以一看。

會話機制

各位看官莫急,要想弄明白故障轉移是怎麽回事,必須要弄明白客戶端-服務端的會話認證機制。

由於HTTP協議本身是無狀態的,這與HTTP協議本來的目的是相符的,那麽小馬哥是怎麽知道那些用戶買了那些東西的呢?

以Tomcat為例,大家都知道session是在服務器端創建並存儲到容器的JVM內存中的,瀏覽器初次訪問服務器會生成一個叫JSESSIONID的cookie,瀏覽器的每次請求都會附帶這個cookie,服務端通過JSESSIONID會找到內存中對應的狀態信息。

程序員小明,打開TT貓,輸入自己的賬號密碼,附帶cookie信息請求到了後臺,TT貓後臺校驗成功以後,會把用戶信息保存到JSESSIONID對應的內存中,這樣小明和TT貓就可以無障礙的深入交流了。

這個過程也可以用以下示意圖來描述:

技術分享

如果你覺得會話機制如此簡單,那可就有點高看小編了,篇幅有限,對會話機制感興趣的同學只能自行查閱資料了。

故障轉移

老鴇之所以能快速安撫騷年使其順利度過這纏綿之夜,有沒有感受到老鴇強大的人工智能氣息?

技術分享

其實我們的負載均衡器Nginx,也是做的相當智能的,如果後端節點服務器宕掉的話,Nginx通過自帶的模塊可以把這臺壞掉的服務踢出upstream負載集群組,然後自動切換到健康節點來提供訪問。

有過開發經驗的小夥伴,都知道服務分有狀態和無狀態。

  • 無狀態服務(Stateless Service):遊客瀏覽商品、搜索商品等等這種不需要鑒權的操作。

  • 有狀態服務(Stateful Service): 添加購物車,下單,支付等等需要用戶認證的操作。

對於這種無狀態的服務請求,不管集群組使用任何負載均衡算法(隨機、輪詢、hash),只要有一個存活,小馬哥的TT貓就可以提供正常服務。

但是對於支付這種需要用戶認證的操作,不得不說,我們要選擇合適的負載均衡算法。

服務獨自存儲用戶狀態

  • 隨機、輪詢算法,小明可能一輩子都無法登陸TT貓

  • hash算法,單一服務宕掉的話會導致用戶狀態丟失

服務統一存儲用戶狀態

架構設計之Spring-Session分布式集群會話管理

總結

技術分享

秋名山上行人稀,常有框架較高低,如今原理依舊在,不見當年老框架。

底層原理可能你這輩子都不過時,解決問題的能力永遠都不過時,積極向上的求知欲永遠是你的強大後盾。

既定目標,做個有追求的程序員,如果你連算法數據結構都能搞得明白,網絡傳輸都可以手到擒來,怎學不會簡單的API調用?

塞內加在《論生命之短暫》中說過“如果一個人出海遇到狂風暴雨,被變換肆虐的風吹得團團轉,你可能會覺得他航行了很遠。其實航行得並不遠,只是浮沈動蕩的時間長而已”,沒錯如今的知識就像出海時遇到的狂風暴雨,我們只是被吹的原地團團轉而已,並沒有在知識的海洋航行很遠。


本文出自 “小柒2015” 博客,請務必保留此出處http://itstyle.blog.51cto.com/2205083/1973884

三分鐘深入TT貓之故障轉移