負(fù)載均衡算法的介紹和實(shí)現(xiàn)

負(fù)載均衡在分布式系統(tǒng)中異常地重要壁畸,下面主要講解一下幾種負(fù)載均衡算法的實(shí)現(xiàn)阀趴。

先給個(gè)基本類氢架,下面的類都是在這個(gè)類的基礎(chǔ)上實(shí)現(xiàn)的

public class IpMap {
    public static HashMap<String, Integer> serverMap = new HashMap<>();

    static {
        serverMap.put("192.168.1.1", 1);
        serverMap.put("192.168.1.2", 1);
        serverMap.put("192.168.1.3", 3);
        serverMap.put("192.168.1.4", 1);
        serverMap.put("192.168.1.5", 4);
        serverMap.put("192.168.1.6", 1);
        serverMap.put("192.168.1.7", 1);
        serverMap.put("192.168.1.10", 2);
        serverMap.put("192.168.1.11", 1);
        serverMap.put("192.168.1.12", 1);
    }
}

RoundRobin 輪詢法

輪詢法,是按公約后的權(quán)重設(shè)置輪詢比率,到達(dá)邊界之后券敌,繼續(xù)繞接唾戚。主要分為普通輪詢和權(quán)重輪詢

普通輪詢法

public class RoundRobin {

    // 輪詢的位置
    private static Integer position = 0;

    public static String getString () {
        // 本地保留服務(wù)端列表,避免服務(wù)器上下線導(dǎo)致并發(fā)問(wèn)題
        Map<String, Integer> serverMap = new HashMap<>(IpMap.serverMap);

        // 獲取服務(wù)端ip列表
        Set<String> keySet = serverMap.keySet();
        List<String> serverList = new ArrayList<>(keySet);

        String server = null;

        // 防止并發(fā)問(wèn)題
        synchronized (position) {
            // 環(huán)形輪詢
            if (position >= keySet.size()) {
                position = 0;
            }
            server = serverList.get(position);
            position++;
        }
        return server;
    }
}

IpMap.serverMap是動(dòng)態(tài)變化的陪白,上下線宕機(jī)都是隨時(shí)隨地的颈走。為了避免IpMap.serverMap被多線程修改導(dǎo)致并發(fā)問(wèn)題,特此將列表緩存在本地咱士。但是這樣可能會(huì)導(dǎo)致服務(wù)端列表修改后立由,不能及時(shí)同步,這個(gè)時(shí)候就需要集群容錯(cuò)策略了序厉。

對(duì)于當(dāng)前輪詢的位置變量position锐膜,為了保證服務(wù)器選擇的順序性,需要在操作的時(shí)候加鎖或者cas

  • 優(yōu)點(diǎn):能夠做到流量分配的絕對(duì)平衡
  • 缺點(diǎn):需要對(duì)position加鎖弛房,并發(fā)吞吐量明顯下降

加權(quán)輪詢法

public class WeightRoundRobin {

    private static AtomicInteger position = new AtomicInteger(0);

    public static String getString () {
        Map<String, Integer> serverMap = new HashMap<>(IpMap.serverMap);
        List<String> serverList = new ArrayList<>();
        serverMap.forEach((serverIp, weight) -> {
            for (int i = 0; i < weight; i++) {
                serverList.add(serverIp);
            }
        });
        if (position.get() >= serverList.size()) {
            position.set(0);
        }
        position.getAndIncrement();
        return serverList.get(position.get());
    }
}

與輪詢法類似道盏,但是列表中根據(jù)權(quán)重會(huì)增加serverIp的個(gè)數(shù),權(quán)重多的訪問(wèn)的就多文捶。

輪詢法分析

  1. 輪詢法其實(shí)主要還是想打到一種絕對(duì)的流量平衡荷逞,但是進(jìn)行了并發(fā)控制,會(huì)比較影響并發(fā)和吞吐量
  2. 如果某一服務(wù)器響應(yīng)過(guò)慢粹排,但是還沒(méi)達(dá)到宕機(jī)的地步种远,這會(huì)導(dǎo)致多個(gè)請(qǐng)求打到同一臺(tái)機(jī)器上,產(chǎn)生數(shù)據(jù)傾斜

隨機(jī)法

隨機(jī)法主要分為普通隨機(jī)和權(quán)重隨機(jī)

普通隨機(jī)法

public class Random {

    public static String getString () {
        Map<String, Integer> serverMap = new HashMap<>(IpMap.serverMap);

        Set<String> keySet = serverMap.keySet();
        List<String> serverList = new ArrayList<>(keySet);
        java.util.Random random = new java.util.Random();
        return serverList.get(random.nextInt(keySet.size()));
    }
}

