Java 如何使用Netflix eureka和ribbon按版本定位服务
我们目前正在探索Netflix,以研究其微服务架构的实现。目前,我们的兴趣集中在服务注册和动态查找功能上Java 如何使用Netflix eureka和ribbon按版本定位服务,java,cloud,service-discovery,netflix,Java,Cloud,Service Discovery,Netflix,我们目前正在探索Netflix,以研究其微服务架构的实现。目前,我们的兴趣集中在服务注册和动态查找功能上 浏览代码、示例和配置,但有些地方不清楚服务版本控制。如果提供发现服务,并且是基于eureka的REST客户端,客户端如何表示需要服务1.2的服务fooBar?客户机在哪里存储/获取该版本号;从本地配置文件(如)获取,还是通过以下方式动态获取 在Eureka REST API中,我看不到任何处理服务版本的内置方式 因此,我认为最好的处理方法是将版本控制信息合并到服务ID中 假设我们有4个服务:
浏览代码、示例和配置,但有些地方不清楚服务版本控制。如果提供发现服务,并且是基于eureka的REST客户端,客户端如何表示需要服务
1.2
的服务fooBar
?客户机在哪里存储/获取该版本号;从本地配置文件(如)获取,还是通过以下方式动态获取 在Eureka REST API中,我看不到任何处理服务版本的内置方式
因此,我认为最好的处理方法是将版本控制信息合并到服务ID中
假设我们有4个服务:用户、统计、登录和oAuth
我们刚刚更新了用户服务API,以更改登录服务中某些新需求所需的功能。但是,这些更改与oAuth使用的API不兼容,没有人有时间更新该服务。统计服务没有使用任何这些功能,因此它不关心使用哪个版本的API。这意味着我们需要同时运行新版本的用户服务(1.2)和旧版本(1.1)
我们可以这样设置:
- User 1.1服务的配置表示注册为“User-1.1”和“User”
- User 1.2服务的配置表示注册为“User-1.2”和“User”
- oAuth服务的配置说明用户服务的ID为“User-1.1”
- 登录服务的配置说明用户服务的ID为“User-1.2”
- 统计服务的配置说明用户服务的ID为“User”
请注意,如果您发布的用户版本1.3引入了与统计服务正在使用的1.2和1.1部分不兼容的更改,则您必须告诉用户1.1和1.2服务也注册为其他服务(如“用户统计安全”),并告诉统计服务使用该ID,或者告诉统计服务使用“user-1.1”或“user-1.2”。服务1注册v1和v2与Eureka 服务2使用不同的功能区客户端发现并向服务1的v1和v2发送请求 我使用
springcloud
实现了这个演示,尽管博客可以在以下位置找到:
我遵循的想法是restemplate
为每个版本使用不同的功能区
客户端,因为每个客户端都有自己的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
...
...
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
...
...
@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();
}
}
服务2 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
...
...
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
...
...
@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();
}
}
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);
}
}
...
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;
}
}
...
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;
}
}
...
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;
}
...
...
@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;
}
}
AppConfig.java(请参见功能区
客户端名称如何与应用程序.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
...
...
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
...
...
@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
...
@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);
}
}
...
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;
}
}
...
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;
}
}
...
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;
}
...
...
@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;
}
}
VersionedNIWSServerListFilter.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);
}
}
...
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;
}
}
...
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;
}
}
...
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;
}
...
...
@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;
}
}
你能用一个真实的场景来解释你对服务版本控制的需求吗?非常有趣的问题和链接,感谢你给我指明了这个方向!示例场景:假设我想部署一个新版本的“customerReview”服务,引入不向后兼容的更改,而不必迁移所有其他调用它的服务一次。因此,我必须保持customerReview的新旧版本都在运行,至少是暂时的。为了保持代码干净,我希望避免在同一服务实例中重复“版本化”端点。到目前为止,我想不出比在不同的spring.application.name下部署和在URL中使用版本化来路由请求更好的方法ests…@yoann-h你是对的,它需要部署同一服务的多个版本,这是任何微服务Landscape的典型。听起来不错。你能详细说明一下如何实现多重注册吗?(目前我只使用基于spring.application.name属性的服务名的基本注册)使用Java库似乎有点棘手。REST API肯定支持它,但底层类似乎只适用于一个实例配置。您可以做的是为每个别名创建一个单独的
DiscoveryClient
。不过,您必须自己设置它,所以看看spring cloud是如何用于inspi的定量。我还建议您支持DiscoveryClient
中的别名。事实上,这可能会证明一个功能请求是合理的。我会进一步挖掘并提交一个。这也可能会导致对一些替代解决方案的解释。我会尝试用我的发现更新此主题。如果在user-1.6到user-1.1中发现错误,会怎么样?不会这种方法要求每个版本有一个分支?每个版本有一个CI/CD管道?这难道不意味着将所有的复杂性都传递给DevOps吗?