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

feat(apollo-client): the spi of config service load balancer client #4394

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ Apollo 2.1.0

------------------
* [Add a config adjust the property source overriden behavior](https://github.com/apolloconfig/apollo/pull/4409)
* [feat(apollo-client): the spi of config service load balancer client](https://github.com/apolloconfig/apollo/pull/4394)
------------------
All issues and pull requests are [here](https://github.com/apolloconfig/apollo/milestone/11?closed=1)
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@
import com.ctrip.framework.apollo.core.utils.ApolloThreadFactory;
import com.ctrip.framework.apollo.core.utils.StringUtils;
import com.ctrip.framework.apollo.exceptions.ApolloConfigException;
import com.ctrip.framework.apollo.spi.ConfigServiceLoadBalancerClient;
import com.ctrip.framework.apollo.tracer.Tracer;
import com.ctrip.framework.apollo.tracer.spi.Transaction;
import com.ctrip.framework.apollo.util.ConfigUtil;
import com.ctrip.framework.apollo.util.ExceptionUtil;
import com.ctrip.framework.apollo.util.http.HttpRequest;
import com.ctrip.framework.apollo.util.http.HttpResponse;
import com.ctrip.framework.apollo.util.http.HttpClient;
import com.ctrip.framework.foundation.internals.ServiceBootstrap;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.HashMultimap;
Expand All @@ -50,10 +52,10 @@
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
Expand Down Expand Up @@ -83,6 +85,8 @@ public class RemoteConfigLongPollService {
private ConfigUtil m_configUtil;
private HttpClient m_httpClient;
private ConfigServiceLocator m_serviceLocator;
private final ConfigServiceLoadBalancerClient configServiceLoadBalancerClient = ServiceBootstrap.loadPrimary(
ConfigServiceLoadBalancerClient.class);

/**
* Constructor.
Expand Down Expand Up @@ -153,7 +157,6 @@ void stopLongPollingRefresh() {
}

private void doLongPollingRefresh(String appId, String cluster, String dataCenter, String secret) {
final Random random = new Random();
ServiceDTO lastServiceDto = null;
while (!m_longPollingStopped.get() && !Thread.currentThread().isInterrupted()) {
if (!m_longPollRateLimiter.tryAcquire(5, TimeUnit.SECONDS)) {
Expand All @@ -167,8 +170,7 @@ private void doLongPollingRefresh(String appId, String cluster, String dataCente
String url = null;
try {
if (lastServiceDto == null) {
List<ServiceDTO> configServices = getConfigServices();
lastServiceDto = configServices.get(random.nextInt(configServices.size()));
lastServiceDto = this.resolveConfigService();
}

url =
Expand Down Expand Up @@ -198,7 +200,7 @@ private void doLongPollingRefresh(String appId, String cluster, String dataCente
}

//try to load balance
if (response.getStatusCode() == 304 && random.nextBoolean()) {
if (response.getStatusCode() == 304 && ThreadLocalRandom.current().nextBoolean()) {
lastServiceDto = null;
}

Expand Down Expand Up @@ -324,6 +326,11 @@ String assembleNotifications(Map<String, Long> notificationsMap) {
return GSON.toJson(notifications);
}

private ServiceDTO resolveConfigService() {
List<ServiceDTO> configServices = this.getConfigServices();
return this.configServiceLoadBalancerClient.chooseOneFrom(configServices);
}

private List<ServiceDTO> getConfigServices() {
List<ServiceDTO> services = m_serviceLocator.getConfigServices();
if (services.size() == 0) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright 2022 Apollo 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
*
* http://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 com.ctrip.framework.apollo.spi;

import com.ctrip.framework.apollo.core.dto.ServiceDTO;
import com.ctrip.framework.apollo.core.spi.Ordered;
import com.ctrip.framework.apollo.internals.ConfigServiceLocator;
import java.util.List;

public interface ConfigServiceLoadBalancerClient extends Ordered {

/**
* choose 1 config service from multiple service instances
*
* @param configServices the return of {@link ConfigServiceLocator#getConfigServices()}
* @return return 1 config service chosen, null if there is no unavailable config service
* @throws IllegalArgumentException if arg is null of empty
*/
ServiceDTO chooseOneFrom(List<ServiceDTO> configServices);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2022 Apollo 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
*
* http://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 com.ctrip.framework.apollo.spi;

import com.ctrip.framework.apollo.core.dto.ServiceDTO;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

/**
* default service provider of {@link ConfigServiceLoadBalancerClient}
*/
public class RandomConfigServiceLoadBalancerClient implements ConfigServiceLoadBalancerClient {

private static final int ORDER = 0;

@Override
public ServiceDTO chooseOneFrom(List<ServiceDTO> configServices) {
if (null == configServices) {
throw new IllegalArgumentException("arg is null");
}
if (configServices.isEmpty()) {
throw new IllegalArgumentException("arg is empty");
}
int index = ThreadLocalRandom.current().nextInt(configServices.size());
return configServices.get(index);
}

@Override
public int getOrder() {
return ORDER;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
com.ctrip.framework.apollo.spi.RandomConfigServiceLoadBalancerClient
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright 2022 Apollo 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
*
* http://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 com.ctrip.framework.apollo.spi;

import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import com.ctrip.framework.foundation.internals.ServiceBootstrap;
import java.util.ArrayList;
import java.util.Iterator;
import org.junit.jupiter.api.Test;

class ConfigServiceLoadBalancerClientTest {

/**
* all {@link ConfigServiceLoadBalancerClient}'s implementations need to conform it.
*/
@Test
void chooseOneFrom() {
Iterator<ConfigServiceLoadBalancerClient> loadBalancerClientIterator =
ServiceBootstrap.loadAll(ConfigServiceLoadBalancerClient.class);
while (loadBalancerClientIterator.hasNext()) {
ConfigServiceLoadBalancerClient configServiceLoadBalancerClient = loadBalancerClientIterator.next();
expectException(configServiceLoadBalancerClient);
}
}

private static void expectException(ConfigServiceLoadBalancerClient loadBalancerClient) {
// arg is null
assertThrows(IllegalArgumentException.class, () -> loadBalancerClient.chooseOneFrom(null));
// arg is empty
assertThrows(IllegalArgumentException.class,
() -> loadBalancerClient.chooseOneFrom(new ArrayList<>()));
}

@Test
void classTypeMatch() {
ConfigServiceLoadBalancerClient loadBalancerClient =
ServiceBootstrap.loadPrimary(ConfigServiceLoadBalancerClient.class);
assertTrue(loadBalancerClient instanceof RandomConfigServiceLoadBalancerClient);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright 2022 Apollo 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
*
* http://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 com.ctrip.framework.apollo.spi;

import static org.junit.jupiter.api.Assertions.assertTrue;

import com.ctrip.framework.apollo.core.dto.ServiceDTO;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;

public class RandomConfigServiceLoadBalancerClientTest {

@Test
public void chooseOneFrom() {
ConfigServiceLoadBalancerClient loadBalancerClient = new RandomConfigServiceLoadBalancerClient();
List<ServiceDTO> configServices = generateConfigServices();
for (int i = 0; i < 100; i++) {
ServiceDTO serviceDTO = loadBalancerClient.chooseOneFrom(configServices);
// always contains it
assertTrue(configServices.contains(serviceDTO));
}
}

private static List<ServiceDTO> generateConfigServices() {
List<ServiceDTO> configServices = new ArrayList<>();
{
ServiceDTO serviceDTO = new ServiceDTO();
serviceDTO.setAppName("appName1");
configServices.add(serviceDTO);
}
{
ServiceDTO serviceDTO = new ServiceDTO();
serviceDTO.setAppName("appName2");
configServices.add(serviceDTO);
}
{
ServiceDTO serviceDTO = new ServiceDTO();
serviceDTO.setAppName("appName3");
configServices.add(serviceDTO);
}
return configServices;
}
}
13 changes: 13 additions & 0 deletions docs/en/usage/java-sdk-user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -1269,3 +1269,16 @@ public class SpringIntegrationTest {
}
```

# Ⅶ. apollo-client customization

## 7.1 ConfigService load balancing algorithm

> from version 2.1.0

To satisfy users' different demands on ConfigService load balancing algorithm when using apollo-client, we provide **spi** since version 2.1.0

The interface is `com.ctrip.framework.apollo.spi.ConfigServiceLoadBalancerClient`.

The Input is multiple ConfigServices returned by meta server, and the output is a ConfigService selected.

The default service provider is `com.ctrip.framework.apollo.spi.RandomConfigServiceLoadBalancerClient`, which chooses one ConfigService from multiple ConfigServices using random strategy .
20 changes: 18 additions & 2 deletions docs/zh/usage/java-sdk-user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# &nbsp;
# 一、准备工作
## 1.1 环境要求

* Java: 1.8+
* 如需运行在 Java 1.7 运行时环境,请使用 1.x 版本的 apollo 客户端,如 1.9.1
* Guava: 20.0+
Expand Down Expand Up @@ -638,7 +638,7 @@ Spring Boot除了支持上述两种集成方式以外,还支持通过applicati
# will inject 'application' namespace in bootstrap phase
apollo.bootstrap.enabled = true
```

2. 注入非默认`application` namespace或多个namespace的配置示例
```properties
apollo.bootstrap.enabled = true
Expand Down Expand Up @@ -1204,3 +1204,19 @@ public class SpringIntegrationTest {
}
}
```

# 七、apollo-client定制
nobodyiam marked this conversation as resolved.
Show resolved Hide resolved

## 7.1 ConfigService负载均衡算法

> from version 2.1.0

为了满足用户使用apollo-client时,对ConfigService负载均衡算法的不同需求,

我们在2.1.0版本中提供了**spi**。

interface是`com.ctrip.framework.apollo.spi.ConfigServiceLoadBalancerClient`。

输入是meta server返回的多个ConfigService,输出是1个ConfigService。

默认服务提供是`com.ctrip.framework.apollo.spi.RandomConfigServiceLoadBalancerClient`,使用random策略,也就是随机从多个ConfigService中选择1个ConfigService。