Przeglądaj źródła

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

wxq 2 lat temu
rodzic
commit
1c4b38e856

+ 1 - 0
CHANGES.md

@@ -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)

+ 12 - 5
apollo-client/src/main/java/com/ctrip/framework/apollo/internals/RemoteConfigLongPollService.java

@@ -28,6 +28,7 @@ import com.ctrip.framework.apollo.core.signature.Signature;
 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;
@@ -35,6 +36,7 @@ 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;
@@ -50,10 +52,10 @@ import com.google.gson.Gson;
 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;
@@ -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.
@@ -153,7 +157,6 @@ public class RemoteConfigLongPollService {
   }
 
   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)) {
@@ -167,8 +170,7 @@ public class RemoteConfigLongPollService {
       String url = null;
       try {
         if (lastServiceDto == null) {
-          List<ServiceDTO> configServices = getConfigServices();
-          lastServiceDto = configServices.get(random.nextInt(configServices.size()));
+          lastServiceDto = this.resolveConfigService();
         }
 
         url =
@@ -198,7 +200,7 @@ public class RemoteConfigLongPollService {
         }
 
         //try to load balance
-        if (response.getStatusCode() == 304 && random.nextBoolean()) {
+        if (response.getStatusCode() == 304 && ThreadLocalRandom.current().nextBoolean()) {
           lastServiceDto = null;
         }
 
@@ -324,6 +326,11 @@ public class RemoteConfigLongPollService {
     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) {

+ 34 - 0
apollo-client/src/main/java/com/ctrip/framework/apollo/spi/ConfigServiceLoadBalancerClient.java

@@ -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);
+}

+ 46 - 0
apollo-client/src/main/java/com/ctrip/framework/apollo/spi/RandomConfigServiceLoadBalancerClient.java

@@ -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;
+  }
+}

+ 1 - 0
apollo-client/src/main/resources/META-INF/services/com.ctrip.framework.apollo.spi.ConfigServiceLoadBalancerClient

@@ -0,0 +1 @@
+com.ctrip.framework.apollo.spi.RandomConfigServiceLoadBalancerClient

+ 56 - 0
apollo-client/src/test/java/com/ctrip/framework/apollo/spi/ConfigServiceLoadBalancerClientTest.java

@@ -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);
+  }
+}

+ 58 - 0
apollo-client/src/test/java/com/ctrip/framework/apollo/spi/RandomConfigServiceLoadBalancerClientTest.java

@@ -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 - 0
docs/en/usage/java-sdk-user-guide.md

@@ -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 .

+ 18 - 2
docs/zh/usage/java-sdk-user-guide.md

@@ -3,7 +3,7 @@
 # &nbsp;
 # 一、准备工作
 ## 1.1 环境要求
-    
+
 * Java: 1.8+
     * 如需运行在 Java 1.7 运行时环境,请使用 1.x 版本的 apollo 客户端,如 1.9.1
 * Guava: 20.0+
@@ -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
@@ -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,输出是1个ConfigService。
+
+默认服务提供是`com.ctrip.framework.apollo.spi.RandomConfigServiceLoadBalancerClient`,使用random策略,也就是随机从多个ConfigService中选择1个ConfigService。