這塊其實(shí)就是在整體的server列表進(jìn)行隨機(jī)顽耳。

加權(quán)隨機(jī)法

public class WeightRandom {

    public static String getString () {
        Map<String, Integer> serverMap = new HashMap<>(IpMap.serverMap);
        List<String> serverList = new ArrayList<>();
        serverMap.forEach((serverIp, weight) -> {
            for (int i = 0; i < weight; i++) {
                serverList.add(serverIp);
            }
        });
        Random random = new Random();
        return serverList.get(random.nextInt(serverList.size()));
    }
}

這塊不解釋了

隨機(jī)法分析

  1. 既然是隨機(jī)法坠敷,可理解為,當(dāng)請(qǐng)求量比較大的時(shí)候射富,請(qǐng)求分布會(huì)相對(duì)均勻一些膝迎。類似于硬幣,當(dāng)你拋100000次胰耗,基本上正反面出現(xiàn)次數(shù)是1比1限次。而且沒(méi)有加鎖,對(duì)并發(fā)和吞吐會(huì)比較友好
  2. 請(qǐng)求量比較低的時(shí)候柴灯,可能會(huì)出現(xiàn)數(shù)據(jù)傾斜
  3. 非對(duì)等集群組網(wǎng)卖漫,硬件配置較大時(shí),會(huì)導(dǎo)致各個(gè)節(jié)點(diǎn)負(fù)載不均勻

哈希法

哈希法主要分為普通哈希和一致性hash

普通哈希法

public class OriginHash {

    public static String getString () {
        Map<String, Integer> serverMap = new HashMap<>(IpMap.serverMap);

        Set<String> keySet = serverMap.keySet();
        List<String> serverList = new ArrayList<>(keySet);
        String clientIp = "127.0.0.1";
        int clientIpHashcode = clientIp.hashCode();
        return serverList.get(clientIpHashcode % keySet.size());
    }
}

主要是根據(jù)客戶端某一個(gè)數(shù)據(jù)進(jìn)行hash弛槐,可以為ip,mac等信息

一致性哈希

一致性hash最初是為了memcache提出的負(fù)載均衡算法依啰,但是由于該算法優(yōu)良乎串,性能比較好,所以目前已經(jīng)用于各種分布式架構(gòu)上了。

基本概念

一致性hash將整個(gè)hash空間組織成一個(gè)虛擬的圓環(huán)叹誉,普通的hash算法可能是對(duì)服務(wù)器的數(shù)量進(jìn)行取模鸯两,而一致性hash是對(duì)2^32取模, 也就是說(shuō)长豁,哈希函數(shù)的H值的空間為0-(2^32 - 1)

[站外圖片上傳中...(image-456ebf-1563700555907)]

整個(gè)空間按順時(shí)針組織钧唐,0 和 2^32 - 1重合

下面將各個(gè)服務(wù)端的服務(wù)器進(jìn)行hash,hash的key可以為ip或主機(jī)名或mac匠襟,這樣每臺(tái)機(jī)器能夠確定在這個(gè)環(huán)中的位置钝侠,假設(shè)四臺(tái)服務(wù)端服務(wù)器用ip地址映射到下面的環(huán)上

1.jpeg

接下來(lái)使用如下算法定位數(shù)據(jù)訪問(wèn)的相應(yīng)服務(wù)器:將數(shù)據(jù)key(客戶端服務(wù)器)使用相同的函數(shù)hash計(jì)算出哈希值,確定在環(huán)中的位置酸舍,從這個(gè)位置沿著順時(shí)針遍歷帅韧,第一臺(tái)遇到的服務(wù)器就是定位到的服務(wù)器

假設(shè)有A,B, C, D四個(gè)客戶端啃勉,經(jīng)過(guò)hash后忽舟,位置在環(huán)上如下位置

客戶端節(jié)點(diǎn)分布在hash圓環(huán)上

通過(guò)一致性hash的算法,可以看出淮阐,最終A定位在NodeA上叮阅,B定位在NodeB上,C定位在NodeC上泣特,D定位在NodeD上浩姥。

性質(zhì)分析

分析一下一致性hash的容錯(cuò)性和可擴(kuò)展性。假設(shè)C不行宕機(jī)群扶,客戶端ABD都不會(huì)受到影響及刻,只有C受到影響。假設(shè)增加一臺(tái)服務(wù)器X竞阐,如下圖所示

添加節(jié)點(diǎn)

其實(shí)也只是影響了C一個(gè)客戶節(jié)點(diǎn)缴饭。則受影響的數(shù)據(jù)僅僅是新服務(wù)器到其環(huán)空間中前一臺(tái)服務(wù)器(即沿著逆時(shí)針?lè)较蛐凶哂龅降牡谝慌_(tái)服務(wù)器)之間數(shù)據(jù),其它數(shù)據(jù)也不會(huì)受到影響骆莹。

