分佈式技術原理與算法解析05-如何使用負載均衡算法

  • Author: shasha
  • Description: 該文簡單介紹微服務技術棧有哪些分別用來做什麼

如何使用負載均衡算法

隨機算法

  • 隨機算法,就是從可用服務節點隨機挑選一個節點來訪問
  • 實現:隨機算法通過生成隨機數來實現,例如服務有10個節點,隨機生成數字2,就訪問2的這個節點
/*
 *  Copyright 2009-2016 Weibo, Inc.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package com.weibo.api.motan.cluster.loadbalance;

import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

import com.weibo.api.motan.core.extension.SpiMeta;
import com.weibo.api.motan.rpc.Referer;
import com.weibo.api.motan.rpc.Request;

/**
 * 
 * random load balance.
 *
 * @author fishermen
 * @version V1.0 created at: 2013-5-21
 */
@SpiMeta(name = "random")
public class RandomLoadBalance<T> extends AbstractLoadBalance<T> {

    @Override
    protected Referer<T> doSelect(Request request) {
        List<Referer<T>> referers = getReferers();

        int idx = (int) (ThreadLocalRandom.current().nextDouble() * referers.size());
        for (int i = 0; i < referers.size(); i++) {
            Referer<T> ref = referers.get((i + idx) % referers.size());
            if (ref.isAvailable()) {
                return ref;
            }
        }
        return null;
    }

    @Override
    protected void doSelectToHolder(Request request, List<Referer<T>> refersHolder) {
        List<Referer<T>> referers = getReferers();

        int idx = (int) (ThreadLocalRandom.current().nextDouble() * referers.size());
        for (int i = 0; i < referers.size(); i++) {
            Referer<T> referer = referers.get((i + idx) % referers.size());
            if (referer.isAvailable()) {
                refersHolder.add(referer);
            }
        }
    }
}

輪詢算法

  • 輪詢算法,就是按照指定的順序,把可用的服務節點挨個訪問一次
  • 輪詢算法通常是吧可用節點放到一個數組裏面,然後按照數組編號,挨個訪問。
/*
 *  Copyright 2009-2016 Weibo, Inc.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package com.weibo.api.motan.cluster.loadbalance;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import com.weibo.api.motan.core.extension.SpiMeta;
import com.weibo.api.motan.rpc.Referer;
import com.weibo.api.motan.rpc.Request;
import com.weibo.api.motan.util.MathUtil;

/**
 * 
 * Round robin loadbalance.
 * 
 * @author fishermen
 * @version V1.0 created at: 2013-6-13
 */
@SpiMeta(name = "roundrobin")
public class RoundRobinLoadBalance<T> extends AbstractLoadBalance<T> {

    private AtomicInteger idx = new AtomicInteger(0);

    @Override
    protected Referer<T> doSelect(Request request) {
        List<Referer<T>> referers = getReferers();

        int index = getNextNonNegative();
        for (int i = 0; i < referers.size(); i++) {
            Referer<T> ref = referers.get((i + index) % referers.size());
            if (ref.isAvailable()) {
                return ref;
            }
            idx.incrementAndGet();
        }
        return null;
    }

    @Override
    protected void doSelectToHolder(Request request, List<Referer<T>> refersHolder) {
        List<Referer<T>> referers = getReferers();

        int index = getNextNonNegative();
        for (int i = 0, count = 0; i < referers.size() && count < MAX_REFERER_COUNT; i++) {
            Referer<T> referer = referers.get((i + index) % referers.size());
            if (referer.isAvailable()) {
                refersHolder.add(referer);
                count++;
            }
        }
    }

    // get non-negative int
    private int getNextNonNegative() {
        return MathUtil.getNonNegative(idx.incrementAndGet());
    }
}

加權輪詢算法

  • 加權輪詢就是給每個節點賦予一個權重,從而使每個節點被訪問的概率不同,權重大的節點被訪問的概率就高,權重小的節點被訪問的概率就小。
  • 實現:加權輪詢就是生成一個隨機序列,該序列裏有n個節點,n是所有節點的權重之和,在這個序列中,每個節點出現的次數就是它的權重值,例如a,b,c三個權重值分別是3,2,1,那麼它生成的序列就是{a,b,c,a,a,b}
