手寫通用型別負載均衡路由引擎(含隨機、輪詢、雜湊等及其帶權形式)
本文記錄了通用型別負載均衡路由引擎(工廠)的實現過程和思路。通過路由引擎獲取指定列舉型別的負載均衡器,降低了程式碼耦合,規範了各個負載均衡器的使用,減少出錯的可能,並簡化了其對應帶權負載均衡的實現(提供預設實現),而無需另外編寫和整合。
文章目錄
一 使用效果
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);
}
}