1. 程式人生 > >手寫通用型別負載均衡路由引擎(含隨機、輪詢、雜湊等及其帶權形式)

手寫通用型別負載均衡路由引擎(含隨機、輪詢、雜湊等及其帶權形式)

本文記錄了通用型別負載均衡路由引擎(工廠)的實現過程和思路。通過路由引擎獲取指定列舉型別的負載均衡器,降低了程式碼耦合,規範了各個負載均衡器的使用,減少出錯的可能,並簡化了其對應帶權負載均衡的實現(提供預設實現),而無需另外編寫和整合。

文章目錄

一 使用效果

    public static void main(String[] args){
        ServiceInfo serviceInfo1=new ServiceInfo("A",0,3);
        ServiceInfo serviceInfo2=new ServiceInfo("B",0,1);
        ServiceInfo serviceInfo3=
new ServiceInfo("C",0,1); //原列表 List<ServiceInfo> serviceInfoList= Lists.newArrayList(serviceInfo1,serviceInfo2,serviceInfo3); //通過路由引擎獲取指定列舉型別負載均衡器(這裡是輪詢) RouteStrategy weightRouteStrategy= RouteEngine.queryClusterStrategy(RouteStrategyEnum.Polling); //使用 select 方法獲取目標元素 (這裡是輪詢10次)
for (int i = 0; i < 10; i++) { System.out.println(weightRouteStrategy.select(serviceInfoList)); } System.out.println("--------------------------------"); //注意:使用selectWithWeight的話ServiceInfo需實現 WeightGetAble 介面 (這裡是帶權輪詢10次) for (int i = 0; i < 10; i++) { System.out.println(weightRouteStrategy.selectWithWeight(serviceInfoList)); } }

結果如下,第一次使用輪詢不考慮權重,則3個元素依次輸出,第二次使用帶權輪詢,實現輸出次數與權重比一致(3:1:1)
結果

二 總體結構

1 結構圖

結構

2 元件介紹

  • RouteEngine(路由引擎)
    即負載均衡器工廠,通過該工廠獲取指定型別負載均衡策略的實現(即負載均衡器)。
  • RouteStrategy(路由策略介面)
    即負載均衡策略的介面,定義了 select選擇方法和 selectWithWeight 帶權選擇方法。其中selectWithWeight 提供預設實現並要求其引數列表元素需實現 WeightGetAble 介面。
  • RouteStrategyEnum(路由策略列舉類)
    即負載均衡策略列舉類,並使用數字code將其對應管理。
  • WeightGetAble(權重獲取介面)
    使用帶權負載均衡的物件所需實現的介面,負責提供權重因子(預設為1)。
  • WeightUtil(權重工具類)
    提供 將實現了WeightGetAble的元素列表轉化為帶權重列表 等方法。
  • impl(路由策略實現類)
    提供路由策略的具體實現

3 相互關係

RouteStrategy利用WeightUtil提供對WeightGetAble列表的預設路由實現,但仍依賴於impl實現類。

RouteStrategyEnum管理RouteStrategy實現類 與數字的對應關係,RouteEngine 管理 RouteStrategyEnum元素 與RouteStrategy實現類 的對應關係。

使用者通過指定 數字 或 RouteStrategyEnum 向 RouteEngine 索取 RouteStrategy實現類。

三 結構實現

1 RouteStrategy

路由策略的介面。預設帶權負載均衡演算法是構造新列表取代原始列表。
selectWithWeight 方法會使用到 WeightUtil和WeightGetAble。

/**
 * @version V1.0
 * @author: lin_shen
 * @date: 18-11-14
 * @Description: 路由策略介面
 */
public interface RouteStrategy {

    /**
     * 負載策略演算法
     *
     * @param primeList 原始列表
     * @return
     */
    <T> T select(List<T> primeList);

    /**
     * 帶權負載策略演算法
     *
     * @param primeList 原始列表
     * @return
     */
    default  <T extends WeightGetAble> T selectWithWeight(List<T> primeList){
        return select(WeightUtil.getWeightList(primeList));
    }
}

2 WeightGetAble

注意這裡提供了 預設實現。

/**
 * @version V1.0
 * @author: lin_shen
 * @date: 18-11-15
 * @Description: 權重介面,使用權重負載均衡的列表元素必須實現此介面
 */

public interface WeightGetAble {

    default int getWeightFactors(){
        return 1;
    }

}

3 WeightUtil

使用列舉來實現工具類單例化,具體可見:
單例模式及其4種推薦寫法和3類保護手段

getWeightList方法將構造新列表取代原列表,策略是將權重轉為次數後新增進新列表,故權重不可過大,這裡只提供了簡單實現,還可以優化。

/**
 * @version V1.0
 * @author: lin_shen
 * @date: 18-11-15
 * @Description: TODO
 */

public enum WeightUtil {
    INSTANCE;
    public static  <T extends WeightGetAble> List<T> getWeightList(List<T> primeList){
        //存放加權後列表
        List<T> weightList= Lists.newArrayList();
        for (T prime:primeList){
            //按權重轉化為次數新增進加權後列表
            //TODO:需注意權重代表列表長度,故不可過大,此處應優化
            int weight=prime.getWeightFactors();
            for(int i=0;i<weight;i++){
                weightList.add(prime);
            }
        }
        return weightList;
    }
}

4 RouteStrategyEnum

管理負載均衡演算法和數字code的關係,並提供按code獲取演算法的功能。

/**
 * @version V1.0
 * @author: lin_shen
 * @date: 18-11-14
 * @Description: 路由策略 列舉類
 */
public enum RouteStrategyEnum {

    //隨機演算法
    Random(0),
    //輪詢演算法
    Polling(1),
    //源地址hash演算法
    HashIP(2);


    private int code;

    RouteStrategyEnum(int code) {
        this.code = code;
    }

    public static RouteStrategyEnum queryByCode  (int code) {
        for (RouteStrategyEnum strategy : values()) {
            if(strategy.getCode()==code){
                return strategy;
            }
        }
        return null;
    }

    public int getCode() {
        return code;
    }
}

5 RouteEnige

提供按列舉或數字獲取負載均衡器的方法。

注意:這裡每次從RouteEngine獲取的PollingRouteStrategyImpl都是新生產的,這是因為輪詢是帶有狀態的(即輪詢到的位置index)。需要使用者自行儲存PollingRouteStrategyImpl例項以維持其狀態。

/**
 * @version V1.0
 * @author: lin_shen
 * @date: 18-11-12
 * @Description: 路由均衡引擎
 */
public class RouteEngine {
    
    private final static RouteStrategy RANDOM_ROUTE_STRATEGY_IMPL =new RandomRouteStrategyImpl();
    
    private final static RouteStrategy HASHIP_ROUTE_STRATEGY_IMPL =new HashIPRouteStrategyImpl();

    public static RouteStrategy queryClusterStrategy(int clusterStrategyCode) {
        RouteStrategyEnum clusterStrategyEnum = RouteStrategyEnum.queryByCode(clusterStrategyCode);
        return queryClusterStrategy(clusterStrategyEnum);
    }

    public static RouteStrategy queryClusterStrategy(RouteStrategyEnum routeStrategyEnum) {
        if(routeStrategyEnum==null){
            return new RandomRouteStrategyImpl();
        }
        switch (routeStrategyEnum){
            case Random:
                return RANDOM_ROUTE_STRATEGY_IMPL ;
            case Polling:
                return new PollingRouteStrategyImpl();
            case HashIP:
                return HASHIP_ROUTE_STRATEGY_IMPL ;
            default:
                return RANDOM_ROUTE_STRATEGY_IMPL ;
        }
    }

}

四 策略實現

1 隨機策略

/**
 * @version V1.0
 * @author: lin_shen
 * @date: 18-11-14
 * @Description: 隨機策略 負載均衡
 */
public class RandomRouteStrategyImpl implements RouteStrategy  {

    @Override
    public <T> T select(List<T> primeList) {
        return primeList.get(RandomUtils.nextInt(0, primeList.size()));
    }

}

2 輪詢策略

這裡使用了可重入鎖來避免併發環境下 index 引發的錯誤。

/**
 * @version V1.0
 * @author: lin_shen
 * @date: 18-11-14
 * @Description: 輪詢策略 負載均衡
 */
public class PollingRouteStrategyImpl implements RouteStrategy {

    /**
     * 計數器
     */
    private int index = 0;
    private Lock lock = new ReentrantLock();

    @Override
    public <T> T select(List<T> primeList) {
        T point=null;
        try {
            lock.tryLock(10,TimeUnit.MILLISECONDS);
            //若計數大於列表元素個數,將計數器歸0
            if (index >= primeList.size()) {
                index = 0;
            }
            point=primeList.get(index++);
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
        //保證程式健壯性,若未取到服務,則改用隨機演算法
        if (point == null) {
            point = primeList.get(RandomUtils.nextInt(0, primeList.size()));
        }
        return point;
    }
    
}

3 基於IP的雜湊策略

/**
 * @version V1.0
 * @author: lin_shen
 * @date: 18-11-14
 * @Description: 基於本地IP的雜湊策略 負載均衡
 */
public class HashIPRouteStrategyImpl implements RouteStrategy {
    
    @Override
    public <T> T select(List<T> primeList) {
        String localIP=null;
        try {
            localIP=InetAddress.getLocalHost().getHostName();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        //保證程式健壯性,若未取到域名,則採用改用隨機字串
        if(localIP==null){
            localIP= RandomUtils.nextBytes(5).toString();
        }

        //獲取源地址對應的hashcode
        int hashCode = localIP.hashCode();
        //獲取服務列表大小
        int size = primeList.size();

        return primeList.get(Math.abs(hashCode) % size);
    }
    
}