/*
 *  Copyright 2009-2016 Weibo, Inc.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package com.weibo.api.motan.cluster.loadbalance;

import com.weibo.api.motan.core.extension.SpiMeta;
import com.weibo.api.motan.rpc.Referer;
import com.weibo.api.motan.rpc.Request;
import com.weibo.api.motan.util.CollectionUtil;
import com.weibo.api.motan.util.LoggerUtil;
import com.weibo.api.motan.util.MathUtil;

import org.apache.commons.lang3.StringUtils;

import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 權重可配置的負載均衡器
 *
 * @author chengya1
 */
@SpiMeta(name = "configurableWeight")
public class ConfigurableWeightLoadBalance<T> extends ActiveWeightLoadBalance<T> {

    @SuppressWarnings("rawtypes")
    private static final RefererListCacheHolder emptyHolder = new EmptyHolder();

    @SuppressWarnings("unchecked")
    private volatile RefererListCacheHolder<T> holder = emptyHolder;

    private String weightString;

    @SuppressWarnings("unchecked")
    @Override
    public void onRefresh(List<Referer<T>> referers) {
        super.onRefresh(referers);

        if (CollectionUtil.isEmpty(referers)) {
            holder = emptyHolder;
        } else if (StringUtils.isEmpty(weightString)) {
            holder = new SingleGroupHolder<T>(referers);
        } else {
            holder = new MultiGroupHolder<T>(weightString, referers);
        }
    }

    @Override
    protected Referer<T> doSelect(Request request) {
        if (holder == emptyHolder) {
            return null;
        }

        RefererListCacheHolder<T> h = this.holder;
        Referer<T> r = h.next();
        if (!r.isAvailable()) {
            int retryTimes = getReferers().size() - 1;
            for (int i = 0; i < retryTimes; i++) {
                r = h.next();
                if (r.isAvailable()) {
                    break;
                }
            }
        }
        if (r.isAvailable()) {
            return r;
        } else {
            noAvailableReferer();
            return null;
        }
    }

    @Override
    protected void doSelectToHolder(Request request, List<Referer<T>> refersHolder) {
        if (holder == emptyHolder) {
            return;
        }

        RefererListCacheHolder<T> h = this.holder;
        int i = 0, j = 0;
        while (i++ < getReferers().size()) {
            Referer<T> r = h.next();
            if (r.isAvailable()) {
                refersHolder.add(r);
                if (++j == MAX_REFERER_COUNT) {
                    return;
                }
            }
        }
        if (refersHolder.isEmpty()) {
            noAvailableReferer();
        }
    }

    private void noAvailableReferer() {
        LoggerUtil.error(this.getClass().getSimpleName() + " 當前沒有可用連接, pool.size=" + getReferers().size());
    }

    @Override
    public void setWeightString(String weightString) {
        this.weightString = weightString;
    }


    /*****************************************************************************************
     * ************************************************************************************* *
     *****************************************************************************************/
    static abstract class RefererListCacheHolder<T> {
        abstract Referer<T> next();
    }

    static class EmptyHolder<T> extends RefererListCacheHolder<T> {
        @Override
        Referer<T> next() {
            return null;
        }
    }

    @SuppressWarnings("hiding")
    class SingleGroupHolder<T> extends RefererListCacheHolder<T> {

        private int size;
        private List<Referer<T>> cache;

        SingleGroupHolder(List<Referer<T>> list) {
            cache = list;
            size = list.size();
            LoggerUtil.info("ConfigurableWeightLoadBalance build new SingleGroupHolder.");
        }

        @Override
        Referer<T> next() {
            return cache.get(ThreadLocalRandom.current().nextInt(size));
        }
    }

    @SuppressWarnings("hiding")
    class MultiGroupHolder<T> extends RefererListCacheHolder<T> {

        private int randomKeySize = 0;
        private List<String> randomKeyList = new ArrayList<String>();
        private Map<String, AtomicInteger> cursors = new HashMap<String, AtomicInteger>();
        private Map<String, List<Referer<T>>> groupReferers = new HashMap<String, List<Referer<T>>>();

