Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: speed up the first loading of namespace when startup meet 404 #61

Merged
merged 10 commits into from
May 18, 2024
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Apollo Java 2.3.0
* [add an initialize method to avoid DefaultProviderManager's logic being triggered when using custom ProviderManager.](https://github.com/apolloconfig/apollo-java/pull/50)
* [Implement parsing time based on pattern for @ApolloJsonValue](https://github.com/apolloconfig/apollo-java/pull/53)
* [Enhance to load mocked properties from apollo.cache-dir](https://github.com/apolloconfig/apollo-java/pull/58)
* [perf: speed up the first loading of namespace when startup meet 404](https://github.com/apolloconfig/apollo-java/pull/61)

------------------
All issues and pull requests are [here](https://github.com/apolloconfig/apollo-java/milestone/3?closed=1)
5 changes: 5 additions & 0 deletions apollo-client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@
<artifactId>log4j-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mock-server</groupId>
<artifactId>mockserver-netty</artifactId>
<scope>test</scope>
</dependency>
<!-- end of test -->
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,9 @@ protected void fireRepositoryChange(String namespace, Properties newProperties)
}
}
}

@Override
public void initialize() {
this.sync();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it better to call trySync?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change to trySync here will slower startup. Let me try to illustrate it.


1. everything from the rate limiter in RemoteConfigRepository

If we do not wait for 2 consecutive calls to method sync, the rateLimter in RemoteConfigRepository will block current thread 500ms on the second call because code m_loadConfigRateLimiter.tryAcquire(5, TimeUnit.SECONDS), that is why so slower when startup.

if (!m_loadConfigRateLimiter.tryAcquire(5, TimeUnit.SECONDS)) {
//wait at most 5 seconds
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
}
}

sequenceDiagram
RemoteConfigRepository ->> RemoteConfigRepository: sync
RemoteConfigRepository ->> RemoteConfigRepository: loadApolloConfig
RemoteConfigRepository ->> m_loadConfigRateLimiter: tryAcquire(5, TimeUnit.SECONDS)
alt if Acquire success
	m_loadConfigRateLimiter -->> RemoteConfigRepository: return immediately
else Acquire fail
	m_loadConfigRateLimiter -->> RemoteConfigRepository: block current thread 500ms
end
Loading

2. how to solve it?

There are 2 solution to speed up.

solution 1: forbid 2 consecutive calls to method sync,

solution 2: use another method on m_loadConfigRateLimiter let it won't block current thread 500ms .


3. Back to talk about method initialize in DefaultConfig.

According to the code

private void initialize() {
try {
updateConfig(m_configRepository.getConfig(), m_configRepository.getSourceType());
} catch (Throwable ex) {
Tracer.logError(ex);
logger.warn("Init Apollo Local Config failed - namespace: {}, reason: {}.",
m_namespace, ExceptionUtil.getDetailMessage(ex));

m_configRepository.getConfig() will invoke method sync.

When we change to code

  private void initialize() {
    try {
      m_configRepository.initialize();
      updateConfig(m_configRepository.getConfig(), m_configRepository.getSourceType());
    } catch (Throwable ex) {
      Tracer.logError(ex);
      logger.warn("Init Apollo Local Config failed - namespace: {}, reason: {}.",
          m_namespace, ExceptionUtil.getDetailMessage(ex));
    } finally {

m_configRepository.initialize(); and m_configRepository.getConfig() alway invoke method sync,
i.e 2 consecutive calls to method sync, that will let startup slower.

But the different in m_configRepository.initialize();, if we use sync instead of trySync,
the exception will throw quickly, so updateConfig(m_configRepository.getConfig(), m_configRepository.getSourceType()); won't execute,

i.e choose solution 1: forbid 2 consecutive calls to method sync to speed up.

That's why use sync instead of trySync here.

Maybe there is better solution here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with you. Let's keep it as it is.

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,9 @@ public interface ConfigRepository {
* @return the config's source type
*/
ConfigSourceType getSourceType();

/**
* Initialize the repository.
*/
void initialize();
Anilople marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public DefaultConfig(String namespace, ConfigRepository configRepository) {

private void initialize() {
try {
m_configRepository.initialize();
updateConfig(m_configRepository.getConfig(), m_configRepository.getSourceType());
} catch (Throwable ex) {
Tracer.logError(ex);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ public LocalFileConfigRepository(String namespace, ConfigRepository upstream) {
m_configUtil = ApolloInjector.getInstance(ConfigUtil.class);
this.setLocalCacheDir(findLocalCacheDir(), false);
this.setUpstreamRepository(upstream);
this.trySync();
}

void setLocalCacheDir(File baseDir, boolean syncImmediately) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ public RemoteConfigRepository(String namespace) {
m_configNeedForceRefresh = new AtomicBoolean(true);
m_loadConfigFailSchedulePolicy = new ExponentialSchedulePolicy(m_configUtil.getOnErrorRetryInterval(),
m_configUtil.getOnErrorRetryInterval() * 8);
this.trySync();
this.schedulePeriodicRefresh();
this.scheduleLongPollingRefresh();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,34 +18,19 @@

import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.core.MetaDomainConsts;
import com.ctrip.framework.apollo.core.dto.ApolloConfig;
import com.ctrip.framework.apollo.core.dto.ServiceDTO;
import java.io.IOException;
import java.net.ServerSocket;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.junit.After;
import org.junit.Before;

import com.ctrip.framework.apollo.build.MockInjector;
import com.ctrip.framework.apollo.core.dto.ServiceDTO;
import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.core.utils.ClassLoaderUtil;
import com.ctrip.framework.apollo.util.ConfigUtil;
import com.google.common.collect.Lists;
import com.google.gson.Gson;
import org.junit.Rule;
import org.junit.rules.TestRule;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.test.util.ReflectionTestUtils;
Expand All @@ -67,25 +52,51 @@ public abstract class BaseIntegrationTest {
protected static int refreshInterval;
protected static TimeUnit refreshTimeUnit;
protected static boolean propertiesOrderEnabled;
private Server server;
private MockedConfigService mockedConfigService;
protected Gson gson = new Gson();

@Rule
public TestRule watcher = new TestWatcher() {
protected void starting(Description description) {
logger.info("Starting test: " + description.getMethodName());
}
protected MockedConfigService newMockedConfigService() {
this.mockedConfigService = new MockedConfigService(port);
this.mockedConfigService.init();
this.mockMetaServer();
return this.mockedConfigService;
}

protected void finished(Description description) {
logger.info("Finished test: " + description.getMethodName());
}
};
protected void mockMetaServer() {
mockMetaServer(false);
}

protected void mockMetaServer(boolean failedAtFirstTime) {
final ServiceDTO someServiceDTO = new ServiceDTO();
someServiceDTO.setAppName(someAppName);
someServiceDTO.setInstanceId(someInstanceId);
someServiceDTO.setHomepageUrl(configServiceURL);
this.mockedConfigService.mockMetaServer(failedAtFirstTime, someServiceDTO);
}

public void mockConfigs(
int mockedStatusCode,
ApolloConfig apolloConfig
) {
this.mockConfigs(false, mockedStatusCode, apolloConfig);
}

public void mockConfigs(
boolean failedAtFirstTime,
int mockedStatusCode,
ApolloConfig apolloConfig
) {
this.mockedConfigService.mockConfigs(
failedAtFirstTime, mockedStatusCode, apolloConfig
);
}

@Before
@BeforeEach
public void setUp() throws Exception {
someAppId = "1003171";
someClusterName = "someClusterName";
someDataCenter = "someDC";

refreshInterval = 5;
refreshTimeUnit = TimeUnit.MINUTES;
propertiesOrderEnabled = false;
Expand All @@ -99,71 +110,20 @@ public void setUp() throws Exception {
MockInjector.setInstance(ConfigUtil.class, new MockConfigUtil());
}

@After
@AfterEach
public void tearDown() throws Exception {
//as ConfigService is singleton, so we must manually clear its container
ConfigService.reset();
MockInjector.reset();
System.clearProperty(ConfigConsts.APOLLO_META_KEY);
ReflectionTestUtils.invokeMethod(MetaDomainConsts.class, "reset");

if (server != null && server.isStarted()) {
server.stop();
if (mockedConfigService != null) {
mockedConfigService.close();
mockedConfigService = null;
}
}

/**
* init and start a jetty server, remember to call server.stop when the task is finished
*
* @param handlers
* @throws Exception
*/
protected Server startServerWithHandlers(ContextHandler... handlers) throws Exception {
server = new Server(port);

ContextHandlerCollection contexts = new ContextHandlerCollection();
contexts.setHandlers(handlers);
contexts.addHandler(mockMetaServerHandler());

server.setHandler(contexts);
server.start();

return server;
}

protected ContextHandler mockMetaServerHandler() {
return mockMetaServerHandler(false);
}

protected ContextHandler mockMetaServerHandler(final boolean failedAtFirstTime) {
final ServiceDTO someServiceDTO = new ServiceDTO();
someServiceDTO.setAppName(someAppName);
someServiceDTO.setInstanceId(someInstanceId);
someServiceDTO.setHomepageUrl(configServiceURL);
final AtomicInteger counter = new AtomicInteger(0);

ContextHandler context = new ContextHandler("/services/config");
context.setHandler(new AbstractHandler() {
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException {
if (failedAtFirstTime && counter.incrementAndGet() == 1) {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
baseRequest.setHandled(true);
return;
}
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_OK);

response.getWriter().println(gson.toJson(Lists.newArrayList(someServiceDTO)));

baseRequest.setHandled(true);
}
});

return context;
}

protected void setRefreshInterval(int refreshInterval) {
BaseIntegrationTest.refreshInterval = refreshInterval;
}
Expand Down
Loading
Loading