透過自定義feignclient 的LoadBalancerFeignClient 或IRule 能實現完全自定義的負載均衡策略,本文主要是透過實現自定義的LoadBalancerFeignClient而達到自定義的負載均衡策略
示例程式碼實現如下:
package cn.zuowenjun.demo;
import com.netflix.loadbalancer.Server;
import feign.Client;
import feign.Request;
import feign.Response;
import org.apache.commons.collections4.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.cloud.netflix.feign.ribbon.CachingSpringLoadBalancerFactory;
import org.springframework.cloud.netflix.feign.ribbon.LoadBalancerFeignClient;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.io.IOException;
import java.net.URL;
import java.util.*;
import java.util.stream.Collectors;
/**
* FeignClient 服務BEAN(含自定義負載均衡請求機制),注意這裡指定了configuration的型別
*/
@FeignClient(value ="zuowenjun-demo" , configuration = {DemoProviderDispatchService.Config.class})
public interface DemoProviderDispatchService {
Logger LOGGER = LoggerFactory.getLogger(FileFmsDispatchService.class);
//要RPC請求的介面,需要自定義負載均衡
@RequestMapping(value = "/fileContent/compare", method = RequestMethod.POST)
ResponseData<Integer> compare(@RequestBody FileBillCompareBO billCompareBO);
/**
* DemoProviderDispatchService 專用的配置類,在這個配置類裡面新增的BEAN均可替換全域性預設的BEAN
* 注意:此處不能加上@Configuration,否則將變成全域性配置了
*/
static class Config {
/**
* 為FileFmsDispatchClient 重新定義專用的Client,在裡面實現自定義的URL請求
*
* @param cachingFactory
* @param clientFactory
* @return
*/
@Bean
public Client requestClient(CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory) {
//獲取當前節點的ID與埠
String currentIpAndPort = CommonUtils.getCurrentIpAndPort();
//構建返回一個完全自定義的LoadBalancerFeignClient
return new LoadBalancerFeignClient(new Client.Default(null, null) {
@Override
public Response execute(Request request, Request.Options options) throws IOException {
//透過重新建立Request,將Request 中的url指向自定義負載均衡的選中的URL
Request newRequest = reCreateRequestForLoadBalance(request);
if (newRequest == null) {
return Response.builder().reason("伺服器繁忙,當前無可用服務節點").status(204).build();
}
return super.execute(newRequest, options);
}
private Request reCreateRequestForLoadBalance(Request request) throws IOException {
URL url = new URL(request.url());
//獲取可用的服務例項節點列表
List<Server> upServers = clientFactory.getLoadBalancer(“zuowenjun-demo”).getReachableServers();
Server bestServer = choose(url, upServers, url.getFile());
if (bestServer == null) {
//找不到最佳可用伺服器節點,說明所有服務節點都壓力山大
return null;
}
url = new URL(url.getProtocol(), bestServer.getHost(), bestServer.getPort(), url.getFile());
return Request.create(request.method(), url.toString(), request.headers(), request.body(), request.charset());
}
/**
* 選擇最優的服務節點
* @param url
* @param upServers
* @param apiPath
* @return
*/
private Server choose(URL url, List<Server> upServers, String apiPath) {
if (CollectionUtils.isEmpty(upServers)) {
throw new ApplicationException(500, "從註冊中心獲取不到可用的服務節點資訊");
}
apiPath=apiPath.startsWith("/")?apiPath.substring(1):apiPath;
String hashKey = Constants.LOADBALANCE_API_PREFIX + apiPath.replace("/", "_");
Boolean existRequest = RedisUtils.existHashKey(hashKey, String.format("%s:%s", url.getHost(), url.getPort()));
Server bestServer = null;
if (!Boolean.TRUE.equals(existRequest)) {
//如果當前即將請求的URL的節點之前沒有快取標記請求處理中時,則可直接複用返回
bestServer = upServers.stream().filter(s -> s.getHost().equals(url.getHost()) && s.getPort() == url.getPort()).findFirst().orElse(null);
if (bestServer != null) {
return bestServer;
}
}
//先從快取中找出當前API 的請求中的節點列表
Map<Object, Object> existRequestMap = RedisUtils.getHashEntries(hashKey);
Set<Object> existRequestIpAndPorts = new HashSet<>();
if (MapUtils.isNotEmpty(existRequestMap)) {
existRequestIpAndPorts.addAll(existRequestMap.keySet());
}
//排除API請求中的節點,保留空閒節點列表
upServers = upServers.stream().filter(s -> !existRequestIpAndPorts.contains(s.getHostPort()) && !s.getHostPort().equals(currentIpAndPort)).collect(Collectors.toList());
if (CollectionUtils.isEmpty(upServers)) {
//說明當前所有節點全部都有處理請求中,無空閒節點
return null;
}
//從空閒節點列表中隨機返回一個節點
int rndNo = new Random().nextInt(upServers.size());
bestServer = upServers.get(rndNo);
LOGGER.debug("DemoProviderDispatchService.Config.requestClient.Client#choose {}", bestServer.getHostPort());
return bestServer;
}
}, cachingFactory, clientFactory);
}
}
}
呼叫時就正常注入DemoProviderDispatchService BEAN,並使用:demoProviderDispatchService.compare(...) 即可實現在自定義負載均衡的策略下請求遠端服務API,這種自定義的負載均衡策略可以滿足特定的效能要求