本文记录了通用类型负载均衡路由引擎(工厂)的实现过程和思路。通过路由引擎获取指定枚举类型的负载均衡器,降低了代码耦合,规范了各个负载均衡器的使用,减少出错的可能,并简化了其对应带权负载均衡的实现(提供默认实现),而无需另外编写和集成。
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)
select
选择方法和 selectWithWeight
带权选择方法。其中selectWithWeight
提供默认实现并要求其参数列表元素需实现 WeightGetAble 接口。RouteStrategy利用WeightUtil提供对WeightGetAble列表的默认路由实现,但仍依赖于impl实现类。
RouteStrategyEnum管理RouteStrategy实现类 与数字的对应关系,RouteEngine 管理 RouteStrategyEnum元素 与RouteStrategy实现类 的对应关系。
用户通过指定 数字 或 RouteStrategyEnum 向 RouteEngine 索取 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));
}
}
注意这里提供了 默认实现。
/**
* @version V1.0
* @author: lin_shen
* @date: 18-11-15
* @Description: 权重接口,使用权重负载均衡的列表元素必须实现此接口
*/
public interface WeightGetAble {
default int getWeightFactors(){
return 1;
}
}
使用枚举来实现工具类单例化,具体可见:
单例模式及其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;
}
}
管理负载均衡算法和数字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;
}
}
提供按枚举或数字获取负载均衡器的方法。
注意:这里每次从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 ;
}
}
}
/**
* @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()));
}
}
这里使用了可重入锁来避免并发环境下 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;
}
}
/**
* @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);
}
}