2017-02-17 5 views
3

Я задаю себе вопрос, не нахожу ответы на него. Может быть, у кого-то есть идеи об этом ;-) Используя реестр служб (Eureka) в Spring Cloud с клиентами RestTemplate и Feign, у меня есть разные версии версии той же услуги. Версия сборки документируется с помощью конечной точки Actuator/info.Открытие весеннего облака для нескольких версий обслуживания

{ 
"build": { 
"version": "0.0.1-SNAPSHOT", 
"artifact": "service-a", 
"name": "service-a", 
"group": "com.mycompany", 
"time": 1487253409000 
} 
} 
... 
{ 
"build": { 
"version": "0.0.2-SNAPSHOT", 
"artifact": "service-a", 
"name": "service-a", 
"group": "com.mycompany", 
"time": 1487325340000 
} 
} 

Есть ли какой-либо смысл запрашивать конкретную версию сборки при вызове клиента? Должен ли я использовать фильтры маршрутизации шлюза, чтобы управлять этим? Но обнаружение версии оставалось бы проблемой, я думаю ...

Хорошо, любое предложение оценено.

+1

Я не думаю, что есть что-то из коробки, которая поможет. Вам нужно будет передать информацию о версии на сервер Eureka, чтобы клиенты Eureka знали версию. Вероятно, вы можете сделать это через метаданные Eureka. Что касается клиентов Feign, то, используя эту информацию, вам, вероятно, придется использовать API-интерфейсы служб обнаружения, чтобы затем получить метаданные об услуге и решить, какой экземпляр для вызова. –

+0