        MultiGroupHolder(String weights, List<Referer<T>> list) {
            LoggerUtil.info("ConfigurableWeightLoadBalance build new MultiGroupHolder. weights:" + weights);
            String[] groupsAndWeights = weights.split(",");
            int[] weightsArr = new int[groupsAndWeights.length];
            Map<String, Integer> weightsMap = new HashMap<String, Integer>(groupsAndWeights.length);
            int i = 0;
            for (String groupAndWeight : groupsAndWeights) {
                String[] gw = groupAndWeight.split(":");
                if (gw.length == 2) {
                    Integer w = Integer.valueOf(gw[1]);
                    weightsMap.put(gw[0], w);
                    groupReferers.put(gw[0], new ArrayList<Referer<T>>());
                    weightsArr[i++] = w;
                }
            }

            // 求出最大公約數,若不爲1,對權重做除法
            int weightGcd = findGcd(weightsArr);
            if (weightGcd != 1) {
                for(Map.Entry<String,Integer> entry: weightsMap.entrySet()) {
                    weightsMap.put(entry.getKey(),entry.getValue()/weightGcd);
                }
            }

            for (Map.Entry<String, Integer> entry : weightsMap.entrySet()) {
                for (int j = 0; j < entry.getValue(); j++) {
                    randomKeyList.add(entry.getKey());
                }
            }
            Collections.shuffle(randomKeyList);
            randomKeySize = randomKeyList.size();

            for (String key : weightsMap.keySet()) {
                cursors.put(key, new AtomicInteger(0));
            }

            for (Referer<T> referer : list) {
                groupReferers.get(referer.getServiceUrl().getGroup()).add(referer);
            }
        }

        @Override
        Referer<T> next() {
            String group = randomKeyList.get(ThreadLocalRandom.current().nextInt(randomKeySize));
            AtomicInteger ai = cursors.get(group);
            List<Referer<T>> referers = groupReferers.get(group);
            return referers.get(MathUtil.getNonNegative(ai.getAndIncrement()) % referers.size());
        }

        // 求最大公約數
        private int findGcd(int n, int m) {
            return (n == 0 || m == 0) ? n + m : findGcd(m, n % m);
        }

        // 求最大公約數
        private int findGcd(int[] arr) {
            int i = 0;
            for (; i < arr.length - 1; i++) {
                arr[i + 1] = findGcd(arr[i], arr[i + 1]);
            }
            return findGcd(arr[i], arr[i - 1]);
        }
    }

}

最少活躍連接算法

  • 最少活躍度算法,每一次訪問都選擇連接數最少的節點。連接數大的可以認爲是處理請求慢,連接數小的可以認爲是處理請求魁岸。
/*
 *  Copyright 2009-2016 Weibo, Inc.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package com.weibo.api.motan.cluster.loadbalance;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;

import com.weibo.api.motan.core.extension.SpiMeta;
import com.weibo.api.motan.rpc.Referer;
import com.weibo.api.motan.rpc.Request;

/**
 * "低併發優化" 負載均衡
 * 
 * <pre>
 * 		1) 低併發度優先: referer的某時刻的call數越小優先級越高 
 * 
 * 		2) 低併發referer獲取策略:
 * 				由於Referer List可能很多,比如上百臺,如果每次都要從這上百個Referer或者最低併發的幾個,性能有些損耗,
 * 				因此 random.nextInt(list.size()) 獲取一個起始的index,然後獲取最多不超過MAX_REFERER_COUNT的
 * 				狀態是isAvailable的referer進行判斷activeCount.
 * </pre>
 * 
 * @author maijunsheng
 * @version 創建時間:2013-6-14
 * 
 */
@SpiMeta(name = "activeWeight")
public class ActiveWeightLoadBalance<T> extends AbstractLoadBalance<T> {

