瀏覽代碼

Integrate env & service discovery in client

Yiming Liu 9 年之前
父節點
當前提交
1cc43d0880
共有 22 個文件被更改,包括 841 次插入56 次删除
  1. 3 1
      apollo-adminservice/src/main/resources/application.yml
  2. 0 7
      apollo-client/pom.xml
  3. 2 1
      apollo-client/src/main/java/com/ctrip/apollo/client/constants/Constants.java
  4. 100 0
      apollo-client/src/main/java/com/ctrip/apollo/client/env/ClientEnvironment.java
  5. 13 7
      apollo-client/src/main/java/com/ctrip/apollo/client/loader/ConfigLoaderFactory.java
  6. 39 0
      apollo-client/src/main/java/com/ctrip/apollo/client/loader/ConfigServiceLocator.java
  7. 17 4
      apollo-client/src/main/java/com/ctrip/apollo/client/loader/impl/RemoteConfigLoader.java
  8. 1 6
      apollo-client/src/main/java/com/ctrip/apollo/client/util/ConfigUtil.java
  9. 2 1
      apollo-client/src/main/resources/apollo.properties
  10. 28 16
      apollo-client/src/test/java/com/ctrip/apollo/client/loader/impl/RemoteConfigLoaderTest.java
  11. 36 7
      apollo-configservice/src/main/java/com/ctrip/apollo/metaservice/controller/ServiceController.java
  12. 4 4
      apollo-configservice/src/main/java/com/ctrip/apollo/metaservice/service/DiscoveryService.java
  13. 3 1
      apollo-configservice/src/main/resources/application.yml
  14. 6 0
      apollo-core/pom.xml
  15. 21 0
      apollo-core/src/main/java/com/ctrip/apollo/Apollo.java
  16. 42 0
      apollo-core/src/main/java/com/ctrip/apollo/core/MetaDomainConsts.java
  17. 1 1
      apollo-core/src/main/java/com/ctrip/apollo/core/ServiceNameConsts.java
  18. 34 0
      apollo-core/src/main/java/com/ctrip/apollo/core/serivce/ApolloService.java
  19. 91 0
      apollo-core/src/main/java/com/ctrip/apollo/core/utils/ApolloThreadFactory.java
  20. 23 0
      apollo-core/src/main/java/com/ctrip/apollo/core/utils/DNSUtil.java
  21. 373 0
      apollo-core/src/main/java/com/ctrip/apollo/core/utils/StringUtils.java
  22. 2 0
      apollo-portal/src/main/resources/application.yml

+ 3 - 1
apollo-adminservice/src/main/resources/application.yml

@@ -3,10 +3,12 @@ spring:
     name: apollo-adminservice
     
 server:
-  port: ${port:8080}
+  port: ${port:23646}
   
 logging:
   level:
     org.springframework.cloud: 'DEBUG'
   file: /opt/logs/apollo-adminservice.log
 
+ctrip:
+  appid: 100003172

+ 0 - 7
apollo-client/pom.xml

@@ -39,13 +39,6 @@
             <artifactId>guava</artifactId>
         </dependency>
         <!-- end of util -->
-        <!-- log -->
-        <dependency>
-            <groupId>org.slf4j</groupId>
-            <artifactId>slf4j-api</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <!-- end of log -->
         <!-- test -->
         <dependency>
             <groupId>commons-logging</groupId>

+ 2 - 1
apollo-client/src/main/java/com/ctrip/apollo/client/constants/Constants.java

@@ -4,7 +4,8 @@ package com.ctrip.apollo.client.constants;
  * @author Jason Song(song_s@ctrip.com)
  */
 public class Constants {
-    public static final String APP_ID = "appId";
+    public static final String APP_ID = "app.id";
     public static final String VERSION = "version";
     public static final String DEFAULT_VERSION_NAME = "latest-release";
+    public static final String ENV = "env";
 }

+ 100 - 0
apollo-client/src/main/java/com/ctrip/apollo/client/env/ClientEnvironment.java