這個(gè)時(shí)候考慮如果是普通的hash算法颗搂,看下表:

hashcode 3個(gè)服務(wù)端節(jié)點(diǎn)編號(hào) 4個(gè)服務(wù)端節(jié)點(diǎn)編號(hào)
0 0 0
1 1 1
2 2 2
3 0 3
4 1 0
5 2 1

可以看到,其實(shí)全亂了....

虛擬節(jié)點(diǎn)

如果一致性hash中節(jié)點(diǎn)較少幕垦,會(huì)產(chǎn)生因?yàn)楣?jié)點(diǎn)分布不均勻的數(shù)據(jù)傾斜問(wèn)題丢氢。

少量數(shù)據(jù)產(chǎn)生數(shù)據(jù)傾斜問(wèn)題

會(huì)導(dǎo)致大部分?jǐn)?shù)據(jù)集中在A上,少量定位在B上先改,為了解決這個(gè)問(wèn)題疚察,引入了虛擬節(jié)點(diǎn)。具體做法可以在服務(wù)器ip或主機(jī)名的后面增加編號(hào)來(lái)實(shí)現(xiàn)仇奶。例如上面的情況貌嫡,可以為每臺(tái)服務(wù)器計(jì)算三個(gè)虛擬節(jié)點(diǎn),于是可以分別計(jì)算 “Node A#1”、“Node A#2”岛抄、“Node A#3”别惦、“Node B#1”、“Node B#2”夫椭、“Node B#3”的哈希值掸掸,于是形成六個(gè)虛擬節(jié)點(diǎn):

添加虛擬節(jié)點(diǎn)

那么虛擬節(jié)點(diǎn)其實(shí)最終指向的也是實(shí)際節(jié)點(diǎn)

實(shí)現(xiàn)

public class ConsistentHash<T> {

    /**
     * 節(jié)點(diǎn)的復(fù)制因子
     */
    private int numberOfReplicas;

    /**
     * 虛擬節(jié)點(diǎn)個(gè)數(shù)
     */
    private final SortedMap<Integer, T> circle = new TreeMap<>();

    /**
     * 初始化節(jié)點(diǎn)
     * @param numberOfReplicas
     * @param nodes
     */
    public ConsistentHash (int numberOfReplicas, Collection<T> nodes) {
        this.numberOfReplicas = numberOfReplicas;
        for (T node : nodes) {
            add(node);
        }
    }

    /**
     * 將節(jié)點(diǎn)加入環(huán)內(nèi)
     * @param node
     */
    public void add (T node) {
        for (int i = 0; i < numberOfReplicas; i++) {
            String nodeStr = node.toString() + i;
            System.out.println("nodeStr:" + nodeStr);
            int hashCode = nodeStr.hashCode();
            System.out.println("hashCode:" + hashCode);
            circle.put(hashCode, node);
        }
    }

    /**
     * 從環(huán)中刪除節(jié)點(diǎn)
     * @param node
     */
    public void remove (T node) {
        for (int i = 0; i < numberOfReplicas; i++) {
            circle.remove(node.toString() + i);
        }
    }

    /**
     * 從環(huán)中查找對(duì)應(yīng)的數(shù)據(jù)
     * @param key
     * @return
     */
    public T get (Object key) {
        if (circle.isEmpty()) {
            return null;
        }
        int hash = key.hashCode();
        if (!circle.containsKey(hash)) {
            // 從環(huán)中hash的位置開(kāi)始,找到值大于hash的列表
            SortedMap<Integer, T> tailMap = circle.tailMap(hash);
            // 如果為空蹭秋,說(shuō)明已經(jīng)到末尾了扰付,取第一個(gè);如果非空感凤,取最近的第一個(gè)節(jié)點(diǎn)
            hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey();
        }
        return circle.get(hash);
    }

    public long getSize () {
        return circle.size();
    }

    public void testBalance () {
        Set<Integer> set = circle.keySet();
        SortedSet<Integer> sortedSet = new TreeSet<>(set);
        System.out.println(sortedSet);
        System.out.println("------------------------------------");
    }

    public static void main (String[] args) {
        Set<String> nodes = new HashSet<>();
        nodes.add("a");
        nodes.add("b");
        nodes.add("c");
        ConsistentHash<String> consistentHash = new ConsistentHash<>(2, nodes);
        consistentHash.add("d");
        System.out.println("hash circle size:" + consistentHash.getSize());
        consistentHash.testBalance();
        System.out.println(consistentHash.get("a3"));
    }
}

