Skip to content

Commit d16e0af

Browse files
authored
feat: refactor Feign eager load, add LoadBalancer warm-up, and fix gateway trailing slash compatibility (#1800)
1 parent 2ce7202 commit d16e0af

File tree

16 files changed

+1089
-151
lines changed

16 files changed

+1089
-151
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@
66
- [feat: Support Polaris config env value and add related tests](https://github.com/Tencent/spring-cloud-tencent/pull/1797)
77
- [refactor: modify the initialization of ApplicationContextAwareUtils.](https://github.com/Tencent/spring-cloud-tencent/pull/1778)
88
- [feat: support enable/disable cloud location provider via configuration.](https://github.com/Tencent/spring-cloud-tencent/pull/1799)
9+
- [feat: refactor Feign eager load, add LoadBalancer warm-up, and fix gateway trailing slash compatibility](https://github.com/Tencent/spring-cloud-tencent/pull/1800)

spring-cloud-starter-tencent-polaris-discovery/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,11 @@
6262
<artifactId>spring-boot-actuator-autoconfigure</artifactId>
6363
<optional>true</optional>
6464
</dependency>
65+
66+
<dependency>
67+
<groupId>io.github.openfeign</groupId>
68+
<artifactId>feign-slf4j</artifactId>
69+
<scope>test</scope>
70+
</dependency>
6571
</dependencies>
6672
</project>

spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/config/PolarisEagerLoadAutoConfiguration.java

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,27 +19,33 @@
1919

2020
import com.tencent.cloud.polaris.discovery.PolarisDiscoveryClient;
2121
import com.tencent.cloud.polaris.discovery.reactive.PolarisReactiveDiscoveryClient;
22-
import com.tencent.cloud.polaris.eager.instrument.feign.FeignEagerLoadSmartLifecycle;
22+
import com.tencent.cloud.polaris.eager.instrument.feign.FeignEagerLoadContextInitializer;
23+
import com.tencent.cloud.polaris.eager.instrument.loadbalancer.LoadBalancerEagerContextInitializer;
24+
import com.tencent.cloud.polaris.eager.instrument.loadbalancer.LoadBalancerEagerLoadProperties;
2325
import com.tencent.cloud.polaris.eager.instrument.services.ServicesEagerLoadSmartLifecycle;
2426

2527
import org.springframework.beans.factory.annotation.Autowired;
2628
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2729
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
30+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
31+
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
2832
import org.springframework.context.ApplicationContext;
2933
import org.springframework.context.annotation.Bean;
3034
import org.springframework.context.annotation.Configuration;
3135

3236
@Configuration
37+
@EnableConfigurationProperties(LoadBalancerEagerLoadProperties.class)
3338
@ConditionalOnProperty(name = "spring.cloud.polaris.discovery.eager-load.enabled", havingValue = "true", matchIfMissing = true)
3439
public class PolarisEagerLoadAutoConfiguration {
3540

3641
@Bean
3742
@ConditionalOnClass(name = "feign.Feign")
3843
@ConditionalOnProperty(name = "spring.cloud.polaris.discovery.eager-load.feign.enabled", havingValue = "true", matchIfMissing = true)
39-
public FeignEagerLoadSmartLifecycle feignEagerLoadSmartLifecycle(
40-
ApplicationContext applicationContext, @Autowired(required = false) PolarisDiscoveryClient polarisDiscoveryClient,
41-
@Autowired(required = false) PolarisReactiveDiscoveryClient polarisReactiveDiscoveryClient) {
42-
return new FeignEagerLoadSmartLifecycle(applicationContext, polarisDiscoveryClient, polarisReactiveDiscoveryClient);
44+
public FeignEagerLoadContextInitializer feignEagerLoadContextInitializer(
45+
ApplicationContext applicationContext,
46+
LoadBalancerClientFactory loadBalancerClientFactory,
47+
LoadBalancerEagerLoadProperties loadBalancerEagerLoadProperties) {
48+
return new FeignEagerLoadContextInitializer(applicationContext, loadBalancerClientFactory, loadBalancerEagerLoadProperties);
4349
}
4450
@Bean
4551
@ConditionalOnProperty(name = "spring.cloud.polaris.discovery.eager-load.services.enabled", havingValue = "true", matchIfMissing = true)
@@ -48,5 +54,12 @@ public ServicesEagerLoadSmartLifecycle serviceEagerLoadSmartLifecycle(
4854
@Autowired(required = false) PolarisReactiveDiscoveryClient polarisReactiveDiscoveryClient) {
4955
return new ServicesEagerLoadSmartLifecycle(polarisDiscoveryClient, polarisReactiveDiscoveryClient);
5056
}
57+
58+
@Bean
59+
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.eager-load.enabled", matchIfMissing = true)
60+
public LoadBalancerEagerContextInitializer loadBalancerEagerContextInitializer(
61+
LoadBalancerClientFactory loadBalancerClientFactory, LoadBalancerEagerLoadProperties properties) {
62+
return new LoadBalancerEagerContextInitializer(loadBalancerClientFactory, properties.getClients());
63+
}
5164
}
5265

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/*
2+
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
3+
*
4+
* Copyright (C) 2021 Tencent. All rights reserved.
5+
*
6+
* Licensed under the BSD 3-Clause License (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* https://opensource.org/licenses/BSD-3-Clause
11+
*
12+
* Unless required by applicable law or agreed to in writing, software distributed
13+
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
14+
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
15+
* specific language governing permissions and limitations under the License.
16+
*/
17+
18+
package com.tencent.cloud.polaris.eager.instrument.feign;
19+
20+
import java.lang.reflect.Field;
21+
import java.lang.reflect.Proxy;
22+
import java.net.URI;
23+
import java.util.HashSet;
24+
import java.util.Set;
25+
26+
import com.tencent.cloud.polaris.eager.instrument.loadbalancer.LoadBalancerEagerLoadProperties;
27+
import com.tencent.cloud.polaris.eager.instrument.loadbalancer.LoadBalancerWarmUpUtils;
28+
import com.tencent.polaris.api.utils.StringUtils;
29+
import feign.Target;
30+
import org.slf4j.Logger;
31+
import org.slf4j.LoggerFactory;
32+
33+
import org.springframework.aop.framework.AopProxy;
34+
import org.springframework.aop.framework.JdkDynamicAopProxyUtils;
35+
import org.springframework.boot.context.event.ApplicationReadyEvent;
36+
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
37+
import org.springframework.cloud.openfeign.FeignClient;
38+
import org.springframework.context.ApplicationContext;
39+
import org.springframework.context.ApplicationListener;
40+
41+
/**
42+
* Feign eager load context initializer.
43+
* Implements ApplicationListener&lt;ApplicationReadyEvent&gt; to warm up FeignClient services
44+
* after the application is ready.
45+
*
46+
* @author Yuwei Fu
47+
*/
48+
public class FeignEagerLoadContextInitializer implements ApplicationListener<ApplicationReadyEvent> {
49+
50+
private static final Logger LOG = LoggerFactory.getLogger(FeignEagerLoadContextInitializer.class);
51+
52+
private final ApplicationContext applicationContext;
53+
54+
private final LoadBalancerClientFactory loadBalancerClientFactory;
55+
56+
private final LoadBalancerEagerLoadProperties loadBalancerEagerLoadProperties;
57+
58+
public FeignEagerLoadContextInitializer(ApplicationContext applicationContext,
59+
LoadBalancerClientFactory loadBalancerClientFactory,
60+
LoadBalancerEagerLoadProperties loadBalancerEagerLoadProperties) {
61+
this.applicationContext = applicationContext;
62+
this.loadBalancerClientFactory = loadBalancerClientFactory;
63+
this.loadBalancerEagerLoadProperties = loadBalancerEagerLoadProperties;
64+
}
65+
66+
public static Target.HardCodedTarget<?> getHardCodedTarget(Object proxy) {
67+
try {
68+
int count = 0;
69+
Object invocationHandler = proxy;
70+
// Avoid infinite loop
71+
while (count++ < 100) {
72+
invocationHandler = Proxy.getInvocationHandler(invocationHandler);
73+
if (invocationHandler instanceof AopProxy) {
74+
invocationHandler = JdkDynamicAopProxyUtils.getTarget(invocationHandler);
75+
continue;
76+
}
77+
break;
78+
}
79+
80+
for (Field field : invocationHandler.getClass().getDeclaredFields()) {
81+
field.setAccessible(true);
82+
Object fieldValue = field.get(invocationHandler);
83+
if (fieldValue instanceof Target.HardCodedTarget) {
84+
return (Target.HardCodedTarget<?>) fieldValue;
85+
}
86+
}
87+
}
88+
catch (Exception e) {
89+
if (LOG.isDebugEnabled()) {
90+
LOG.debug("proxy:{}, getTarget failed.", proxy, e);
91+
}
92+
}
93+
return null;
94+
}
95+
96+
@Override
97+
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
98+
LOG.info("feign eager-load start");
99+
100+
// Get services that are already warmed by LoadBalancerEagerContextInitializer
101+
Set<String> skipServices = getLoadBalancerEagerLoadServices();
102+
103+
// Set to track already warmed services
104+
Set<String> warmedServices = new HashSet<>();
105+
106+
// Warm up FeignClient services
107+
for (Object bean : applicationContext.getBeansWithAnnotation(FeignClient.class).values()) {
108+
try {
109+
if (Proxy.isProxyClass(bean.getClass())) {
110+
Target.HardCodedTarget<?> hardCodedTarget = getHardCodedTarget(bean);
111+
if (hardCodedTarget != null) {
112+
FeignClient feignClient = hardCodedTarget.type().getAnnotation(FeignClient.class);
113+
// if feignClient contains url, it doesn't need to eager load.
114+
if (StringUtils.isEmpty(feignClient.url())) {
115+
// support variables and default values.
116+
String url = hardCodedTarget.name();
117+
// refer to FeignClientFactoryBean, convert to URL, then take the host as the service name.
118+
if (!url.startsWith("http://") && !url.startsWith("https://")) {
119+
url = "http://" + url;
120+
}
121+
String serviceName = URI.create(url).getHost();
122+
123+
// Skip if already warmed by LoadBalancerEagerContextInitializer
124+
if (skipServices.contains(serviceName)) {
125+
LOG.debug("[{}] skip eager-load, already configured in LoadBalancerEagerLoadProperties.clients", serviceName);
126+
continue;
127+
}
128+
129+
// Skip if already warmed in this round
130+
if (warmedServices.contains(serviceName)) {
131+
LOG.debug("[{}] already warmed, skip.", serviceName);
132+
continue;
133+
}
134+
135+
LOG.info("[{}] eager-load start, feign name: {}", serviceName, hardCodedTarget.name());
136+
LoadBalancerWarmUpUtils.warmUp(loadBalancerClientFactory, serviceName);
137+
138+
warmedServices.add(serviceName);
139+
}
140+
}
141+
}
142+
}
143+
catch (Exception e) {
144+
LOG.debug("[{}] eager-load failed.", bean, e);
145+
}
146+
}
147+
LOG.info("feign eager-load end");
148+
}
149+
150+
/**
151+
* Get services configured in LoadBalancerEagerLoadProperties.
152+
* These services are warmed by LoadBalancerEagerContextInitializer.
153+
* @return set of service names to skip
154+
*/
155+
private Set<String> getLoadBalancerEagerLoadServices() {
156+
Set<String> services = new HashSet<>();
157+
if (loadBalancerEagerLoadProperties != null
158+
&& loadBalancerEagerLoadProperties.isEnabled()
159+
&& loadBalancerEagerLoadProperties.getClients() != null) {
160+
services.addAll(loadBalancerEagerLoadProperties.getClients());
161+
}
162+
return services;
163+
}
164+
}

spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/instrument/feign/FeignEagerLoadSmartLifecycle.java

Lines changed: 0 additions & 128 deletions
This file was deleted.

0 commit comments

Comments
 (0)