Skip to content

Commit

Permalink
feat(apollo-client): the spi of config service load balancer client (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
Anilople authored Jun 13, 2022
1 parent 2cc9fc8 commit 1c4b38e
Show file tree
Hide file tree
Showing 9 changed files with 239 additions and 7 deletions.
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定制

## 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,输出是1ConfigService

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

0 comments on commit 1c4b38e

Please sign in to comment.