其實(shí)上面例子中的節(jié)點(diǎn)并不是很好悯周,取的a,b,c,d,導(dǎo)致hash的比較集中陪竿,但是意思是這樣禽翼,具體時(shí)間盡量避免節(jié)點(diǎn)集中,而且虛擬節(jié)點(diǎn)要選好散列

服務(wù)調(diào)用時(shí)延

消費(fèi)者會(huì)緩存所有服務(wù)提供者的服務(wù)調(diào)用時(shí)延族跛,周期性地計(jì)算服務(wù)調(diào)用平均時(shí)延闰挡,然后計(jì)算每個(gè)服務(wù)提供者服務(wù)調(diào)用時(shí)延與平均時(shí)延的差值,根據(jù)差值大小調(diào)整權(quán)重礁哄,盡量保證服務(wù)調(diào)用時(shí)延大的接受更少的請(qǐng)求

粘滯連接

粘滯連接用于有狀態(tài)的服務(wù)长酗,盡可能讓客戶端總是像同一服務(wù)提供者發(fā)起服務(wù)調(diào)用,除非它宕機(jī)桐绒。但是服務(wù)一般都是無(wú)狀態(tài)的夺脾,所以很少使用

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市茉继,隨后出現(xiàn)的幾起案子咧叭,更是在濱河造成了極大的恐慌,老刑警劉巖烁竭,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件菲茬,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡派撕,警方通過(guò)查閱死者的電腦和手機(jī)婉弹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)终吼,“玉大人镀赌,你說(shuō)我怎么就攤上這事〖使颍” “怎么了商佛?”我有些...
    開(kāi)封第一講書人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵蛙粘,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我威彰,道長(zhǎng),這世上最難降的妖魔是什么穴肘? 我笑而不...
    開(kāi)封第一講書人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任歇盼,我火速辦了婚禮,結(jié)果婚禮上评抚,老公的妹妹穿的比我還像新娘豹缀。我一直安慰自己,他們只是感情好慨代,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布邢笙。 她就那樣靜靜地躺著,像睡著了一般侍匙。 火紅的嫁衣襯著肌膚如雪氮惯。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,604評(píng)論 1 305
  • 那天想暗,我揣著相機(jī)與錄音妇汗,去河邊找鬼。 笑死说莫,一個(gè)胖子當(dāng)著我的面吹牛杨箭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播储狭,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼互婿,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了辽狈?” 一聲冷哼從身側(cè)響起慈参,我...
    開(kāi)封第一講書人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎稻艰,沒(méi)想到半個(gè)月后懂牧,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡尊勿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年僧凤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片元扔。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡躯保,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出澎语,到底是詐尸還是另有隱情途事,我是刑警寧澤验懊,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站尸变,受9級(jí)特大地震影響义图,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜召烂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一碱工、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧奏夫,春花似錦怕篷、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至麻削,卻和暖如春蒸痹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背呛哟。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工电抚, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人竖共。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓蝙叛,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親公给。 傳聞我的和親對(duì)象是個(gè)殘疾皇子借帘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 概述 比較經(jīng)典的5種負(fù)載均衡算法:隨機(jī)法肺然、輪詢法、最少連接數(shù)法腿准、最快響應(yīng)法际起、Hash化散列法(包括IP-Hash和...
    黃靠譜閱讀 3,001評(píng)論 0 33
  • 一、負(fù)載均衡算法理論 以下理論知識(shí)為互聯(lián)網(wǎng)資源的整理吐葱,若有侵權(quán)街望,請(qǐng)私信聯(lián)系刪除 1. 輪詢算法(Round Rob...
    HRocky閱讀 2,066評(píng)論 0 0
  • 什么是負(fù)載均衡 負(fù)載均衡,英文名稱為L(zhǎng)oad Balance弟跑,指由多臺(tái)服務(wù)器以對(duì)稱的方式組成一個(gè)服務(wù)器集合灾前,每臺(tái)服...
    kevin0016閱讀 660評(píng)論 0 6
  • 記得,我剛工作的時(shí)候孟辑,同事說(shuō)了一個(gè)故事:在他剛工作的時(shí)候哎甲,他同事有一天興沖沖的跑到公司說(shuō)蔫敲,你們知道嗎,公司請(qǐng)了個(gè)大...
    CoderBear閱讀 544評(píng)論 0 1
  • 今天夫休息炭玫,早上幫忙煮飯奈嘿,我?guī)殞毴ベI豆?jié){包子,回來(lái)我洗菜弄好吞加,他炒菜我吃早餐指么,后來(lái)休息全家人看手機(jī),后來(lái)剃頭榴鼎,搶...
    許小燕_917c閱讀 122評(píng)論 0 0