    @Override
    protected Referer<T> doSelect(Request request) {
        List<Referer<T>> referers = getReferers();

        int refererSize = referers.size();
        int startIndex = ThreadLocalRandom.current().nextInt(refererSize);
        int currentCursor = 0;
        int currentAvailableCursor = 0;

        Referer<T> referer = null;

        while (currentAvailableCursor < MAX_REFERER_COUNT && currentCursor < refererSize) {
            Referer<T> temp = referers.get((startIndex + currentCursor) % refererSize);
            currentCursor++;

            if (!temp.isAvailable()) {
                continue;
            }

            currentAvailableCursor++;

            if (referer == null) {
                referer = temp;
            } else {
                if (compare(referer, temp) > 0) {
                    referer = temp;
                }
            }
        }

        return referer;
    }

    @Override
    protected void doSelectToHolder(Request request, List<Referer<T>> refersHolder) {
        List<Referer<T>> referers = getReferers();

        int refererSize = referers.size();
        int startIndex = ThreadLocalRandom.current().nextInt(refererSize);
        int currentCursor = 0;
        int currentAvailableCursor = 0;

        while (currentAvailableCursor < MAX_REFERER_COUNT && currentCursor < refererSize) {
            Referer<T> temp = referers.get((startIndex + currentCursor) % refererSize);
            currentCursor++;

            if (!temp.isAvailable()) {
                continue;
            }

            currentAvailableCursor++;

            refersHolder.add(temp);
        }

        Collections.sort(refersHolder, new LowActivePriorityComparator<T>());
    }

    private int compare(Referer<T> referer1, Referer<T> referer2) {
        return referer1.activeRefererCount() - referer2.activeRefererCount();
    }

    static class LowActivePriorityComparator<T> implements Comparator<Referer<T>> {
        @Override
        public int compare(Referer<T> referer1, Referer<T> referer2) {
            return referer1.activeRefererCount() - referer2.activeRefererCount();
        }
    }

}

一致hash算法

  • 一致性hash算法,是通過某個hash函數,把同一個來源的請求都映射到同一個節點上。
/*
 *  Copyright 2009-2016 Weibo, Inc.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package com.weibo.api.motan.cluster.loadbalance;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import com.weibo.api.motan.common.MotanConstants;
import com.weibo.api.motan.core.extension.SpiMeta;
import com.weibo.api.motan.rpc.Referer;
import com.weibo.api.motan.rpc.Request;
import com.weibo.api.motan.util.MathUtil;

/**
 * 
 * Use consistent hash to choose referer
 *
 * @author fishermen
 * @version V1.0 created at: 2013-5-21
 */
@SpiMeta(name = "consistent")
public class ConsistentHashLoadBalance<T> extends AbstractLoadBalance<T> {

    private List<Referer<T>> consistentHashReferers;

    @Override
    public void onRefresh(List<Referer<T>> referers) {
        super.onRefresh(referers);

        List<Referer<T>> copyReferers = new ArrayList<Referer<T>>(referers);
        List<Referer<T>> tempRefers = new ArrayList<Referer<T>>();
        for (int i = 0; i < MotanConstants.DEFAULT_CONSISTENT_HASH_BASE_LOOP; i++) {
            Collections.shuffle(copyReferers);
            for (Referer<T> ref : copyReferers) {
                tempRefers.add(ref);
            }
        }

        consistentHashReferers = tempRefers;
    }

    @Override
    protected Referer<T> doSelect(Request request) {

        int hash = getHash(request);
        Referer<T> ref;
        for (int i = 0; i < getReferers().size(); i++) {
            ref = consistentHashReferers.get((hash + i) % consistentHashReferers.size());
            if (ref.isAvailable()) {
                return ref;
            }
        }
        return null;
    }

    @Override
    protected void doSelectToHolder(Request request, List<Referer<T>> refersHolder) {
        List<Referer<T>> referers = getReferers();

        int hash = getHash(request);
        for (int i = 0; i < referers.size(); i++) {
            Referer<T> ref = consistentHashReferers.get((hash + i) % consistentHashReferers.size());
            if (ref.isAvailable()) {
                refersHolder.add(ref);
            }
        }
    }

    private int getHash(Request request) {
        int hashcode;
        if (request.getArguments() == null || request.getArguments().length == 0) {
            hashcode = request.hashCode();
        } else {
            hashcode = Arrays.hashCode(request.getArguments());
        }
        return MathUtil.getNonNegative(hashcode);
    }


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