Я думаю, что вы правы :-(но это настоящая проблема, так как вам сначала нужно собрать все экземпляры службы, а затем отфильтровать их по версии –

+0

Вероятно, хороший старт: https://jmnarloch.wordpress.com/2015/11/ 25/spring-cloud-ribbon-dynamic-routing/ –

ответ

1

Ok. Это код, чтобы ввести версию сборки в службу («сервис-а») метаданные экземпляра должны быть зарегистрированы Eureka:

@Configuration 
@ConditionalOnClass({ EurekaInstanceConfigBean.class, EurekaClient.class }) 
public class EurekaClientInstanceBuildVersionAutoConfiguration { 

    @Autowired(required = false) 
    private EurekaInstanceConfig instanceConfig; 

    @Autowired(required = false) 
    private BuildProperties buildProperties; 

    @Value("${eureka.instance.metadata.keys.version:instanceBuildVersion}") 
    private String versionMetadataKey; 

    @PostConstruct 
    public void init() { 
     if (this.instanceConfig == null || buildProperties == null) { 
      return; 
     } 
     this.instanceConfig.getMetadataMap().put(versionMetadataKey, buildProperties.getVersion()); 
    } 
} 

Это код для подтверждения передачи метаданных в «сервис-б»:

@Component 
public class DiscoveryClientRunner implements CommandLineRunner { 
    private final Logger logger = LoggerFactory.getLogger(this.getClass()); 

    @Autowired 
    private DiscoveryClient client; 

    @Override 
    public void run(String... args) throws Exception { 
     client.getInstances("service-a").forEach((ServiceInstance s) -> { 
      logger.debug(String.format("%s: %s", s.getServiceId(), s.getUri())); 
      for (Entry<String, String> md : s.getMetadata().entrySet()) { 
       logger.debug(String.format("%s: %s", md.getKey(), md.getValue())); 
      } 
     }); 
    } 
} 

Обратите внимание, что если "бросались в составе" (т.е. "экземпляр-сборка-версия"), ключ метаданных Camel Case принудительно.

И это решение я нашел для фильтрации экземпляров службы в соответствии с их версией:

@Configuration 
@EnableConfigurationProperties(InstanceBuildVersionProperties.class) 
public class EurekaInstanceBuildVersionFilterAutoConfig { 

    @Value("${eureka.instance.metadata.keys.version:instanceBuildVersion}") 
    private String versionMetadataKey; 

    @Bean 
    @ConditionalOnProperty(name = "eureka.client.filter.enabled", havingValue = "true") 
    public EurekaInstanceBuildVersionFilter eurekaInstanceBuildVersionFilter(InstanceBuildVersionProperties filters) { 
     return new EurekaInstanceBuildVersionFilter(versionMetadataKey, filters); 
    } 
} 

@Aspect 
@RequiredArgsConstructor 
public class EurekaInstanceBuildVersionFilter { 
    private final Logger logger = LoggerFactory.getLogger(this.getClass()); 

    private final String versionMetadataKey; 
    private final InstanceBuildVersionProperties filters; 

    @SuppressWarnings("unchecked") 
    @Around("execution(public * org.springframework.cloud.netflix.eureka.EurekaDiscoveryClient.getInstances(..))") 
    public Object filterInstances(ProceedingJoinPoint jp) throws Throwable { 
     if (filters == null || !filters.isEnabled()) logger.error("Should not be filtering..."); 
     List<ServiceInstance> instances = (List<ServiceInstance>) jp.proceed(); 
     return instances.stream() 
       .filter(i -> filters.isKept((String) jp.getArgs()[0], i.getMetadata().get(versionMetadataKey))) //DEBUG MD key is Camel Cased! 
       .collect(Collectors.toList()); 
    } 
} 

@ConfigurationProperties("eureka.client.filter") 
public class InstanceBuildVersionProperties { 
    private final Logger logger = LoggerFactory.getLogger(this.getClass()); 

    /** 
    * Indicates whether or not service instances versions should be filtered 
    */ 
    @Getter @Setter 
    private boolean enabled = false; 

    /** 
    * Map of service instance version filters. 
    * The key is the service name and the value configures a filter set for services instances 
    */ 
    @Getter 
    private Map<String, InstanceBuildVersionFilter> services = new HashMap<>(); 

    public boolean isKept(String serviceId, String instanceVersion) { 
     logger.debug("Considering service {} instance version {}", serviceId, instanceVersion); 
     if (services.containsKey(serviceId) && StringUtils.hasText(instanceVersion)) { 
      InstanceBuildVersionFilter filter = services.get(serviceId); 
      String[] filteredVersions = filter.getVersions().split("\\s*,\\s*"); // trimming 
      logger.debug((filter.isExcludeVersions() ? "Excluding" : "Including") + " instances: " + Arrays.toString(filteredVersions)); 
      return contains(filteredVersions, instanceVersion) ? !filter.isExcludeVersions() : filter.isExcludeVersions(); 
     } 
     return true; 
    } 

    @Getter @Setter 
    public static class InstanceBuildVersionFilter { 
     /** 
     * Comma separated list of service version labels to filter 
     */ 
     private String versions; 
     /** 
     * Indicates whether or not to keep the associated instance versions. 
     * When false, versions are kept, otherwise they will be filtered out 
     */ 
     private boolean excludeVersions = false; 
    } 
} 

Вы можете указать для каждой потребленной службы списка ожидаемых или избегали версий и открытия будет фильтроваться соответствующим образом.

logging.level.com.mycompany.demo = ОТЛАДКА

eureka.client.filter.enabled = истинные

eureka.client.filter.services.service-a.versions = 0.0. 1-SNAPSHOT

Просьба представить в виде комментариев любое предложение. Thx

+0

, пожалуйста, см. Комментарии альтернативных решений для упрощения регистрации части процесса: вы можете добавить метаданные с простыми свойствами. –

0

Это трюк для взлома Eureka Dashboard. Добавить этот аспект AspectJ (потому что InstanceInfo используется в EurekaController не Spring Bean) к проекту @EnableEurekaServer:

@Configuration 
@Aspect 
public class EurekaDashboardVersionLabeler { 

    @Value("${eureka.instance.metadata.keys.version:instanceBuildVersion}") 
    private String versionMetadataKey; 

    @Around("execution(public * com.netflix.appinfo.InstanceInfo.getId())") 
    public String versionLabelAppInstances(ProceedingJoinPoint jp) throws Throwable { 
     String instanceId = (String) jp.proceed(); 
     for (StackTraceElement ste : Thread.currentThread().getStackTrace()) { 
      // limit to EurekaController#populateApps in order to avoid side effects 
      if (ste.getClassName().contains("EurekaController")) { 
       InstanceInfo info = (InstanceInfo) jp.getThis(); 
       String version = info.getMetadata().get(versionMetadataKey); 
       if (StringUtils.hasText(version)) { 
        return String.format("%s [%s]", instanceId, version); 
       } 
       break; 
      } 
     } 
     return instanceId; 
    } 

    @Bean("post-construct-labeler") 
    public EurekaDashboardVersionLabeler init() { 
     return EurekaDashboardVersionLabeler.aspectOf(); 
    } 

    private static EurekaDashboardVersionLabeler instance = new EurekaDashboardVersionLabeler(); 
    /** Singleton pattern used by LTW then Spring */ 
    public static EurekaDashboardVersionLabeler aspectOf() { 
     return instance; 
    } 
} 

Вы также должны добавить зависимость не обеспечиваются стартеров:

<dependencies> 
    <dependency> 
     <groupId>org.springframework.cloud</groupId> 
     <artifactId>spring-cloud-starter-eureka-server</artifactId> 
    </dependency> 
    <dependency> 
     <groupId>org.aspectj</groupId> 
     <artifactId>aspectjrt</artifactId> 
     <scope>runtime</scope> 
    </dependency> 
</dependencies> 

И активировать LTW среды выполнения с арг VM, конечно:

-javaagent:D:\.m2\repository\org\aspectj\aspectjweaver\1.8.9\aspectjweaver-1.8.9.jar 
0

служба 1 регистрирует v1 и v2 с Eureka

Сервис 2 обнаруживает и посылает запросы на службы 1 «S v1 и v2, используя различные ленты клиенты

Я получил эту демонстрацию, чтобы работать и будет блог об этом в ближайшие пару дней.

http://tech.asimio.net/2017/03/06/Multi-version-Service-Discovery-using-Spring-Cloud-Netflix-Eureka-and-Ribbon.html

Идея я последовала за RestTemplate использовать другой Ribbon клиент для каждой версии, потому что каждый клиент имеет свой собственный ServerListFilter.


служба 1

application.yml

... 
eureka: 
    client: 
    registerWithEureka: true 
    fetchRegistry: true 
    serviceUrl: 
     defaultZone: http://localhost:8000/eureka/ 
    instance: 
    hostname: ${hostName} 
    statusPageUrlPath: ${management.context-path}/info 
    healthCheckUrlPath: ${management.context-path}/health 
    preferIpAddress: true 
    metadataMap: 
     instanceId: ${spring.application.name}:${server.port} 

--- 
spring: 
    profiles: v1 
eureka: 
    instance: 
    metadataMap: 
     versions: v1 

--- 
spring: 
    profiles: v1v2 
eureka: 
    instance: 
    metadataMap: 
     versions: v1,v2 
... 

служба 2

application.yml

... 
eureka: 
    client: 
    registerWithEureka: false 
    fetchRegistry: true 
    serviceUrl: 
     defaultZone: http://localhost:8000/eureka/ 

demo-multiversion-registration-api-1-v1: 
    ribbon: 
    # Eureka vipAddress of the target service 
    DeploymentContextBasedVipAddresses: demo-multiversion-registration-api-1 
    NIWSServerListClassName: com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList 
    # Interval to refresh the server list from the source (ms) 
    ServerListRefreshInterval: 30000 

demo-multiversion-registration-api-1-v2: 
    ribbon: 
    # Eureka vipAddress of the target service 
    DeploymentContextBasedVipAddresses: demo-multiversion-registration-api-1 
    NIWSServerListClassName: com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList 
    # Interval to refresh the server list from the source (ms) 
    ServerListRefreshInterval: 30000 
... 

Application.java

... 
@SpringBootApplication(scanBasePackages = { 
    "com.asimio.api.multiversion.demo2.config", 
    "com.asimio.api.multiversion.demo2.rest" 
}) 
@EnableDiscoveryClient 
public class Application { 

    public static void main(String[] args) { 
     SpringApplication.run(Application.class, args); 
    } 
} 

AppConfig.java (Посмотрите, как имя Ribbon клиент соответствует Ribbon ключ найден в application.yml

... 
@Configuration 
@RibbonClients(value = { 
    @RibbonClient(name = "demo-multiversion-registration-api-1-v1", configuration = RibbonConfigDemoApi1V1.class), 
    @RibbonClient(name = "demo-multiversion-registration-api-1-v2", configuration = RibbonConfigDemoApi1V2.class) 
}) 
public class AppConfig { 

    @Bean(name = "loadBalancedRestTemplate") 
    @LoadBalanced 
    public RestTemplate loadBalancedRestTemplate() { 
     return new RestTemplate(); 
    } 
} 

RibbonConfigDemoApi1V1.java

... 
public class RibbonConfigDemoApi1V1 { 

    private DiscoveryClient discoveryClient; 

    @Bean 
    public ServerListFilter<Server> serverListFilter() { 
     return new VersionedNIWSServerListFilter<>(this.discoveryClient, RibbonClientApi.DEMO_REGISTRATION_API_1_V1); 
    } 

    @Autowired 
    public void setDiscoveryClient(DiscoveryClient discoveryClient) { 
     this.discoveryClient = discoveryClient; 
    } 
} 

RibbonConfigDemoApi1V2.java похожа, но с использованием RibbonClientApi.DEMO_REGISTRATION_API_1_V2

RibbonClientApi.Java

... 
public enum RibbonClientApi { 

    DEMO_REGISTRATION_API_1_V1("demo-multiversion-registration-api-1", "v1"), 

    DEMO_REGISTRATION_API_1_V2("demo-multiversion-registration-api-1", "v2"); 

    public final String serviceId; 
    public final String version; 

    private RibbonClientApi(String serviceId, String version) { 
     this.serviceId = serviceId; 
     this.version = version; 
    } 
} 

VersionedNIWSServerListFilter.java

... 
public class VersionedNIWSServerListFilter<T extends Server> extends DefaultNIWSServerListFilter<T> { 

    private static final String VERSION_KEY = "versions"; 

    private final DiscoveryClient discoveryClient; 
    private final RibbonClientApi ribbonClientApi; 

    public VersionedNIWSServerListFilter(DiscoveryClient discoveryClient, RibbonClientApi ribbonClientApi) { 
     this.discoveryClient = discoveryClient; 
     this.ribbonClientApi = ribbonClientApi; 
    } 

    @Override 
    public List<T> getFilteredListOfServers(List<T> servers) { 
     List<T> result = new ArrayList<>(); 
     List<ServiceInstance> serviceInstances = this.discoveryClient.getInstances(this.ribbonClientApi.serviceId); 
     for (ServiceInstance serviceInstance : serviceInstances) { 
      List<String> versions = this.getInstanceVersions(serviceInstance); 
      if (versions.isEmpty() || versions.contains(this.ribbonClientApi.version)) { 
       result.addAll(this.findServerForVersion(servers, serviceInstance)); 
      } 
     } 
     return result; 
    } 

    private List<String> getInstanceVersions(ServiceInstance serviceInstance) { 
     List<String> result = new ArrayList<>(); 
     String rawVersions = serviceInstance.getMetadata().get(VERSION_KEY); 
     if (StringUtils.isNotBlank(rawVersions)) { 
      result.addAll(Arrays.asList(rawVersions.split(","))); 
     } 
     return result; 
    } 
... 

AggregationResource.java

... 
@RestController 
@RequestMapping(value = "/aggregation", produces = "application/json") 
public class AggregationResource { 

    private static final String ACTORS_SERVICE_ID_V1 = "demo-multiversion-registration-api-1-v1"; 
    private static final String ACTORS_SERVICE_ID_V2 = "demo-multiversion-registration-api-1-v2"; 

    private RestTemplate loadBalancedRestTemplate; 

    @RequestMapping(value = "/v1/actors/{id}", method = RequestMethod.GET) 
    public com.asimio.api.multiversion.demo2.model.v1.Actor findActorV1(@PathVariable(value = "id") String id) { 
     String url = String.format("http://%s/v1/actors/{id}", ACTORS_SERVICE_ID_V1); 
     return this.loadBalancedRestTemplate.getForObject(url, com.asimio.api.multiversion.demo2.model.v1.Actor.class, id); 
    } 

    @RequestMapping(value = "/v2/actors/{id}", method = RequestMethod.GET) 
    public com.asimio.api.multiversion.demo2.model.v2.Actor findActorV2(@PathVariable(value = "id") String id) { 
     String url = String.format("http://%s/v2/actors/{id}", ACTORS_SERVICE_ID_V2); 
     return this.loadBalancedRestTemplate.getForObject(url, com.asimio.api.multiversion.demo2.model.v2.Actor.class, id); 
    } 

    @Autowired 
    public void setLoadBalancedRestTemplate(RestTemplate loadBalancedRestTemplate) { 
     this.loadBalancedRestTemplate = loadBalancedRestTemplate; 
    } 
} 
+0

Большое спасибо, я внимательно посмотрю. Но это, похоже, не касается моего прецедента: какая польза для службы (1) для объявления/публикации нескольких версий для себя? Это, вероятно, нарушает SRP. Моя потребность заключалась в развертывании двух автономных версий Service 1, а затем для фильтрации не поддерживаемых версий со стороны клиента (Service 2). Плюс мои решения избегают манипулировать несколькими клиентом HTTP (Ribbon). –

+0

У вас есть очень хороший момент, кстати, предлагая свойство eureka.instance предоставлять метаданные (я пропустил это); поэтому мой класс EurekaClientInstanceBuildVersionAutoConfiguration можно заменить на «eureka.instance.metadata-map.instanceBuildVersion = @ pom.version @» в моих примерах. Thx –

+0

«Что нужно для службы (1) объявить/опубликовать несколько версий для себя?» Я считаю, что API никогда не должны отступать назад, если только они (клиент и службы) не развертываются в очень контролируемой среде, например, существующий клиент никогда не сталкивается с новыми API. – ootero

Смежные вопросы