@@ -0,0 +1,100 @@
+package com.ctrip.apollo.client.env;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Enumeration;
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.ctrip.apollo.Apollo;
+import com.ctrip.apollo.Apollo.Env;
+import com.ctrip.apollo.client.constants.Constants;
+import com.ctrip.apollo.core.MetaDomainConsts;
+import com.ctrip.apollo.core.utils.StringUtils;
+
+public class ClientEnvironment {
+
+  private static final Logger logger = LoggerFactory.getLogger(ClientEnvironment.class);
+
+  private final static String DEFAULT_FILE = "/apollo.properties";
+
+  private AtomicReference<Env> env = new AtomicReference<Env>();
+
+  private static ClientEnvironment instance = new ClientEnvironment();
+
+  private ClientEnvironment() {
+
+  }
+
+  public static ClientEnvironment getInstance() {
+    return instance;
+  }
+
+  public Env getEnv() {
+    if (env.get() == null) {
+      Env resultEnv = Apollo.getEnv();
+      Properties apolloProperties = null;
+      try {
+        apolloProperties = readConfigFile(DEFAULT_FILE, null);
+      } catch (IOException e) {
+        throw new IllegalArgumentException("Could not read Apollo properties");
+      }
+      if (apolloProperties != null) {
+        String strEnv = apolloProperties.getProperty(Constants.ENV);
+        if (!StringUtils.isBlank(strEnv)) {
+          resultEnv = Env.valueOf(strEnv.trim().toUpperCase());
+        }
+      }
+      env.compareAndSet(null, resultEnv);
+    }
+
+    if (env.get() == null) {
+      throw new IllegalArgumentException("Apollo env is not set");
+    }
+
+    return env.get();
+  }
+
+  public String getMetaServerDomainName() {
+    return MetaDomainConsts.getDomain(getEnv());
+  }
+
+  @SuppressWarnings("unchecked")
+  private Properties readConfigFile(String configPath, Properties defaults) throws IOException {
+    InputStream in = this.getClass().getResourceAsStream(configPath);
+    logger.info("Reading config from resource {}", configPath);
+    if (in == null) {
+      // load outside resource under current user path
+      Path path = new File(System.getProperty("user.dir") + configPath).toPath();
+      if (Files.isReadable(path)) {
+        in = new FileInputStream(path.toFile());
+        logger.info("Reading config from file {} ", path);
+      }
+    }
+    Properties props = new Properties();
+    if (defaults != null) {
+      props.putAll(defaults);
+    }
+
+    if (in != null) {
+      props.load(in);
+    }
+
+    StringBuilder sb = new StringBuilder();
+    for (Enumeration<String> e = (Enumeration<String>) props.propertyNames(); e
+        .hasMoreElements();) {
+      String key = e.nextElement();
+      String val = (String) props.getProperty(key);
+      sb.append(key).append('=').append(val).append('\n');
+    }
+    logger.info("Reading properties: \n" + sb.toString());
+    return props;
+  }
+}

+ 13 - 7
apollo-client/src/main/java/com/ctrip/apollo/client/loader/ConfigLoaderFactory.java

@@ -2,6 +2,8 @@ package com.ctrip.apollo.client.loader;
 
 import com.ctrip.apollo.client.loader.impl.InMemoryConfigLoader;
 import com.ctrip.apollo.client.loader.impl.LocalFileConfigLoader;
+import com.ctrip.apollo.Apollo.Env;
+import com.ctrip.apollo.client.env.ClientEnvironment;
 import com.ctrip.apollo.client.loader.impl.RemoteConfigLoader;
 import com.ctrip.apollo.client.util.ConfigUtil;
 import org.springframework.web.client.RestTemplate;
