背景
gateway中多实例请求转发,默认采用轮训转发策略。在有些场景下,某些请求想固定到某一台实例上,这里通过重写默认负载均衡策略的方式实现。
以下代码为,大文件分片上传,多实例场景,根据文件md5和实例总数取模,选取处理服务实例。保证同一文件在固定实例上进行处理,保证最后的文件合并不会有问题。
实现
Gateway有两种客户端负载均衡器,LoadBalancerClientFilter和ReactiveLoadBalancerClientFilter。LoadBalancerClientFilter使用一个Ribbon的阻塞式LoadBalancerClient,Gateway建议使用ReactiveLoadBalancerClientFilter。
以下通过重写LoadBalancerClientFilter实现。
重写代码
public class IdUploadLoadBalancerClientFilter extends LoadBalancerClientFilter {
private static final Log log = LogFactory.getLog(IdUploadLoadBalancerClientFilter.class);
private static final String ID_INTEGRATION = "id-integration";
private static final List<String> UPLOAD_URL_LIST = Lists.newArrayList("/v5/base/access/data/multipart/upload", "/v5/base/access/data/multipart/merge");
private LoadBalancerProperties properties;
@Resource
private NacosDiscoveryClient nacosDiscoveryClient;
public IdUploadLoadBalancerClientFilter(LoadBalancerClient loadBalancer, LoadBalancerProperties properties) {
super(loadBalancer, properties);
this.properties = properties;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
String schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR);
if (url == null
|| (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) {
return chain.filter(exchange);
}
// preserve the original url
addOriginalRequestUrl(exchange, url);
if (log.isTraceEnabled()) {
log.trace("LoadBalancerClientFilter url before: " + url);
}
ServiceInstance instance = choose(exchange);
String serviceId = instance.getServiceId();
//判断当前实例id是否为id-integration
if (StringUtils.equalsIgnoreCase(serviceId, ID_INTEGRATION)) {
ServerHttpRequest request = exchange.getRequest();
HttpMethod httpMethod = request.getMethod();
String path = request.getURI().getPath();
String contentType = String.join(";", Objects.requireNonNull(request.getHeaders().get("Content-Type")));
if (httpMethod != null && httpMethod.matches(HttpMethod.POST.name()) && UPLOAD_URL_LIST.contains(path)) {
boolean isUploadLoad = StringUtils.containsIgnoreCase(contentType, MediaType.MULTIPART_FORM_DATA_VALUE);
String fileMd5 = null;
if (isUploadLoad) {
fileMd5 = String.join(";", Objects.requireNonNull(request.getHeaders().get("fileMd5")));
} else {
List<String> fileMd5List = request.getQueryParams().get("fileMd5");
if (fileMd5List != null && fileMd5List.size() > 0) {
fileMd5 = fileMd5List.get(0);
}
}
if (StringUtils.isNotBlank(fileMd5)) {
List<ServiceInstance> instances = nacosDiscoveryClient.getInstances(ID_INTEGRATION);
int targetIndex = Math.abs(fileMd5.hashCode()) % instances.size();
ServiceInstance serviceInstance = instances.get(targetIndex);
RibbonLoadBalancerClient.RibbonServer ribbonServer = (RibbonLoadBalancerClient.RibbonServer) instance;
NacosServer nacosServer = (NacosServer) ribbonServer.getServer();
nacosServer.setHost(serviceInstance.getHost());
nacosServer.setPort(serviceInstance.getPort());
Map<String, String> metadata = ribbonServer.getMetadata();
boolean secure = ribbonServer.isSecure();
instance = new RibbonLoadBalancerClient.RibbonServer(serviceId, nacosServer, secure, metadata);
}
}
}
if (instance == null) {
throw NotFoundException.create(properties.isUse404(),
"Unable to find instance for " + url.getHost());
}
URI uri = exchange.getRequest().getURI();
// if the `lb:<scheme>` mechanism was used, use `<scheme>` as the default,
// if the loadbalancer doesn't provide one.
String overrideScheme = instance.isSecure() ? "https" : "http";
if (schemePrefix != null) {
overrideScheme = url.getScheme();
}
URI requestUrl = loadBalancer.reconstructURI(
new DelegatingServiceInstance(instance, overrideScheme), uri);
if (log.isTraceEnabled()) {
log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
}
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
return chain.filter(exchange);
}
}
bean配置
@Bean
public LoadBalancerClientFilter loadBalancerClientFilter(LoadBalancerClient client,
LoadBalancerProperties properties) {
return new IdUploadLoadBalancerClientFilter(client, properties);
}