Skip to content

Commit 5df2056

Browse files
Aniloplenobodyiamcoderabbitai[bot]
authored
perf: speed up when startup meet timeout (#64)
* perf: speed up startup when meet timeout of meta service and config service lazy init * test: mockMetaSeverWithDelay simulate timeout * Update ConfigServiceLocatorTest.java * test: move some function to parent * Create ConfigServiceTimeoutIntegrationTest.java * Update CHANGES.md * Update ConfigServiceTimeoutIntegrationTest.java * Revert "perf: speed up startup when meet timeout of meta service and config service" This reverts commit a8e2070. * Update BaseIntegrationTest.java * Update MockedConfigService.java * Update ConfigServiceLocatorTest.java * feat: submit tash tryUpdateConfigServices * test: fix setup will trigger context load * Update ConfigIntegrationTest.java * feat: speed up http request when http get to meta server * feat: add rate limiter and custom timeout for discovery. * Update RemoteConfigRepositoryTest.java * Apply suggestions from code review Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --------- Co-authored-by: Jason Song <nobodyiam@gmail.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent 3f0979d commit 5df2056

File tree

9 files changed

+546
-101
lines changed

9 files changed

+546
-101
lines changed

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Apollo Java 2.3.0
99
* [Implement parsing time based on pattern for @ApolloJsonValue](https://github.com/apolloconfig/apollo-java/pull/53)
1010
* [Enhance to load mocked properties from apollo.cache-dir](https://github.com/apolloconfig/apollo-java/pull/58)
1111
* [perf: speed up the first loading of namespace when startup meet 404](https://github.com/apolloconfig/apollo-java/pull/61)
12+
* [perf: speed up when startup meets timeout](https://github.com/apolloconfig/apollo-java/pull/64)
1213

1314
------------------
1415
All issues and pull requests are [here](https://github.com/apolloconfig/apollo-java/milestone/3?closed=1)

apollo-client/src/main/java/com/ctrip/framework/apollo/internals/ConfigServiceLocator.java

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,16 @@
2121
import com.ctrip.framework.apollo.core.utils.DeferredLoggerFactory;
2222
import com.ctrip.framework.apollo.core.utils.DeprecatedPropertyNotifyUtil;
2323
import com.ctrip.framework.foundation.Foundation;
24+
import com.google.common.util.concurrent.RateLimiter;
2425
import java.lang.reflect.Type;
2526
import java.util.List;
2627
import java.util.Map;
2728
import java.util.concurrent.Executors;
2829
import java.util.concurrent.ScheduledExecutorService;
30+
import java.util.concurrent.atomic.AtomicBoolean;
2931
import java.util.concurrent.atomic.AtomicReference;
3032

3133
import org.slf4j.Logger;
32-
import org.slf4j.LoggerFactory;
3334

3435
import com.ctrip.framework.apollo.build.ApolloInjector;
3536
import com.ctrip.framework.apollo.core.dto.ServiceDTO;
@@ -57,6 +58,13 @@ public class ConfigServiceLocator {
5758
private AtomicReference<List<ServiceDTO>> m_configServices;
5859
private Type m_responseType;
5960
private ScheduledExecutorService m_executorService;
61+
/**
62+
* forbid submit multiple task to {@link #m_executorService},
63+
* <p>
64+
* so use this AtomicBoolean as a signal
65+
*/
66+
protected AtomicBoolean discoveryTaskQueueMark;
67+
private RateLimiter m_discoveryRateLimiter;
6068
private static final Joiner.MapJoiner MAP_JOINER = Joiner.on("&").withKeyValueSeparator("=");
6169
private static final Escaper queryParamEscaper = UrlEscapers.urlFormParameterEscaper();
6270

@@ -72,6 +80,8 @@ public ConfigServiceLocator() {
7280
m_configUtil = ApolloInjector.getInstance(ConfigUtil.class);
7381
this.m_executorService = Executors.newScheduledThreadPool(1,
7482
ApolloThreadFactory.create("ConfigServiceLocator", true));
83+
this.discoveryTaskQueueMark = new AtomicBoolean(false);
84+
this.m_discoveryRateLimiter = RateLimiter.create(m_configUtil.getDiscoveryQPS());
7585
initConfigServices();
7686
}
7787

@@ -154,14 +164,39 @@ private String getDeprecatedCustomizedConfigService() {
154164
return configServices;
155165
}
156166

167+
void doSubmitUpdateTask() {
168+
m_executorService.submit(() -> {
169+
boolean needUpdate = this.discoveryTaskQueueMark.getAndSet(false);
170+
if (needUpdate) {
171+
this.tryUpdateConfigServices();
172+
}
173+
});
174+
}
175+
176+
void trySubmitUpdateTask() {
177+
// forbid multiple submit in a short period
178+
boolean needForceAlready = this.discoveryTaskQueueMark.getAndSet(true);
179+
if (needForceAlready) {
180+
// do not submit because submit already, task running
181+
} else {
182+
doSubmitUpdateTask();
183+
}
184+
}
185+
157186
/**
158187
* Get the config service info from remote meta server.
159188
*
160189
* @return the services dto
161190
*/
162191
public List<ServiceDTO> getConfigServices() {
163192
if (m_configServices.get().isEmpty()) {
164-
updateConfigServices();
193+
trySubmitUpdateTask();
194+
// quick fail
195+
throw new ApolloConfigException(
196+
"No available config service, "
197+
+ "server side maybe crash or network cannot connect to server from this ip, "
198+
+ "one of meta service url is " + assembleMetaServiceUrl()
199+
);
165200
}
166201

167202
return m_configServices.get();
@@ -190,10 +225,22 @@ public void run() {
190225
m_configUtil.getRefreshIntervalTimeUnit());
191226
}
192227

228+
synchronized boolean tryAcquireForUpdate() {
229+
return this.m_discoveryRateLimiter.tryAcquire();
230+
}
231+
193232
private synchronized void updateConfigServices() {
233+
if (!tryAcquireForUpdate()) {
234+
// quick return without wait
235+
return;
236+
}
194237
String url = assembleMetaServiceUrl();
195238

196239
HttpRequest request = new HttpRequest(url);
240+
241+
request.setConnectTimeout(m_configUtil.getDiscoveryConnectTimeout());
242+
request.setReadTimeout(m_configUtil.getDiscoveryReadTimeout());
243+
197244
int maxRetries = 2;
198245
Throwable exception = null;
199246

apollo-client/src/main/java/com/ctrip/framework/apollo/util/ConfigUtil.java

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,18 @@
3838
public class ConfigUtil {
3939

4040
private static final Logger logger = LoggerFactory.getLogger(ConfigUtil.class);
41+
42+
/**
43+
* qps limit: discovery config service from meta
44+
* <p>
45+
* 1 times per second
46+
*/
47+
private int discoveryQPS = 1;
48+
/** 1 second */
49+
private int discoveryConnectTimeout = 1000;
50+
/** 1 second */
51+
private int discoveryReadTimeout = 1000;
52+
4153
private int refreshInterval = 5;
4254
private TimeUnit refreshIntervalTimeUnit = TimeUnit.MINUTES;
4355
private int connectTimeout = 1000; //1 second
@@ -210,24 +222,63 @@ public TimeUnit getRefreshIntervalTimeUnit() {
210222
return refreshIntervalTimeUnit;
211223
}
212224

213-
private void initQPS() {
214-
String customizedLoadConfigQPS = System.getProperty("apollo.loadConfigQPS");
215-
if (!Strings.isNullOrEmpty(customizedLoadConfigQPS)) {
225+
static Integer getCustomizedIntegerValue(String systemKey) {
226+
String customizedValue = System.getProperty(systemKey);
227+
if (!Strings.isNullOrEmpty(customizedValue)) {
216228
try {
217-
loadConfigQPS = Integer.parseInt(customizedLoadConfigQPS);
229+
return Integer.parseInt(customizedValue);
218230
} catch (Throwable ex) {
219-
logger.error("Config for apollo.loadConfigQPS is invalid: {}", customizedLoadConfigQPS);
231+
logger.error("Config for {} is invalid: {}", systemKey, customizedValue);
220232
}
221233
}
234+
return null;
235+
}
222236

223-
String customizedLongPollQPS = System.getProperty("apollo.longPollQPS");
224-
if (!Strings.isNullOrEmpty(customizedLongPollQPS)) {
225-
try {
226-
longPollQPS = Integer.parseInt(customizedLongPollQPS);
227-
} catch (Throwable ex) {
228-
logger.error("Config for apollo.longPollQPS is invalid: {}", customizedLongPollQPS);
237+
private void initQPS() {
238+
{
239+
Integer value = getCustomizedIntegerValue("apollo.discoveryConnectTimeout");
240+
if (null != value) {
241+
discoveryConnectTimeout = value;
242+
}
243+
}
244+
{
245+
Integer value = getCustomizedIntegerValue("apollo.discoveryReadTimeout");
246+
if (null != value) {
247+
discoveryReadTimeout = value;
248+
}
249+
}
250+
{
251+
Integer value = getCustomizedIntegerValue("apollo.discoveryQPS");
252+
if (null != value) {
253+
discoveryQPS = value;
229254
}
230255
}
256+
257+
{
258+
Integer value = getCustomizedIntegerValue("apollo.loadConfigQPS");
259+
if (null != value) {
260+
loadConfigQPS = value;
261+
}
262+
}
263+
264+
{
265+
Integer value = getCustomizedIntegerValue("apollo.longPollQPS");
266+
if (null != value) {
267+
longPollQPS = value;
268+
}
269+
}
270+
}
271+
272+
public int getDiscoveryQPS() {
273+
return discoveryQPS;
274+
}
275+
276+
public int getDiscoveryConnectTimeout() {
277+
return discoveryConnectTimeout;
278+
}
279+
280+
public int getDiscoveryReadTimeout() {
281+
return discoveryReadTimeout;
231282
}
232283

233284
public int getLoadConfigQPS() {

apollo-client/src/test/java/com/ctrip/framework/apollo/BaseIntegrationTest.java

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,20 @@
1616
*/
1717
package com.ctrip.framework.apollo;
1818

19+
import com.ctrip.framework.apollo.build.ApolloInjector;
1920
import com.ctrip.framework.apollo.core.ConfigConsts;
2021
import com.ctrip.framework.apollo.core.MetaDomainConsts;
2122
import com.ctrip.framework.apollo.core.dto.ApolloConfig;
2223
import com.ctrip.framework.apollo.core.dto.ServiceDTO;
24+
import com.ctrip.framework.apollo.internals.RemoteConfigLongPollService;
25+
import com.google.common.base.Joiner;
26+
import java.io.File;
27+
import java.io.FileOutputStream;
2328
import java.io.IOException;
2429
import java.net.ServerSocket;
30+
import java.nio.file.Files;
31+
import java.util.Map;
32+
import java.util.Properties;
2533
import java.util.concurrent.TimeUnit;
2634

2735
import com.ctrip.framework.apollo.build.MockInjector;
@@ -39,7 +47,7 @@
3947
* @author Jason Song(song_s@ctrip.com)
4048
*/
4149
public abstract class BaseIntegrationTest {
42-
private static final Logger logger = LoggerFactory.getLogger(BaseIntegrationTest.class);
50+
protected final Logger log = LoggerFactory.getLogger(this.getClass());
4351

4452
private static final String someAppName = "someAppName";
4553
private static final String someInstanceId = "someInstanceId";
@@ -53,7 +61,11 @@ public abstract class BaseIntegrationTest {
5361
protected static TimeUnit refreshTimeUnit;
5462
protected static boolean propertiesOrderEnabled;
5563
private MockedConfigService mockedConfigService;
56-
protected Gson gson = new Gson();
64+
65+
protected final String defaultNamespace = ConfigConsts.NAMESPACE_APPLICATION;
66+
67+
private File configDir;
68+
5769

5870
protected MockedConfigService newMockedConfigService() {
5971
this.mockedConfigService = new MockedConfigService(port);
@@ -108,10 +120,22 @@ public void setUp() throws Exception {
108120
ReflectionTestUtils.invokeMethod(MetaDomainConsts.class, "reset");
109121

110122
MockInjector.setInstance(ConfigUtil.class, new MockConfigUtil());
123+
124+
configDir = new File(ClassLoaderUtil.getClassPath() + "config-cache");
125+
if (configDir.exists()) {
126+
configDir.delete();
127+
}
128+
configDir.mkdirs();
111129
}
112130

113131
@AfterEach
114132
public void tearDown() throws Exception {
133+
// get the instance will trigger long poll task execute, so move it from setup to tearDown
134+
RemoteConfigLongPollService remoteConfigLongPollService
135+
= ApolloInjector.getInstance(RemoteConfigLongPollService.class);
136+
ReflectionTestUtils.invokeMethod(remoteConfigLongPollService, "stopLongPollingRefresh");
137+
recursiveDelete(configDir);
138+
115139
//as ConfigService is singleton, so we must manually clear its container
116140
ConfigService.reset();
117141
MockInjector.reset();
@@ -124,6 +148,23 @@ public void tearDown() throws Exception {
124148
}
125149
}
126150

151+
private void recursiveDelete(File file) {
152+
if (!file.exists()) {
153+
return;
154+
}
155+
if (file.isDirectory()) {
156+
for (File f : file.listFiles()) {
157+
recursiveDelete(f);
158+
}
159+
}
160+
try {
161+
Files.deleteIfExists(file.toPath());
162+
} catch (IOException e) {
163+
e.printStackTrace();
164+
}
165+
166+
}
167+
127168
protected void setRefreshInterval(int refreshInterval) {
128169
BaseIntegrationTest.refreshInterval = refreshInterval;
129170
}
@@ -136,6 +177,38 @@ protected void setPropertiesOrderEnabled(boolean propertiesOrderEnabled) {
136177
BaseIntegrationTest.propertiesOrderEnabled = propertiesOrderEnabled;
137178
}
138179

180+
protected void createLocalCachePropertyFile(Properties properties) {
181+
createLocalCachePropertyFile(defaultNamespace, properties);
182+
}
183+
184+
protected void createLocalCachePropertyFile(String namespace, Properties properties) {
185+
String filename = assembleLocalCacheFileName(namespace);
186+
File file = new File(configDir, filename);
187+
try (FileOutputStream in = new FileOutputStream(file)) {
188+
properties.store(in, "Persisted by " + this.getClass().getSimpleName());
189+
} catch (IOException e) {
190+
throw new IllegalStateException("fail to save " + namespace + " to file", e);
191+
}
192+
}
193+
194+
private String assembleLocalCacheFileName(String namespace) {
195+
return String.format("%s.properties", Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR)
196+
.join(someAppId, someClusterName, namespace));
197+
}
198+
199+
protected ApolloConfig assembleApolloConfig(
200+
String namespace,
201+
String releaseKey,
202+
Map<String, String> configurations
203+
) {
204+
ApolloConfig apolloConfig =
205+
new ApolloConfig(someAppId, someClusterName, namespace, releaseKey);
206+
207+
apolloConfig.setConfigurations(configurations);
208+
209+
return apolloConfig;
210+
}
211+
139212
public static class MockConfigUtil extends ConfigUtil {
140213

141214
@Override

apollo-client/src/test/java/com/ctrip/framework/apollo/MockedConfigService.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
*/
3939
public class MockedConfigService implements AutoCloseable {
4040

41+
private static final String META_SERVER_PATH = "/services/config?.*";
42+
4143
private final Logger log = LoggerFactory.getLogger(this.getClass());
4244

4345
private final Gson gson = new Gson();
@@ -95,8 +97,8 @@ public void mockMetaServer(ServiceDTO ... serviceDTOList) {
9597
* @param serviceDTOList apollo meta server's response
9698
*/
9799
public void mockMetaServer(boolean failedAtFirstTime, ServiceDTO ... serviceDTOList) {
98-
final String path = "/services/config";
99-
RequestDefinition requestDefinition = HttpRequest.request("GET").withPath(path);
100+
RequestDefinition requestDefinition = HttpRequest.request("GET")
101+
.withPath(META_SERVER_PATH);
100102

101103
// need clear
102104
server.clear(requestDefinition);
@@ -118,6 +120,26 @@ public void mockMetaServer(boolean failedAtFirstTime, ServiceDTO ... serviceDTOL
118120
);
119121
}
120122

123+
/**
124+
* simulate timeout
125+
*/
126+
public void mockMetaSeverWithDelay(long milliseconds, ServiceDTO ... serviceDTOList) {
127+
RequestDefinition requestDefinition = HttpRequest.request("GET")
128+
.withPath(META_SERVER_PATH);
129+
130+
// need clear
131+
server.clear(requestDefinition);
132+
133+
String body = gson.toJson(Lists.newArrayList(serviceDTOList));
134+
server.when(requestDefinition)
135+
.respond(HttpResponse.response()
136+
.withDelay(TimeUnit.MILLISECONDS, milliseconds)
137+
.withStatusCode(HttpServletResponse.SC_OK)
138+
.withContentType(MediaType.JSON_UTF_8)
139+
.withBody(body)
140+
);
141+
}
142+
121143
public void mockConfigs(
122144
int mockedStatusCode,
123145
ApolloConfig apolloConfig

0 commit comments

Comments
 (0)