spring Cloud 2021 Alibaba OpenFeign 使用 Ribbon 的负载均衡
引入依赖
<!-- nacos 注册与发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 由于 spring Cloud 2021 移除了ribbon 作为负载均衡器,
取而代之的是 springLoadbalancer所以需要引入该依赖,而openFei也随之默认支持了
springLoadbalancer -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>
<!-- ribbon 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
<!-- openFeign 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
至于为什么要引入 spring-cloud-loadbalancer见 springCloud文档
spring-cloud-starter-openfeign支持spring-cloud-starter-loadbalancer。然而,作为一个可选的依赖项,如果你想使用它,你需要确保它已被添加到你的项目中。
openFeign 发起请求的流程
openFeign 会调用feign.SynchronousMethodHandler#executeAndDecode -> feign.Client#execute的方法,而feign.Client默认实现为 org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient,看一下 org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient#execute方法
@Override
public Response execute(Request request, Request.Options options) throws IOException {
final URI originalUri = URI.create(request.url());
String serviceId = originalUri.getHost();
Assert.state(serviceId != null, "Request URI does not contain a valid hostname: " + originalUri);
String hint = getHint(serviceId);
DefaultRequest<RequestDataContext> lbRequest = new DefaultRequest<>(
new RequestDataContext(buildRequestData(request), hint));
Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator
.getSupportedLifecycleProcessors(
loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),
RequestDataContext.class, ResponseData.class, ServiceInstance.class);
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
ServiceInstance instance = loadBalancerClient.choose(serviceId, lbRequest);
org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse = new DefaultResponse(
instance);
if (instance == null) {
String message = "Load balancer does not contain an instance for the service " + serviceId;
if (LOG.isWarnEnabled()) {
LOG.warn(message);
}
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
.onComplete(new CompletionContext<ResponseData, ServiceInstance, RequestDataContext>(
CompletionContext.Status.DISCARD, lbRequest, lbResponse)));
return Response.builder().request(request).status(HttpStatus.SERVICE_UNAVAILABLE.value())
.body(message, StandardCharsets.UTF_8).build();
}
String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri).toString();
Request newRequest = buildRequest(request, reconstructedUrl);
LoadBalancerProperties loadBalancerProperties = loadBalancerClientFactory.getProperties(serviceId);
return executeWithLoadBalancerLifecycleProcessing(delegate, options, newRequest, lbRequest, lbResponse,
supportedLifecycleProcessors, loadBalancerProperties.isUseRawStatusCodeInResponseData());
}
主要关注 第14行的 ServiceInstance instance = loadBalancerClient.choose(serviceId, lbRequest);
这里的loadBalancerClient::LoadBalancerClient是通过 spring 注入进来的 而 loadBalancerClient::LoadBalancerClient默认实现是 org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient见 org.springframework.cloud.loadbalancer.config.BlockingLoadBalancerClientAutoConfiguration#blockingLoadBalancerClient
所以我们只需要替换掉 LoadBalancerClientbean 为Ribbon 的loadLoadBalancerClient即可
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.openfeign.loadbalancer;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Set;
import feign.Client;
import feign.Request;
import feign.Response;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.CompletionContext;
import org.springframework.cloud.client.loadbalancer.DefaultRequest;
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycle;
import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycleValidator;
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
import org.springframework.cloud.client.loadbalancer.RequestDataContext;
import org.springframework.cloud.client.loadbalancer.ResponseData;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.http.HttpStatus;
import org.springframework.util.Assert;
import static org.springframework.cloud.openfeign.loadbalancer.LoadBalancerUtils.buildRequestData;
import static org.springframework.cloud.openfeign.loadbalancer.LoadBalancerUtils.executeWithLoadBalancerLifecycleProcessing;
/**
* A {@link Client} implementation that uses {@link LoadBalancerClient} to select a
* {@link ServiceInstance} to use while resolving the request host.
*
* @author Olga Maciaszek-Sharma
* @since 2.2.0
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public class FeignBlockingLoadBalancerClient implements Client {
private static final Log LOG = LogFactory.getLog(FeignBlockingLoadBalancerClient.class);
private final Client delegate;
private final LoadBalancerClient loadBalancerClient;
private final LoadBalancerClientFactory loadBalancerClientFactory;
/**
* @deprecated in favour of
* {@link FeignBlockingLoadBalancerClient#FeignBlockingLoadBalancerClient(Client, LoadBalancerClient, LoadBalancerClientFactory)}
*/
@Deprecated
public FeignBlockingLoadBalancerClient(Client delegate, LoadBalancerClient loadBalancerClient,
LoadBalancerProperties properties, LoadBalancerClientFactory loadBalancerClientFactory) {
this.delegate = delegate;
this.loadBalancerClient = loadBalancerClient;
this.loadBalancerClientFactory = loadBalancerClientFactory;
}
public FeignBlockingLoadBalancerClient(Client delegate, LoadBalancerClient loadBalancerClient,
LoadBalancerClientFactory loadBalancerClientFactory) {
this.delegate = delegate;
this.loadBalancerClient = loadBalancerClient;
this.loadBalancerClientFactory = loadBalancerClientFactory;
}
@Override
public Response execute(Request request, Request.Options options) throws IOException {
final URI originalUri = URI.create(request.url());
String serviceId = originalUri.getHost();
Assert.state(serviceId != null, "Request URI does not contain a valid hostname: " + originalUri);
String hint = getHint(serviceId);
DefaultRequest<RequestDataContext> lbRequest = new DefaultRequest<>(
new RequestDataContext(buildRequestData(request), hint));
Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator
.getSupportedLifecycleProcessors(
loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),
RequestDataContext.class, ResponseData.class, ServiceInstance.class);
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
ServiceInstance instance = loadBalancerClient.choose(serviceId, lbRequest);
org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse = new DefaultResponse(
instance);
if (instance == null) {
String message = "Load balancer does not contain an instance for the service " + serviceId;
if (LOG.isWarnEnabled()) {
LOG.warn(message);
}
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
.onComplete(new CompletionContext<ResponseData, ServiceInstance, RequestDataContext>(
CompletionContext.Status.DISCARD, lbRequest, lbResponse)));
return Response.builder().request(request).status(HttpStatus.SERVICE_UNAVAILABLE.value())
.body(message, StandardCharsets.UTF_8).build();
}
String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri).toString();
Request newRequest = buildRequest(request, reconstructedUrl);
LoadBalancerProperties loadBalancerProperties = loadBalancerClientFactory.getProperties(serviceId);
return executeWithLoadBalancerLifecycleProcessing(delegate, options, newRequest, lbRequest, lbResponse,
supportedLifecycleProcessors, loadBalancerProperties.isUseRawStatusCodeInResponseData());
}
protected Request buildRequest(Request request, String reconstructedUrl) {
return Request.create(request.httpMethod(), reconstructedUrl, request.headers(), request.body(),
request.charset(), request.requestTemplate());
}
// Visible for Sleuth instrumentation
public Client getDelegate() {
return delegate;
}
private String getHint(String serviceId) {
LoadBalancerProperties properties = loadBalancerClientFactory.getProperties(serviceId);
String defaultHint = properties.getHint().getOrDefault("default", "default");
String hintPropertyValue = properties.getHint().get(serviceId);
return hintPropertyValue != null ? hintPropertyValue : defaultHint;
}
}
实现 ribbon的 loadLoadBalancerClient
事实上ribbon 是有实现的 LoadBalancerClient接口的,并注入的 ,见[org.springframework.cloud.client.loadbalancer.LoadBalancerClient]
但可能是 因为 netflix不更新后spring重写了LoadBalancerClient接口 而springCloud2021的choose和原来的的不太一样所以我们需要先继承 ribbon的 org.springframework.cloud.client.loadbalancer.LoadBalancerClient重写 choose方法
package com.tuling.orderopenfeignnacos.entity;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.Server;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.netflix.ribbon.*;
public class OpenFeignCompatibleRibbonLoadBalancerClient extends RibbonLoadBalancerClient {
private SpringClientFactory clientFactory;
public OpenFeignCompatibleRibbonLoadBalancerClient(SpringClientFactory clientFactory) {
super(clientFactory);
this.clientFactory = clientFactory;
}
private ServerIntrospector serverIntrospector(String serviceId) {
ServerIntrospector serverIntrospector = this.clientFactory.getInstance(serviceId,
ServerIntrospector.class);
if (serverIntrospector == null) {
serverIntrospector = new DefaultServerIntrospector();
}
return serverIntrospector;
}
private boolean isSecure(Server server, String serviceId) {
IClientConfig config = this.clientFactory.getClientConfig(serviceId);
ServerIntrospector serverIntrospector = serverIntrospector(serviceId);
return RibbonUtils.isSecure(config, serverIntrospector, server);
}
/**
* Chooses a ServiceInstance from the LoadBalancer for the specified service and
* LoadBalancer request.
*
* @param serviceId The service ID to look up the LoadBalancer.
* @param request The request to pass on to the LoadBalancer
* @return A ServiceInstance that matches the serviceId.
*/
@Override
public <T> ServiceInstance choose(String serviceId, Request<T> request) {
Server server = getServer(getLoadBalancer(serviceId), request);
if (server == null) {
return null;
}
return new RibbonServer(serviceId, server, isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
}
}
注册 OpenFeignCompatibleRibbonLoadBalancerClient
package com.tuling.orderopenfeignnacos.config;
import com.tuling.orderopenfeignnacos.entity.OpenFeignCompatibleRibbonLoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
/**
* openfeign springCloud 2021 ribbon 兼容适配
*/
@Configuration
public class OpenFeignRibbonCompatibleConfigure {
@Resource
private SpringClientFactory springClientFactory;
/**
* 参见 @see @link org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration#loadBalancerClient()
* @return
*/
@Bean
public LoadBalancerClient loadBalancerClient() {
return new OpenFeignCompatibleRibbonLoadBalancerClient(springClientFactory);
}
}

浙公网安备 33010602011771号