@@ -10,14 +12,13 @@ import org.springframework.web.client.RestTemplate;
  * @author Jason Song(song_s@ctrip.com)
  */
 public class ConfigLoaderFactory {
-    private static ConfigLoaderFactory configLoaderFactory = new ConfigLoaderFactory();
+  private static ConfigLoaderFactory configLoaderFactory = new ConfigLoaderFactory();
 
-    private ConfigLoaderFactory() {
-    }
+  private ConfigLoaderFactory() {}
 
-    public static ConfigLoaderFactory getInstance() {
-        return configLoaderFactory;
-    }
+  public static ConfigLoaderFactory getInstance() {
+    return configLoaderFactory;
+  }
 
     public ConfigLoader getLocalFileConfigLoader() {
         ConfigLoader configLoader = new LocalFileConfigLoader();
@@ -31,12 +32,17 @@ public class ConfigLoaderFactory {
     }
 
     public ConfigLoader getRemoteConfigLoader() {
-        ConfigLoader remoteConfigLoader = new RemoteConfigLoader(new RestTemplate(), ConfigUtil.getInstance());
+        ConfigLoader remoteConfigLoader = new RemoteConfigLoader(new RestTemplate(), ConfigUtil.getInstance(), new ConfigServiceLocator());
 //        remoteConfigLoader.setFallBackLoader(getInMemoryConfigLoader());
         return remoteConfigLoader;
     }
 
     public ConfigLoaderManager getConfigLoaderManager() {
+      ClientEnvironment env = ClientEnvironment.getInstance();
+      if (env.getEnv().equals(Env.LOCAL)) {
+        return new ConfigLoaderManager(getLocalFileConfigLoader(), ConfigUtil.getInstance());
+      } else {
         return new ConfigLoaderManager(getRemoteConfigLoader(), ConfigUtil.getInstance());
+      }
     }
 }

+ 39 - 0
apollo-client/src/main/java/com/ctrip/apollo/client/loader/ConfigServiceLocator.java

@@ -0,0 +1,39 @@
+package com.ctrip.apollo.client.loader;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.client.RestTemplate;
+
+import com.ctrip.apollo.client.env.ClientEnvironment;
+import com.ctrip.apollo.core.serivce.ApolloService;
+
+public class ConfigServiceLocator {
+
+  private static final Logger logger = LoggerFactory.getLogger(ConfigServiceLocator.class);
+
+  private RestTemplate restTemplate = new RestTemplate();
+
+  private List<ApolloService> serviceCaches = new ArrayList<>();
+
+  public List<ApolloService> getConfigServices() {
+    ClientEnvironment env = ClientEnvironment.getInstance();
+    String domainName = env.getMetaServerDomainName();
+    String url = domainName + "/services/config";
+    try {
+      ApolloService[] services = restTemplate.getForObject(new URI(url), ApolloService[].class);
+      if (services != null && services.length > 0) {
+        serviceCaches.clear();
+        for (ApolloService service : services) {
+          serviceCaches.add(service);
+        }
+      }
+    } catch (Exception e) {
+      logger.warn(e.getMessage());
+    }
+    return serviceCaches;
+  }
+}

+ 17 - 4
apollo-client/src/main/java/com/ctrip/apollo/client/loader/impl/RemoteConfigLoader.java

@@ -1,8 +1,10 @@
 package com.ctrip.apollo.client.loader.impl;
 
+import com.ctrip.apollo.client.loader.ConfigServiceLocator;
 import com.ctrip.apollo.client.model.ApolloRegistry;
 import com.ctrip.apollo.client.util.ConfigUtil;
 import com.ctrip.apollo.core.dto.ApolloConfig;
+import com.ctrip.apollo.core.serivce.ApolloService;
 import com.google.common.collect.Maps;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -13,6 +15,7 @@ import org.springframework.http.ResponseEntity;
 import org.springframework.util.StringUtils;
 import org.springframework.web.client.RestTemplate;
 
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -24,10 +27,12 @@ public class RemoteConfigLoader extends AbstractConfigLoader {
     private static final Logger logger = LoggerFactory.getLogger(RemoteConfigLoader.class);
     private final RestTemplate restTemplate;
     private final ConfigUtil configUtil;
-
-    public RemoteConfigLoader(RestTemplate restTemplate, ConfigUtil configUtil) {
+    private final ConfigServiceLocator serviceLocator;
+    
+    public RemoteConfigLoader(RestTemplate restTemplate, ConfigUtil configUtil, ConfigServiceLocator locator) {
         this.restTemplate = restTemplate;
         this.configUtil = configUtil;
+        this.serviceLocator = locator;
     }
 
     ApolloConfig getRemoteConfig(RestTemplate restTemplate, String uri, String cluster, ApolloRegistry apolloRegistry, ApolloConfig previousConfig) {
@@ -78,9 +83,17 @@ public class RemoteConfigLoader extends AbstractConfigLoader {
     @Override
     protected ApolloConfig doLoadApolloConfig(ApolloRegistry apolloRegistry, ApolloConfig previous) {
         ApolloConfig result = this.getRemoteConfig(restTemplate,
-                                        configUtil.getConfigServerUrl(), configUtil.getCluster(),
+                                        getConfigServiceUrl(), configUtil.getCluster(),
                                         apolloRegistry, previous);
         //When remote server return 304, we need to return the previous result
         return result == null ? previous : result;
     }
-}
+    
+    private String getConfigServiceUrl() {
+      List<ApolloService> services = serviceLocator.getConfigServices();
+      if(services.size()==0){
+        throw new RuntimeException("No available config service");
+      }
+        return services.get(0).getHomepageUrl();
+     }
+}

+ 1 - 6
apollo-client/src/main/java/com/ctrip/apollo/client/util/ConfigUtil.java

@@ -23,7 +23,7 @@ public class ConfigUtil {
     public static final String APOLLO_PROPERTY = "apollo.properties";
     private static ConfigUtil configUtil = new ConfigUtil();
     private ApplicationContext applicationContext;
-
+    
     private ConfigUtil() {
     }
 
@@ -31,11 +31,6 @@ public class ConfigUtil {
         return configUtil;
     }
 
-    public String getConfigServerUrl() {
-        // TODO return the meta server url based on different environments
-        return "http://localhost";
-    }
-
     public String getCluster() {
         // TODO return the actual cluster
         return "default";

+ 2 - 1
apollo-client/src/main/resources/apollo.properties

@@ -1,2 +1,3 @@
-appId=101
+app.id=101
 version=1.0
+env=local

+ 28 - 16
apollo-client/src/test/java/com/ctrip/apollo/client/loader/impl/RemoteConfigLoaderTest.java

@@ -1,29 +1,35 @@
 package com.ctrip.apollo.client.loader.impl;
 
-import com.ctrip.apollo.client.model.ApolloRegistry;
-import com.ctrip.apollo.client.util.ConfigUtil;
-import com.ctrip.apollo.core.dto.ApolloConfig;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyMap;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.List;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
-import org.springframework.core.env.CompositePropertySource;
-import org.springframework.core.env.MapPropertySource;
 import org.springframework.http.HttpEntity;
 import org.springframework.http.HttpMethod;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.client.RestTemplate;
 
-import java.util.Map;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.*;
+import com.ctrip.apollo.client.loader.ConfigServiceLocator;
+import com.ctrip.apollo.client.model.ApolloRegistry;
+import com.ctrip.apollo.client.util.ConfigUtil;
+import com.ctrip.apollo.core.dto.ApolloConfig;
+import com.ctrip.apollo.core.serivce.ApolloService;
 
 /**
  * @author Jason Song(song_s@ctrip.com)
@@ -35,12 +41,14 @@ public class RemoteConfigLoaderTest {
     private RestTemplate restTemplate;
     private ConfigUtil configUtil;
     @Mock
+    private ConfigServiceLocator serviceLocater;
+    @Mock
     private ResponseEntity<ApolloConfig> someResponse;
 
     @Before
     public void setUp() {
         configUtil = spy(ConfigUtil.getInstance());
-        remoteConfigLoader = spy(new RemoteConfigLoader(restTemplate, configUtil));
+        remoteConfigLoader = spy(new RemoteConfigLoader(restTemplate, configUtil, serviceLocater));
     }
 
     @Test
@@ -52,7 +60,12 @@ public class RemoteConfigLoaderTest {
         ApolloRegistry apolloRegistry = assembleSomeApolloRegistry(someAppId, "someVersion");
         ApolloConfig previousConfig = null;
 
-        when(configUtil.getConfigServerUrl()).thenReturn(someServerUrl);
+        
+        ApolloService someService = new ApolloService();
+        someService.setHomepageUrl(someServerUrl);
+        List<ApolloService> someServices = new ArrayList<>();
+        someServices.add(someService);
+        when(serviceLocater.getConfigServices()).thenReturn(someServices);
         when(configUtil.getCluster()).thenReturn(someCluster);
         doReturn(apolloConfig).when(remoteConfigLoader)
             .getRemoteConfig(restTemplate, someServerUrl, someCluster, apolloRegistry, previousConfig);
@@ -115,7 +128,6 @@ public class RemoteConfigLoaderTest {
         ApolloConfig result = remoteConfigLoader.getRemoteConfig(restTemplate, someServerUrl, someClusterName, apolloRegistry, previousConfig);
 
         assertNull(result);
-
     }
 
     private ApolloRegistry assembleSomeApolloRegistry(long someAppId, String someVersion) {

+ 36 - 7
apollo-configservice/src/main/java/com/ctrip/apollo/metaservice/controller/ServiceController.java

@@ -1,11 +1,13 @@
 package com.ctrip.apollo.metaservice.controller;
 
+import java.util.ArrayList;
 import java.util.List;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import com.ctrip.apollo.core.serivce.ApolloService;
 import com.ctrip.apollo.metaservice.service.DiscoveryService;
 import com.netflix.appinfo.InstanceInfo;
 
@@ -18,17 +20,44 @@ public class ServiceController {
 
 
   @RequestMapping("/meta")
-  public List<InstanceInfo> getMetaService() {
-    return discoveryService.getMetaServiceInstances();
+  public List<ApolloService> getMetaService() {
+    List<InstanceInfo> instances = discoveryService.getMetaServiceInstances();
+    List<ApolloService> result = new ArrayList<ApolloService>();
+    for (InstanceInfo instance : instances) {
+      ApolloService service = new ApolloService();
+      service.setAppName(instance.getAppName());
+      service.setInstanceId(instance.getInstanceId());
+      service.setHomepageUrl(instance.getHomePageUrl());
+      result.add(service);
+    }
+    return result;
   }
 
   @RequestMapping("/config")
-  public List<InstanceInfo> getConfigService() {
-    return discoveryService.getConfigServiceInstances();
+  public List<ApolloService> getConfigService() {
+    List<InstanceInfo> instances = discoveryService.getConfigServiceInstances();
+    List<ApolloService> result = new ArrayList<ApolloService>();
+    for (InstanceInfo instance : instances) {
+      ApolloService service = new ApolloService();
+      service.setAppName(instance.getAppName());
+      service.setInstanceId(instance.getInstanceId());
+      service.setHomepageUrl(instance.getHomePageUrl());
+      result.add(service);
+    }
+    return result;
   }
-  
+
   @RequestMapping("/admin")
-  public List<InstanceInfo> getAdminService(){
-    return discoveryService.getAdminServiceInstances();
+  public List<ApolloService> getAdminService() {
+    List<InstanceInfo> instances = discoveryService.getAdminServiceInstances();
+    List<ApolloService> result = new ArrayList<ApolloService>();
+    for (InstanceInfo instance : instances) {
+      ApolloService service = new ApolloService();
+      service.setAppName(instance.getAppName());
+      service.setInstanceId(instance.getInstanceId());
+      service.setHomepageUrl(instance.getHomePageUrl());
+      result.add(service);
+    }
+    return result;
   }
 }

+ 4 - 4
apollo-configservice/src/main/java/com/ctrip/apollo/metaservice/service/DiscoveryService.java

@@ -6,7 +6,7 @@ import java.util.List;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
-import com.ctrip.apollo.core.ServiceIdConsts;
+import com.ctrip.apollo.core.ServiceNameConsts;
 import com.netflix.appinfo.InstanceInfo;
 import com.netflix.discovery.EurekaClient;
 import com.netflix.discovery.shared.Application;
@@ -18,17 +18,17 @@ public class DiscoveryService {
   private EurekaClient eurekaClient;
 
   public List<InstanceInfo> getConfigServiceInstances() {
-    Application application = eurekaClient.getApplication(ServiceIdConsts.APOLLO_CONFIGSERVICE);
+    Application application = eurekaClient.getApplication(ServiceNameConsts.APOLLO_CONFIGSERVICE);
     return application != null ? application.getInstances() : new ArrayList<>();
   }
 
   public List<InstanceInfo> getMetaServiceInstances() {
-    Application application = eurekaClient.getApplication(ServiceIdConsts.APOLLO_METASERVICE);
+    Application application = eurekaClient.getApplication(ServiceNameConsts.APOLLO_METASERVICE);
     return application != null ? application.getInstances() : new ArrayList<>();
   }
 
   public List<InstanceInfo> getAdminServiceInstances(){
-    Application application = eurekaClient.getApplication(ServiceIdConsts.APOLLO_ADMINSERVICE);
+    Application application = eurekaClient.getApplication(ServiceNameConsts.APOLLO_ADMINSERVICE);
     return application != null ? application.getInstances() : new ArrayList<>();
   }
 }

+ 3 - 1
apollo-configservice/src/main/resources/application.yml

@@ -3,10 +3,12 @@ spring:
     name: apollo-configservice
 
 server:
-  port: ${port:80}
+  port: ${port:2756}
   
 logging:
   level:
     org.springframework.cloud: 'DEBUG'
   file: /opt/logs/apollo-configservice.log
 
+ctrip:
+  appid: 100003171

+ 6 - 0
apollo-core/pom.xml

@@ -29,5 +29,11 @@
 			<artifactId>guava</artifactId>
 		</dependency>
 		<!-- end of util -->
+		<!-- log -->
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <!-- end of log -->
 	</dependencies>
 </project>

+ 21 - 0
apollo-core/src/main/java/com/ctrip/apollo/Apollo.java

@@ -0,0 +1,21 @@
+package com.ctrip.apollo;
+
+public class Apollo {
+  
+  public final static String VERSION = "java-0.0.1-SNAPSHOT";
+
+  private static Env m_env;
+
+  public enum Env {
+      LOCAL, DEV, FWS, FAT, UAT, LPT, PROD, TOOLS
+  }
+
+  public static void initialize(Env env) {
+      m_env = env;
+  }
+
+  public static Env getEnv() {
+      return m_env;
+  }
+
+}

+ 42 - 0
apollo-core/src/main/java/com/ctrip/apollo/core/MetaDomainConsts.java

@@ -0,0 +1,42 @@
+package com.ctrip.apollo.core;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.ctrip.apollo.Apollo.Env;
+
+public class MetaDomainConsts {
+
+  public static final String LOCAL = "http://localhost";
+
+  public static final String DEV = "http://ws.meta.apollo.fx.dev.nt.ctripcorp.com";
+
+  public static final String FAT = "http://ws.meta.apollo.fx.fat.nt.ctripcorp.com";;
+
+  public static final String FWS = "http://ws.meta.apollo.fx.fws.nt.ctripcorp.com";
+
+  public static final String UAT = "http://ws.meta.apollo.fx.uat.nt.ctripcorp.com";
+
+  public static final String LPT = "http://ws.meta.apollo.fx.lpt.nt.ctripcorp.com";
+
+  public static final String TOOLS = "http://ws.meta.apollo.fx.tools.ctripcorp.com";
+
+  public static final String PRD = "http://ws.meta.apollo.fx.ctripcorp.com";
+
+  private static Map<Env, String> domains = new HashMap<>();
+
+  static {
+    domains.put(Env.LOCAL, LOCAL);
+    domains.put(Env.DEV, DEV);
+    domains.put(Env.FAT, FAT);
+    domains.put(Env.FWS, FWS);
+    domains.put(Env.UAT, UAT);
+    domains.put(Env.LPT, LPT);
+    domains.put(Env.TOOLS, TOOLS);
+    domains.put(Env.PROD, PRD);
+  }
+
+  public static String getDomain(Env env) {
+    return domains.get(env);
+  }
+}

+ 1 - 1
apollo-core/src/main/java/com/ctrip/apollo/core/ServiceIdConsts.java → apollo-core/src/main/java/com/ctrip/apollo/core/ServiceNameConsts.java

@@ -1,6 +1,6 @@
 package com.ctrip.apollo.core;
 
-public class ServiceIdConsts {
+public class ServiceNameConsts {
 
   public static final String APOLLO_METASERVICE = "apollo-metaservice";
 

+ 34 - 0
apollo-core/src/main/java/com/ctrip/apollo/core/serivce/ApolloService.java

@@ -0,0 +1,34 @@
+package com.ctrip.apollo.core.serivce;
+
+public class ApolloService {
+
+  private String appName;
+
+  private String instanceId;
+
+  private String homepageUrl;
+
+  public String getAppName() {
+    return appName;
+  }
+
+  public String getHomepageUrl() {
+    return homepageUrl;
+  }
+
+  public String getInstanceId() {
+    return instanceId;
+  }
+
+  public void setAppName(String appName) {
+    this.appName = appName;
+  }
+
+  public void setHomepageUrl(String homepageUrl) {
+    this.homepageUrl = homepageUrl;
+  }
+
+  public void setInstanceId(String instanceId) {
+    this.instanceId = instanceId;
+  }
+}

+ 91 - 0
apollo-core/src/main/java/com/ctrip/apollo/core/utils/ApolloThreadFactory.java

@@ -0,0 +1,91 @@
+package com.ctrip.apollo.core.utils;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ApolloThreadFactory implements ThreadFactory {
+    private static Logger log = LoggerFactory.getLogger(ApolloThreadFactory.class);
+
+    private final AtomicLong m_threadNumber = new AtomicLong(1);
+
+    private final String m_namePrefix;
+
+    private final boolean m_daemon;
+
+    private final static ThreadGroup m_threadGroup = new ThreadGroup("Apollo");
+
+    public static ThreadGroup getThreadGroup() {
+        return m_threadGroup;
+    }
+
+    public static ThreadFactory create(String namePrefix, boolean daemon) {
+        return new ApolloThreadFactory(namePrefix, daemon);
+    }
+
+    public static boolean waitAllShutdown(int timeoutInMillis) {
+        ThreadGroup group = getThreadGroup();
+        Thread[] activeThreads = new Thread[group.activeCount()];
+        group.enumerate(activeThreads);
+        Set<Thread> alives = new HashSet<Thread>(Arrays.asList(activeThreads));
+        Set<Thread> dies = new HashSet<Thread>();
+        log.info("Current ACTIVE thread count is: {}", alives.size());
+        long expire = System.currentTimeMillis() + timeoutInMillis;
+        while (System.currentTimeMillis() < expire) {
+            classify(alives, dies, new ClassifyStandard<Thread>() {
+                @Override
+                public boolean satisfy(Thread t) {
+                    return !t.isAlive() || t.isInterrupted() || t.isDaemon();
+                }
+            });
+            if (alives.size() > 0) {
+                log.info("Alive apollo threads: {}", alives);
+                try {
+                    TimeUnit.SECONDS.sleep(2);
+                } catch (InterruptedException e) {
+                    // ignore
+                }
+            } else {
+                log.info("All apollo threads are shutdown.");
+                return true;
+            }
+        }
+        log.warn("Some apollo threads are still alive but expire time has reached, alive threads: {}", alives);
+        return false;
+    }
+
+    private static interface ClassifyStandard<T> {
+        boolean satisfy(T t);
+    }
+
+    private static <T> void classify(Set<T> src, Set<T> des, ClassifyStandard<T> standard) {
+        Set<T> s = new HashSet<>();
+        for (T t : src) {
+            if (standard.satisfy(t)) {
+                s.add(t);
+            }
+        }
+        src.removeAll(s);
+        des.addAll(s);
+    }
+
+    private ApolloThreadFactory(String namePrefix, boolean daemon) {
+        m_namePrefix = namePrefix;
+        m_daemon = daemon;
+    }
+
+    public Thread newThread(Runnable r) {
+        Thread t = new Thread(m_threadGroup, r,//
+              m_threadGroup.getName() + "-" + m_namePrefix + "-" + m_threadNumber.getAndIncrement());
+        t.setDaemon(m_daemon);
+        if (t.getPriority() != Thread.NORM_PRIORITY)
+            t.setPriority(Thread.NORM_PRIORITY);
+        return t;
+    }
+}

+ 23 - 0
apollo-core/src/main/java/com/ctrip/apollo/core/utils/DNSUtil.java

@@ -0,0 +1,23 @@
+package com.ctrip.apollo.core.utils;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class DNSUtil {
+
+	public static List<String> resolve(String domainName) throws UnknownHostException {
+		List<String> result = new ArrayList<String>();
+
+		InetAddress[] addresses = InetAddress.getAllByName(domainName);
+		if (addresses != null) {
+			for (InetAddress addr : addresses) {
+				result.add(addr.getHostAddress());
+			}
+		}
+
+		return result;
+	}
+
+}

+ 373 - 0
apollo-core/src/main/java/com/ctrip/apollo/core/utils/StringUtils.java

@@ -0,0 +1,373 @@
+package com.ctrip.apollo.core.utils;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+public class StringUtils {
+
+	public static final String EMPTY = "";
+
+	/**
+	 * <p>
+	 * Checks if a String is empty ("") or null.
+	 * </p>
+	 *
+	 * <pre>
+	 * StringUtils.isEmpty(null)      = true
+	 * StringUtils.isEmpty("")        = true
+	 * StringUtils.isEmpty(" ")       = false
+	 * StringUtils.isEmpty("bob")     = false
+	 * StringUtils.isEmpty("  bob  ") = false
+	 * </pre>
+	 *
+	 * <p>
+	 * NOTE: This method changed in Lang version 2.0. It no longer trims the String. That functionality is available in isBlank().
+	 * </p>
+	 *
+	 * @param str
+	 *           the String to check, may be null
+	 * @return <code>true</code> if the String is empty or null
+	 */
+	public static boolean isEmpty(String str) {
+		return str == null || str.length() == 0;
+	}
+
+	/**
+	 * <p>
+	 * Checks if a String is whitespace, empty ("") or null.
+	 * </p>
+	 *
+	 * <pre>
+	 * StringUtils.isBlank(null)      = true
+	 * StringUtils.isBlank("")        = true
+	 * StringUtils.isBlank(" ")       = true
+	 * StringUtils.isBlank("bob")     = false
+	 * StringUtils.isBlank("  bob  ") = false
+	 * </pre>
+	 *
+	 * @param str
+	 *           the String to check, may be null
+	 * @return <code>true</code> if the String is null, empty or whitespace
+	 */
+	public static boolean isBlank(String str) {
+		int strLen;
+		if (str == null || (strLen = str.length()) == 0) {
+			return true;
+		}
+		for (int i = 0; i < strLen; i++) {
+			if ((Character.isWhitespace(str.charAt(i)) == false)) {
+				return false;
+			}
+		}
+		return true;
+	}
+
+	/**
+	 * <p>
+	 * Removes control characters (char &lt;= 32) from both ends of this String returning <code>null</code> if the String is empty
+	 * ("") after the trim or if it is <code>null</code>.
+	 *
+	 * <p>
+	 * The String is trimmed using {@link String#trim()}. Trim removes start and end characters &lt;= 32. To strip whitespace use
+	 * {@link #stripToNull(String)}.
+	 * </p>
+	 *
+	 * <pre>
+	 * StringUtils.trimToNull(null)          = null
+	 * StringUtils.trimToNull("")            = null
+	 * StringUtils.trimToNull("     ")       = null
+	 * StringUtils.trimToNull("abc")         = "abc"
+	 * StringUtils.trimToNull("    abc    ") = "abc"
+	 * </pre>
+	 *
+	 * @param str
+	 *           the String to be trimmed, may be null
+	 * @return the trimmed String, <code>null</code> if only chars &lt;= 32, empty or null String input
+	 * @since 2.0
+	 */
+	public static String trimToNull(String str) {
+		String ts = trim(str);
+		return isEmpty(ts) ? null : ts;
+	}
+
+	/**
+	 * <p>
+	 * Removes control characters (char &lt;= 32) from both ends of this String returning an empty String ("") if the String is empty
+	 * ("") after the trim or if it is <code>null</code>.
+	 *
+	 * <p>
+	 * The String is trimmed using {@link String#trim()}. Trim removes start and end characters &lt;= 32. To strip whitespace use
+	 * {@link #stripToEmpty(String)}.
+	 * </p>
+	 *
+	 * <pre>
+	 * StringUtils.trimToEmpty(null)          = ""
+	 * StringUtils.trimToEmpty("")            = ""
+	 * StringUtils.trimToEmpty("     ")       = ""
+	 * StringUtils.trimToEmpty("abc")         = "abc"
+	 * StringUtils.trimToEmpty("    abc    ") = "abc"
+	 * </pre>
+	 *
+	 * @param str
+	 *           the String to be trimmed, may be null
+	 * @return the trimmed String, or an empty String if <code>null</code> input
+	 * @since 2.0
+	 */
+	public static String trimToEmpty(String str) {
+		return str == null ? EMPTY : str.trim();
+	}
+
+	/**
+	 * <p>
+	 * Removes control characters (char &lt;= 32) from both ends of this String, handling <code>null</code> by returning
+	 * <code>null</code>.
+	 * </p>
+	 *
+	 * <p>
+	 * The String is trimmed using {@link String#trim()}. Trim removes start and end characters &lt;= 32. To strip whitespace use
+	 * {@link #strip(String)}.
+	 * </p>
+	 *
+	 * <p>
+	 * To trim your choice of characters, use the {@link #strip(String, String)} methods.
+	 * </p>
+	 *
+	 * <pre>
+	 * StringUtils.trim(null)          = null
+	 * StringUtils.trim("")            = ""
+	 * StringUtils.trim("     ")       = ""
+	 * StringUtils.trim("abc")         = "abc"
+	 * StringUtils.trim("    abc    ") = "abc"
+	 * </pre>
+	 *
+	 * @param str
+	 *           the String to be trimmed, may be null
+	 * @return the trimmed string, <code>null</code> if null String input
+	 */
+	public static String trim(String str) {
+		return str == null ? null : str.trim();
+	}
+
+	/**
+	 * <p>
+	 * Compares two Strings, returning <code>true</code> if they are equal.
+	 * </p>
+	 *
+	 * <p>
+	 * <code>null</code>s are handled without exceptions. Two <code>null</code> references are considered to be equal. The comparison
+	 * is case sensitive.
+	 * </p>
+	 *
+	 * <pre>
+	 * StringUtils.equals(null, null)   = true
+	 * StringUtils.equals(null, "abc")  = false
+	 * StringUtils.equals("abc", null)  = false
+	 * StringUtils.equals("abc", "abc") = true
+	 * StringUtils.equals("abc", "ABC") = false
+	 * </pre>
+	 *
+	 * @see java.lang.String#equals(Object)
+	 * @param str1
+	 *           the first String, may be null
+	 * @param str2
+	 *           the second String, may be null
+	 * @return <code>true</code> if the Strings are equal, case sensitive, or both <code>null</code>
+	 */
+	public static boolean equals(String str1, String str2) {
+		return str1 == null ? str2 == null : str1.equals(str2);
+	}
+
+	/**
+	 * <p>
+	 * Compares two Strings, returning <code>true</code> if they are equal ignoring the case.
+	 * </p>
+	 *
+	 * <p>
+	 * <code>null</code>s are handled without exceptions. Two <code>null</code> references are considered equal. Comparison is case
+	 * insensitive.
+	 * </p>
+	 *
+	 * <pre>
+	 * StringUtils.equalsIgnoreCase(null, null)   = true
+	 * StringUtils.equalsIgnoreCase(null, "abc")  = false
+	 * StringUtils.equalsIgnoreCase("abc", null)  = false
+	 * StringUtils.equalsIgnoreCase("abc", "abc") = true
+	 * StringUtils.equalsIgnoreCase("abc", "ABC") = true
+	 * </pre>
+	 *
+	 * @see java.lang.String#equalsIgnoreCase(String)
+	 * @param str1
+	 *           the first String, may be null
+	 * @param str2
+	 *           the second String, may be null
+	 * @return <code>true</code> if the Strings are equal, case insensitive, or both <code>null</code>
+	 */
+	public static boolean equalsIgnoreCase(String str1, String str2) {
+		return str1 == null ? str2 == null : str1.equalsIgnoreCase(str2);
+	}
+
+	/**
+	 * <p>
+	 * Check if a String starts with a specified prefix.
+	 * </p>
+	 *
+	 * <p>
+	 * <code>null</code>s are handled without exceptions. Two <code>null</code> references are considered to be equal. The comparison
+	 * is case sensitive.
+	 * </p>
+	 *
+	 * <pre>
+	 * StringUtils.startsWith(null, null)      = true
+	 * StringUtils.startsWith(null, "abc")     = false
+	 * StringUtils.startsWith("abcdef", null)  = false
+	 * StringUtils.startsWith("abcdef", "abc") = true
+	 * StringUtils.startsWith("ABCDEF", "abc") = false
+	 * </pre>
+	 *
+	 * @see java.lang.String#startsWith(String)
+	 * @param str
+	 *           the String to check, may be null
+	 * @param prefix
+	 *           the prefix to find, may be null
+	 * @return <code>true</code> if the String starts with the prefix, case sensitive, or both <code>null</code>
+	 * @since 2.4
+	 */
+	public static boolean startsWith(String str, String prefix) {
+		return startsWith(str, prefix, false);
+	}
+
+	/**
+	 * <p>
+	 * Case insensitive check if a String starts with a specified prefix.
+	 * </p>
+	 *
+	 * <p>
+	 * <code>null</code>s are handled without exceptions. Two <code>null</code> references are considered to be equal. The comparison
+	 * is case insensitive.
+	 * </p>
+	 *
+	 * <pre>
+	 * StringUtils.startsWithIgnoreCase(null, null)      = true
+	 * StringUtils.startsWithIgnoreCase(null, "abc")     = false
+	 * StringUtils.startsWithIgnoreCase("abcdef", null)  = false
+	 * StringUtils.startsWithIgnoreCase("abcdef", "abc") = true
+	 * StringUtils.startsWithIgnoreCase("ABCDEF", "abc") = true
+	 * </pre>
+	 *
+	 * @see java.lang.String#startsWith(String)
+	 * @param str
+	 *           the String to check, may be null
+	 * @param prefix
+	 *           the prefix to find, may be null
+	 * @return <code>true</code> if the String starts with the prefix, case insensitive, or both <code>null</code>
+	 * @since 2.4
+	 */
+	public static boolean startsWithIgnoreCase(String str, String prefix) {
+		return startsWith(str, prefix, true);
+	}
+
+	/**
+	 * <p>
+	 * Checks if the String contains only unicode digits. A decimal point is not a unicode digit and returns false.
+	 * </p>
+	 *
+	 * <p>
+	 * <code>null</code> will return <code>false</code>. An empty String (length()=0) will return <code>true</code>.
+	 * </p>
+	 *
+	 * <pre>
+	 * StringUtils.isNumeric(null)   = false
+	 * StringUtils.isNumeric("")     = true
+	 * StringUtils.isNumeric("  ")   = false
+	 * StringUtils.isNumeric("123")  = true
+	 * StringUtils.isNumeric("12 3") = false
+	 * StringUtils.isNumeric("ab2c") = false
+	 * StringUtils.isNumeric("12-3") = false
+	 * StringUtils.isNumeric("12.3") = false
+	 * </pre>
+	 *
+	 * @param str
+	 *           the String to check, may be null
+	 * @return <code>true</code> if only contains digits, and is non-null
+	 */
+	public static boolean isNumeric(String str) {
+		if (str == null) {
+			return false;
+		}
+		int sz = str.length();
+		for (int i = 0; i < sz; i++) {
+			if (Character.isDigit(str.charAt(i)) == false) {
+				return false;
+			}
+		}
+		return true;
+	}
+
+	/**
+	 * <p>
+	 * Check if a String starts with a specified prefix (optionally case insensitive).
+	 * </p>
+	 *
+	 * @see java.lang.String#startsWith(String)
+	 * @param str
+	 *           the String to check, may be null
+	 * @param prefix
+	 *           the prefix to find, may be null
+	 * @param ignoreCase
+	 *           inidicates whether the compare should ignore case (case insensitive) or not.
+	 * @return <code>true</code> if the String starts with the prefix or both <code>null</code>
+	 */
+	private static boolean startsWith(String str, String prefix, boolean ignoreCase) {
+		if (str == null || prefix == null) {
+			return (str == null && prefix == null);
+		}
+		if (prefix.length() > str.length()) {
+			return false;
+		}
+		return str.regionMatches(ignoreCase, 0, prefix, 0, prefix.length());
+	}
+
+	public static interface StringFormatter<T> {
+		public String format(T obj);
+	}
+
+	public static <T> String join(Collection<T> collection, String separator) {
+		return join(collection, separator, new StringFormatter<T>() {
+			@Override
+			public String format(T obj) {
+				return obj.toString();
+			}
+		});
+	}
+
+	public static <T> String join(Collection<T> collection, String separator, StringFormatter<T> formatter) {
+		Iterator<T> iterator = collection.iterator();
+		// handle null, zero and one elements before building a buffer
+		if (iterator == null) {
+			return null;
+		}
+		if (!iterator.hasNext()) {
+			return EMPTY;
+		}
+		T first = iterator.next();
+		if (!iterator.hasNext()) {
+			return first == null ? "" : formatter.format(first);
+		}
+
+		// two or more elements
+		StringBuilder buf = new StringBuilder(256); // Java default is 16, probably too small
+		if (first != null) {
+			buf.append(formatter.format(first));
+		}
+
+		while (iterator.hasNext()) {
+			buf.append(separator);
+			T obj = iterator.next();
+			if (obj != null) {
+				buf.append(formatter.format(obj));
+			}
+		}
+
+		return buf.toString();
+	}
+}

+ 2 - 0
apollo-portal/src/main/resources/application.yml

@@ -9,3 +9,5 @@ spring:
     username: sa
     password: sa
 
+ctrip:
+  appid: 100003173