想法
在使用gateway时gateway是这样处理请求的,当一个请求到达gateway时,gateway会从路由配置中选择一个Instance将这个请求转发过去(我们的服务都是通过lb://协议接入的),在默认情况下是随机选择Instance。所以有一个想法,能不能根据Instance的负载,选择负载最小的那个Instance进行转发。我们的服务接入了promethus
或者actuator
可以通过它们获取到一个衡量服务器负载的参数。
方案
首先确定一个参数来衡量Instance的负载,这里采用Prometheus
的process_cpu_usage
这个参数,为了获取这个参数,需要每个Instance都加载promethus的插件。gateway通过访问promethus
的接口获取到process_cpu_usage
的数值,我们将相同Service的这个参数放在一个list中,然后做好排序,每次转发时选取负载最少的那个,将请求转发到这个Instance。这个list每10秒左右维护一次,重新获取下process_cpu_usage
,并且排序。但这里有一个小问题,不能保证此时Instance还在正常工作中,所以,在转发之前先发送一个请求到Instance,看看是不是正常工作中,如果不工作了,那么按照顺序选取下一个Instance。如果promethus
的接口出问题了,或者返回延迟了,那么就随机一个Instance转发。
方案总结
- 建立一个Instance的list。这个list按照
system.load.average.1m
排序。 - 建立一个定时任务维护这个list
- 自定义一个
LoadBalancerClientFilter
类,用来替换默认的Filter
,这个Filter按照list排序选择Instance转发请求
实现
promethus
的相关处理
@Data
public class InstanceData {
private Boolean death;
private ServiceInstance serviceInstance;
private Map<String, PrometheusData> prometheusDatas;
}
@Data
public class PrometheusData {
private Long timestamp;
private String name;
private String type;
private String description;
private List<MetricData> datas;
public void addMetricData(MetricData data){
if(datas==null)
datas=new ArrayList<>();
datas.add(data);
}
@Data
public static class MetricData{
private Float value;
private Map<String,String> metric;
}
}
private Map<String,List<InstanceData>> serviceDataMap=new ConcurrentHashMap<>();
private Map<String, Map<String,InstanceData>> instanceDataMap=new ConcurrentHashMap<>();
public List<InstanceData> getInstanceDataList(String servicdId){
return serviceDataMap.get(servicdId);
}
- 定义一个全局变量
instanceDataMap
保存Instance的数据(按照Service分), - 定义一个全局变量
serviceDataMap
保存Service的数据, - 定义一个类
InstanceData
,将Instance
和promethus
的返回数据封装在一起 promethus
的返回结构处理成PrometheusData
,这样基本的数据结构定义完成。- 通过
List<InstanceData> getInstanceDataList(String servicdId)
函数获取Instance的List。这个List的第一个元素就是我们转发的Instance。
@Slf4j
public class PrometheusHandler implements Function<String, List<PrometheusData>> {
@Override
public List<PrometheusData> apply(String m) {
String[] ms=m.split("[\n]");
final String[] data=new String[2];
List<PrometheusData> prometheusDatas=new ArrayList<>();
PrometheusData prometheusData=null;
for(String s:ms){
if(s.startsWith("#")){
String[] sarray=s.split("[ ]");
if(sarray[1].equals("HELP")){
prometheusData=new PrometheusData();
prometheusData.setTimestamp(System.currentTimeMillis());
prometheusDatas.add(prometheusData);
prometheusData.setName(sarray[2]);
String description=s.replace("#","").replace("HELP","").replace(sarray[2],"").trim();
prometheusData.setDescription(description);
data[0]=sarray[2];
}else