服务端负载均衡RIBBON
-#### Ribbon集成示例
- 客户端集成
- 引入SpringCloud中的ribbon相关依赖
- 通过RibbonLoadBlanceClient的choose方法获取到服务实例
- 通过服务实例得到 ip + port 去调用服务
思考:Ribbon如何得到服务实例的详细信息?
Ribbon相关的初始化方式
RibbonAutoConfiguration:ribbon的自动装配类
LoadBalanceClient:定义了各种方法,如URI获取,服务实例选择等。(默认RibbonLoadBalancerClient)
SpringClientFactory:内部包含了各种获取对象–如loadbalancer。Instance,等方法
追踪RibbonAutoConfiguration自动装配类
- SpringClientFactory spring工厂类 探究如何做到资源隔离的
@Bean
public SpringClientFactory springClientFactory() {
// 定位1 SpringClientFactory
SpringClientFactory factory = new SpringClientFactory();
// 在工厂类中将配置文件装载进来了
factory.setConfigurations(this.configurations);
return factory;
}
- 定位1 SpringClientFactory 调用父类NamedContextFactory 的方法
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
implements DisposableBean, ApplicationContextAware {
.......... 省略代码
ApplicationContextAware 类会帮助我们拿到容器的上线文
private ApplicationContext parent; // 定义了ApplicationContext 取名parent
private Map<String, C> configurations = new ConcurrentHashMap<>();//创建了一个 ConcurrentHashMap
.......... 省略代码
@Override // 直接将 ApplicationContext 拿了进来
public void setApplicationContext(ApplicationContext parent) throws BeansException {
this.parent = parent;
}
.......... 省略代码
//在这边定义了一个 createContext方法
protected AnnotationConfigApplicationContext createContext(String name) {
// 创建了一个AnnotationConfigApplicationContext 出来 这是一个子容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 在这个上下文中传入了 name name就是我们传入的serviceid
if (this.configurations.containsKey(name)) { // 判断我们传入的名字在不在
for (Class<?> configuration : this.configurations.get(name)
.getConfiguration()) {
// 如果name存在,便利我们能够拿到的所有name 同时将我们configuration 注册到 AnnotationConfigApplicationContext 中去
context.register(configuration);
}
}
for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
// 将 default 开头的configurations.entrySet().getKey().startsWith("default.")开头的注册到AnnotationConfigApplicationContext 上下文中
if (entry.getKey().startsWith("default.")) {
for (Class<?> configuration : entry.getValue().getConfiguration()) {
context.register(configuration);
}
}
}
// 同时又进行了一系列的注册
context.register(PropertyPlaceholderAutoConfiguration.class,
this.defaultConfigType);
context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
this.propertySourceName,
Collections.<String, Object> singletonMap(this.propertyName, name)));
// 在这个地方判断 ApplicationContext 是否为空 如果不为空 将AnnotationConfigApplicationContext 注入到父容器 ApplicationContext 中
// 通过这段代码 可以知道,ribbon的资源隔离是通过父子容器的机制进行隔离的
// 父子容器:spring(父容器) 和 springMVC(子容器)
// 父子容器之间的关系: 父容器不能拿到子容器的bean 反观 子容器可以很轻松的拿到父容器的bean
if (this.parent != null) {
// Uses Environment from parent as well as beans
context.setParent(this.parent);
}
context.setDisplayName(generateDisplayName(name));
context.refresh();
return context;
}
追踪LoadBalancerClient Bean 通过断点的方式进行调试
@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient() {
return new RibbonLoadBalancerClient(springClientFactory());
}
- 断点位置
@GetMapping("index")
public Object getIndex(){
ServiceInstance serviceInstance = loadBalancerClient.choose("hello-server"); // 断点 定位1
String ip = serviceInstance.getHost();
int port = serviceInstance.getPort();
return restTemplate.getForObject("http://"+ip+":"+port,String.class,"");
}
- 定位1
@Override
public ServiceInstance choose(String serviceId) { // yml 配置的serviceId
return choose(serviceId, null);
}
public ServiceInstance choose(String serviceId, Object hint) { // yml 配置的serviceId
Server server = getServer(getLoadBalancer(serviceId), hint); // 定位2
if (server == null) {
return null;
}
return new RibbonServer(serviceId, server, isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
}
- 定位2
protected ILoadBalancer getLoadBalancer(String serviceId) {
return this.clientFactory.getLoadBalancer(serviceId); // getLoadBalancer 定位3
}
- 定位3
public ILoadBalancer getLoadBalancer(String name) {
return getInstance(name, ILoadBalancer.class); // getInstance 定位4
}
- 定位4
@Override
public <C> C getInstance(String name, Class<C> type) {
C instance = super.getInstance(name, type); // 调用父类的getInstance 定位5
if (instance != null) {
return instance;
}
IClientConfig config = getInstance(name, IClientConfig.class);
return instantiateWithConfig(getContext(name), type, config);
}
- 定位5
public <T> T getInstance(String name, Class<T> type) {
AnnotationConfigApplicationContext context = getContext(name); // 在此处获取一次context 定位6
if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
type).length > 0) {
return context.getBean(type);
}
return null;
}
- 定位6
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();// map中放入的是刚才构建的自容器
protected AnnotationConfigApplicationContext getContext(String name) {
if (!this.contexts.containsKey(name)) { // 判断 serversId存不存在
synchronized (this.contexts) {
if (!this.contexts.containsKey(name)) {
this.contexts.put(name, createContext(name)); // 如果 serversId 不存在 创建一个容器,放入到contexts 中 容器构建时,调用的是 createContext 方法 定位7
}
}
}
return this.contexts.get(name);
}
- 定位7
protected AnnotationConfigApplicationContext createContext(String name) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
if (this.configurations.containsKey(name)) {
for (Class<?> configuration : this.configurations.get(name)
.getConfiguration()) {
context.register(configuration);
}
}
for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
if (entry.getKey().startsWith("default.")) {
for (Class<?> configuration : entry.getValue().getConfiguration()) {
context.register(configuration);
}
}
}
context.register(PropertyPlaceholderAutoConfiguration.class,
this.defaultConfigType);
context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
this.propertySourceName,
Collections.<String, Object>singletonMap(this.propertyName, name)));
if (this.parent != null) {
// Uses Environment from parent as well as beans
context.setParent(this.parent);
// jdk11 issue
// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
context.setClassLoader(this.parent.getClassLoader());
}
context.setDisplayName(generateDisplayName(name));
context.refresh();
return context;
}
- 仔细追踪调用链路
追踪 getServer 链路
- 定位1
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
if (loadBalancer == null) {
return null;
}
// Use 'default' on a null hint, or just pass it on?
return loadBalancer.chooseServer(hint != null ? hint : "default"); // 定位2
}
- 定位2 ZoneAwareLoadBalancer (定位)
public Server chooseServer(Object key) {
if (ENABLED.get() && this.getLoadBalancerStats().getAvailableZones().size() > 1) { // 定义了区域
Server server = null;
try {
LoadBalancerStats lbStats = this.getLoadBalancerStats();
Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
logger.debug("Zone snapshots: {}", zoneSnapshot);
if (this.triggeringLoad == null) {
this.triggeringLoad = DynamicPropertyFactory.getInstance().getDoubleProperty("ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".triggeringLoadPerServerThreshold", 0.2D);
}
if (this.triggeringBlackoutPercentage == null) {
this.triggeringBlackoutPercentage = DynamicPropertyFactory.getInstance().getDoubleProperty("ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".avoidZoneWithBlackoutPercetage", 0.99999D);
}
Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, this.triggeringLoad.get(), this.triggeringBlackoutPercentage.get());
logger.debug("Available zones: {}", availableZones);
if (availableZones != null && availableZones.size() < zoneSnapshot.keySet().size()) {
String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);
logger.debug("Zone chosen: {}", zone);
if (zone != null) {
BaseLoadBalancer zoneLoadBalancer = this.getLoadBalancer(zone);
server = zoneLoadBalancer.chooseServer(key);
}
}
} catch (Exception var8) {
logger.error("Error choosing server using zone aware logic for load balancer={}", this.name, var8);
}
if (server != null) {
return server;
} else {
logger.debug("Zone avoidance logic is not invoked.");
return super.chooseServer(key);
}
} else { // 没有定义区域
logger.debug("Zone aware logic disabled or there is only one zone");
return super.chooseServer(key); // 定位 3
}
}
- 定位3 BaseLoadBalancer(定位)
private static final IRule DEFAULT_RULE = new RoundRobinRule(); // rule 默认是轮训方式
public Server chooseServer(Object key) { // 调用父类的chooseServer方法
if (this.counter == null) {
this.counter = this.createCounter(); // 定位5
}
this.counter.increment();
if (this.rule == null) {
return null;
} else {
try {
return this.rule.choose(key); // 调用 this.rule.choose(key) 定位5
} catch (Exception var3) {
logger.warn("LoadBalancer [{}]: Error choosing server for key {}", new Object[]{this.name, key, var3});
return null;
}
}
}
- 定位4 IRule
public interface IRule{ // 规则 可以自定义
/*
* choose one alive server from lb.allServers or
* lb.upServers according to key
*
* @return choosen Server object. NULL is returned if none
* server is available
*/
public Server choose(Object key);
public void setLoadBalancer(ILoadBalancer lb);
public ILoadBalancer getLoadBalancer();
}
- Ribbon中的负载均衡规则
负载均衡规则,在ribbon中提供了以下负载均衡规则
名称 | 描述 |
---|---|
ZoneAvoidanceRule | 区域规避策略(默认策略) |
AbstractLoadBalanceRule | 抽象celue,策略的模板 |
RoundRobinRule | 轮询策略 |
BestAvailableRule | 最小调用数 |
RandomRule | 随机数策略 |
RetryRule | 重试策略 |
WeightedResponseTimeRule | 权重策略 |
AvailabilityFilteringRule | 线程抽样策略 |
@Bean
public IRule ribbonRule(){
//自定义规则
return new RandomRule();
}
- Rule 规则的实现 上接 定位5 RandomRule
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key); // 定位6 getLoadBalancer choose 定位7
}
- 定位6
@Override
public ILoadBalancer getLoadBalancer(){
return lb;
}
- 定位7
@edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE")
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
}
Server server = null;
while (server == null) {
if (Thread.interrupted()) {
return null;
}
List<Server> upList = lb.getReachableServers();
List<Server> allList = lb.getAllServers();
int serverCount = allList.size();
if (serverCount == 0) {
/*
* No servers. End regardless of pass, because subsequent passes
* only get more restrictive.
*/
return null;
}
int index = chooseRandomInt(serverCount);
server = upList.get(index);
if (server == null) {
/*
* The only time this should happen is if the server list were
* somehow trimmed. This is a transient condition. Retry after
* yielding.
*/
Thread.yield();
continue;
}
if (server.isAlive()) { // 在这边判断服务是否存活 定位8
return (server);
}
// Shouldn't actually happen.. but must be transient or a bug.
server = null;
Thread.yield();
}
return server;
}
- 定位8 Server
public boolean isAlive() {
return isAliveFlag; // 在这边如何判断服务是否存活
}
容错机制
-
Iping机制
相当于心跳,每隔一段时间判断服务实例是否正常 -
Iping机制的装载流程
- 为引入Eureka时,加载配置文件,一旦配置没有指定Iping,默认调用new DummyPing(),全部都返回true
- 引入Eureka时,加载EurekaRibbonClientConfiguration装载一个Iping,如果自定义了一个Iping,默认使用NIWSDiscoveryPing,通过Eureka的状态返回服务是否正常。
-
重试机制
如果调用后,出现异常,可以通过重试机制,发起重试。 -
再次追踪 RibbonAutoConfiguration 的工厂类 SpringClientFactory
@Bean
public SpringClientFactory springClientFactory() {
SpringClientFactory factory = new SpringClientFactory();
factory.setConfigurations(this.configurations);
return factory;
}
- new SpringClientFactory
public SpringClientFactory() {
super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name"); // 在加载的时候构建了一个 RibbonClientConfiguration
}
- RibbonClientConfiguration
@Bean
@ConditionalOnMissingBean
public IRule ribbonRule(IClientConfig config) { //规则在此处被实例化
if (this.propertiesFactory.isSet(IRule.class, name)) { // 如果自定义了Rule(或者在配置文件中定义) 在这边会直接返回,否则 启用 ZoneAvoidanceRule
return this.propertiesFactory.get(IRule.class, config, name);
}
ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
rule.initWithNiwsConfig(config);
return rule;
}
@Bean
@ConditionalOnMissingBean
public IPing ribbonPing(IClientConfig config) { // Iping规则在此处定义
if (this.propertiesFactory.isSet(IPing.class, name)) { // 如果我们在代码中自定义了 Iping规则,会直接使用 否则,返回DummyPing
return this.propertiesFactory.get(IPing.class, config, name);
}
return new DummyPing(); // 下一步
}
- DummyPing
public class DummyPing extends AbstractLoadBalancerPing {
public DummyPing() {
}
public boolean isAlive(Server server) {
return true; // 没有自定义配置Iping规则时,isAlive会永远返回 true
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
- ribbon中引入Eureka 追踪 RibbonEurekaAutoConfiguration
@Configuration
@EnableConfigurationProperties
@ConditionalOnRibbonAndEurekaEnabled
@AutoConfigureAfter(RibbonAutoConfiguration.class)
@RibbonClients(defaultConfiguration = EurekaRibbonClientConfiguration.class) // 在此处引入了EurekaRibbonClientConfiguration
public class RibbonEurekaAutoConfiguration {
}
- EurekaRibbonClientConfiguration
@Bean
@ConditionalOnMissingBean
public IPing ribbonPing(IClientConfig config) { // 在此处帮我们修改了IPing 规则
if (this.propertiesFactory.isSet(IPing.class, serviceId)) { // 此处判断我们是否自定义 Iping规则,有直接返回,没有则使用 NIWSDiscoveryPing
return this.propertiesFactory.get(IPing.class, config, serviceId);
}
NIWSDiscoveryPing ping = new NIWSDiscoveryPing();
ping.initWithNiwsConfig(config);
return ping;
}
@Bean
@ConditionalOnMissingBean
public ServerList<?> ribbonServerList(IClientConfig config,
Provider<EurekaClient> eurekaClientProvider) { // 在此处重新定义了 ServerList
if (this.propertiesFactory.isSet(ServerList.class, serviceId)) {
return this.propertiesFactory.get(ServerList.class, config, serviceId);
}
DiscoveryEnabledNIWSServerList discoveryServerList = new DiscoveryEnabledNIWSServerList( // 在此处 Eureka 将自身的 ServerList 给到了 Ribbon 处理
config, eurekaClientProvider);
DomainExtractingServerList serverList = new DomainExtractingServerList(
discoveryServerList, config, this.approximateZoneFromHostname);
return serverList;
}
- NIWSDiscoveryPing
/**
* "Ping" Discovery Client
* i.e. we dont do a real "ping". We just assume that the server is up if Discovery Client says so
* @author stonse
*
*/
public class NIWSDiscoveryPing extends AbstractLoadBalancerPing {
BaseLoadBalancer lb = null;
public NIWSDiscoveryPing() {
}
public BaseLoadBalancer getLb() {
return lb;
}
/**
* Non IPing interface method - only set this if you care about the "newServers Feature"
* @param lb
*/
public void setLb(BaseLoadBalancer lb) {
this.lb = lb;
}
public boolean isAlive(Server server) {
boolean isAlive = true;
if (server!=null && server instanceof DiscoveryEnabledServer){ // 在此处获取服务实例 拿到所有的服务实例
DiscoveryEnabledServer dServer = (DiscoveryEnabledServer)server;
InstanceInfo instanceInfo = dServer.getInstanceInfo();
if (instanceInfo!=null){
InstanceStatus status = instanceInfo.getStatus();
if (status!=null){ // 在此处返回所有 服务实例为up状态的服务
isAlive = status.equals(InstanceStatus.UP);
}
}
}
return isAlive;
}
@Override
public void initWithNiwsConfig(
IClientConfig clientConfig) {
}
}
如何实现Ribbon的重试机制
- RibbonAutoConfiguration
@Bean
@ConditionalOnClass(
name = {"org.springframework.retry.support.RetryTemplate"}
) // 当 RetryTemplate 存在的时候,会触发重试机制 需要引入对应jar
@ConditionalOnMissingBean
public LoadBalancedRetryFactory loadBalancedRetryPolicyFactory(final SpringClientFactory clientFactory) { // 重试机制的加载
return new RibbonLoadBalancedRetryFactory(clientFactory);
}
- 引入的jar
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
@LoadBalanced 注解 restTemplate 是 spring自带的
- 断点的方式查询调用链路
@GetMapping("index")
public Object getIndex(){
// ServiceInstance serviceInstance = loadBalancerClient.choose("hello-server");
// String ip = serviceInstance.getHost();
// int port = serviceInstance.getPort();
// return restTemplate.getForObject("http://"+ip+":"+port,String.class,"");
return restTemplate.getForObject("http://hello-server",String.class,""); // 断点 定位1
}
- 定位1
@Nullable
public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {
RequestCallback requestCallback = this.acceptHeaderRequestCallback(responseType);
HttpMessageConverterExtractor<T> responseExtractor = new HttpMessageConverterExtractor(responseType, this.getMessageConverters(), this.logger);
return this.execute(url, HttpMethod.GET, requestCallback, responseExtractor, (Object[])uriVariables); // 定位2
}
- 定位2
@Nullable
public <T> T execute(String url, HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor, Object... uriVariables) throws RestClientException {
URI expanded = this.getUriTemplateHandler().expand(url, uriVariables);
return this.doExecute(expanded, method, requestCallback, responseExtractor); // 定位3
}
- 定位3
@Nullable
protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
Assert.notNull(url, "URI is required");
Assert.notNull(method, "HttpMethod is required");
ClientHttpResponse response = null;
Object var14;
try {
ClientHttpRequest request = this.createRequest(url, method);
if (requestCallback != null) {
requestCallback.doWithRequest(request);
}
response = request.execute(); // 在此处发起调用 此前都在组装request 定位4
this.handleResponse(url, method, response);
var14 = responseExtractor != null ? responseExtractor.extractData(response) : null;
} catch (IOException var12) {
String resource = url.toString();
String query = url.getRawQuery();
resource = query != null ? resource.substring(0, resource.indexOf(63)) : resource;
throw new ResourceAccessException("I/O error on " + method.name() + " request for \"" + resource + "\": " + var12.getMessage(), var12);
} finally {
if (response != null) {
response.close();
}
}
return var14;
}
- 定位4 AbstractClientHttpRequest
public final ClientHttpResponse execute() throws IOException {
this.assertNotExecuted();
ClientHttpResponse result = this.executeInternal(this.headers); // 在此处调用了一个执行方法 定位5
this.executed = true;
return result;
}
- 定位5
@Override
protected ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException {
byte[] bytes = this.bufferedOutput.toByteArray();
if (headers.getContentLength() < 0) {
headers.setContentLength(bytes.length);
}
ClientHttpResponse result = executeInternal(headers, bytes); // 在此处调用 定位6
this.bufferedOutput = new ByteArrayOutputStream(0);
return result;
}
- 定位6
@Override
protected final ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
InterceptingRequestExecution requestExecution = new InterceptingRequestExecution();
return requestExecution.execute(this, bufferedOutput); // 在此处调用了一个执行方法 定位7
}
- 定位7
@Override
public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
if (this.iterator.hasNext()) {
ClientHttpRequestInterceptor nextInterceptor = this.iterator.next(); // 在此处寻找到了一个拦截去,然后对我们的请求地址进行了一波拦截
return nextInterceptor.intercept(request, body, this); // 定位8 对请求地址进行拦截 (重试机制) 定位9 没有配置重试机制
}
else {
HttpMethod method = request.getMethod();
Assert.state(method != null, "No standard HTTP method");
ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);
request.getHeaders().forEach((key, value) -> delegate.getHeaders().addAll(key, value));
if (body.length > 0) {
if (delegate instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) delegate;
streamingOutputMessage.setBody(outputStream -> StreamUtils.copy(body, outputStream));
}
else {
StreamUtils.copy(body, delegate.getBody());
}
}
return delegate.execute();
}
}
}
- 定位8 进入了 RetryLoadBalancerInterceptor 的拦截器中 在这边 利用 拦截器的方式将请求交给ribbon的处理
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI(); // 在此处拦截到了请求地址 即我们的请求
final String serviceName = originalUri.getHost();
Assert.state(serviceName != null,
"Request URI does not contain a valid hostname: " + originalUri);
final LoadBalancedRetryPolicy retryPolicy = this.lbRetryFactory
.createRetryPolicy(serviceName, this.loadBalancer);
RetryTemplate template = createRetryTemplate(serviceName, request, retryPolicy); // 在此处创建 createRetryTemplate 在此处 做了一个自己的 template 然后进行了一波请求
return template.execute(context -> {
ServiceInstance serviceInstance = null;
if (context instanceof LoadBalancedRetryContext) {
LoadBalancedRetryContext lbContext = (LoadBalancedRetryContext) context;
serviceInstance = lbContext.getServiceInstance();
}
if (serviceInstance == null) {
serviceInstance = this.loadBalancer.choose(serviceName);
}
ClientHttpResponse response = RetryLoadBalancerInterceptor.this.loadBalancer
.execute(serviceName, serviceInstance,
this.requestFactory.createRequest(request, body, execution));
int statusCode = response.getRawStatusCode();
if (retryPolicy != null && retryPolicy.retryableStatusCode(statusCode)) {
byte[] bodyCopy = StreamUtils.copyToByteArray(response.getBody());
response.close();
throw new ClientHttpResponseStatusCodeException(serviceName, response,
bodyCopy);
}
return response;
}, new LoadBalancedRecoveryCallback<ClientHttpResponse, ClientHttpResponse>() {
// This is a special case, where both parameters to
// LoadBalancedRecoveryCallback are
// the same. In most cases they would be different.
@Override
protected ClientHttpResponse createResponse(ClientHttpResponse response,
URI uri) {
return response;
}
});
}
- 定位9 没有开启重试机制 LoadBalancerInterceptor
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null,
"Request URI does not contain a valid hostname: " + originalUri);
return this.loadBalancer.execute(serviceName,
this.requestFactory.createRequest(request, body, execution));
}