如何通过请求URL路由Eureka中注册的不同版本服务?
如何通过URL指定Eureka服务版本实现路由?
嘿,这个场景我太熟了!很多团队在做服务版本迭代的时候都会遇到这个需求——要让不同版本的服务共存,还能通过URL精准路由到对应版本的实例。核心是得靠服务网关(比如Spring Cloud Gateway,现在主流用这个)来搭桥,因为Eureka本身只是个注册中心,不管路由转发的事儿。下面给你一步步拆解实现方案:
一、核心逻辑梳理
Eureka会把所有服务实例的元数据(包括你配置的versions=v1/v2)都存起来,但它不会帮你筛选实例路由。所以我们要做的是:
- 网关拦截请求,从URL里提取版本标识(不管是路径前缀还是请求参数);
- 从Eureka拉取的服务实例列表中,筛选出
metadataMap.versions和提取到的版本匹配的实例; - 把请求转发给筛选后的实例。
二、具体实现(以Spring Cloud Gateway为例)
1. 先给网关搭好依赖基础
网关得能和Eureka通信,拉取服务列表,所以要引入这两个核心依赖(Maven为例):
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>
2. 两种URL版本传递方案
方案一:路径前缀指定版本(推荐,更RESTful)
比如请求URL是 http://网关地址/v1/你的服务名/api/users,我们要提取v1这个版本,然后路由到对应实例。
- 第一步:配置网关路由规则(
application.yml)
spring: cloud: gateway: routes: - id: versioned-service-route uri: lb://你的服务注册名 # 用lb://前缀开启负载均衡,指向Eureka里的服务名 predicates: - Path=/{version}/你的服务注册名/** # 匹配带版本前缀的路径 filters: - StripPrefix=1 # 把路径里的第一个段(也就是v1/v2)去掉,避免后端服务收到多余的路径 - name: VersionRouteFilter # 自定义过滤器,用来筛选对应版本的实例
- 第二步:编写自定义版本筛选过滤器
这个过滤器的作用是从路径里拿到版本号,然后从Eureka的实例列表里挑出匹配的:
import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; import org.springframework.cloud.netflix.eureka.EurekaDiscoveryClient; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import java.util.List; import java.util.stream.Collectors; @Component public class VersionRouteFilter extends AbstractGatewayFilterFactory<VersionRouteFilter.Config> { private final EurekaDiscoveryClient discoveryClient; // 注入Eureka客户端,用来获取服务实例列表 public VersionRouteFilter(EurekaDiscoveryClient discoveryClient) { this.discoveryClient = discoveryClient; } @Override public GatewayFilter apply(Config config) { return (exchange, chain) -> { // 从路径变量里提取版本号 String version = exchange.getRequest().getPath().segment(0); String serviceId = "你的服务注册名"; // 筛选出metadata中versions匹配的实例 List<EurekaDiscoveryClient.EurekaServiceInstance> targetInstances = discoveryClient.getInstances(serviceId) .stream() .filter(instance -> version.equals(instance.getMetadata().get("versions"))) .collect(Collectors.toList()); if (targetInstances.isEmpty()) { // 没有对应版本的实例,直接返回404(你也可以自定义错误响应) return exchange.getResponse().setComplete(); } // 交给负载均衡器选择具体实例转发 return chain.filter(exchange); }; } // 过滤器的配置类,这里暂时不需要参数,留空即可 public static class Config {} }
方案二:请求参数指定版本
如果你的URL习惯用参数传递版本,比如 http://网关地址/你的服务名/api/users?version=v2,可以用这个方案。
- 路由配置(
application.yml)
spring: cloud: gateway: routes: - id: version-param-service-route uri: lb://你的服务注册名 predicates: - Path=/你的服务注册名/** filters: - name: VersionParamRouteFilter
- 自定义参数版过滤器
import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; import org.springframework.cloud.netflix.eureka.EurekaDiscoveryClient; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import java.util.List; import java.util.stream.Collectors; @Component public class VersionParamRouteFilter extends AbstractGatewayFilterFactory<VersionParamRouteFilter.Config> { private final EurekaDiscoveryClient discoveryClient; public VersionParamRouteFilter(EurekaDiscoveryClient discoveryClient) { this.discoveryClient = discoveryClient; } @Override public GatewayFilter apply(Config config) { return (exchange, chain) -> { // 从请求参数里拿version,默认给v1(你也可以改成返回错误) String version = exchange.getRequest().getQueryParams().getFirst("version"); if (version == null) { version = "v1"; } String serviceId = "你的服务注册名"; List<EurekaDiscoveryClient.EurekaServiceInstance> targetInstances = discoveryClient.getInstances(serviceId) .stream() .filter(instance -> version.equals(instance.getMetadata().get("versions"))) .collect(Collectors.toList()); if (targetInstances.isEmpty()) { return exchange.getResponse().setComplete(); } return chain.filter(exchange); }; } public static class Config {} }
3. 更优雅的进阶方案:自定义负载均衡策略
上面的过滤器方式虽然能用,但更专业的做法是把版本筛选逻辑整合到负载均衡器里,让Spring Cloud的负载均衡器直接帮你选对应版本的实例。
比如写一个基于版本的负载均衡规则:
import com.netflix.loadbalancer.AbstractLoadBalancerRule; import com.netflix.loadbalancer.Server; import org.springframework.cloud.netflix.eureka.EurekaServerIntrospector; import org.springframework.stereotype.Component; import java.util.List; import java.util.stream.Collectors; @Component public class VersionBasedLoadBalancerRule extends AbstractLoadBalancerRule { private final EurekaServerIntrospector eurekaServerIntrospector; public VersionBasedLoadBalancerRule(EurekaServerIntrospector eurekaServerIntrospector) { this.eurekaServerIntrospector = eurekaServerIntrospector; } @Override public Server choose(Object key) { // key就是我们传递过来的版本号 String targetVersion = (String) key; List<Server> allServers = getLoadBalancer().getAllServers(); // 筛选出metadata匹配的实例 List<Server> targetServers = allServers.stream() .filter(server -> { String instanceVersion = eurekaServerIntrospector.getMetadata(server).get("versions"); return targetVersion.equals(instanceVersion); }) .collect(Collectors.toList()); if (targetServers.isEmpty()) { return null; } // 简单轮询选实例,你也可以改成随机、权重等策略 return targetServers.get((int) (System.currentTimeMillis() % targetServers.size())); } @Override public void initWithNiwsConfig(com.netflix.client.config.IClientConfig clientConfig) { // 初始化配置,这里不需要额外操作 } }
然后在网关配置里指定这个规则给你的服务:
你的服务注册名: ribbon: NFLoadBalancerRuleClassName: com.your.package.VersionBasedLoadBalancerRule # 替换成你的类路径
最后在网关过滤器里把版本号作为key传递给负载均衡器就行,这样负载均衡器会直接帮你选对应版本的实例,逻辑更清晰。
三、验证测试
- 启动Eureka Server,分别启动v1和v2版本的服务(记得配置
eureka.instance.metadataMap.versions=v1和v2); - 启动配置好的Spring Cloud Gateway;
- 发请求测试:
- 路径前缀方式:
http://网关地址/v1/服务名/api/users应该打到v1实例,/v2/...打到v2; - 参数方式:
http://网关地址/服务名/api/users?version=v1路由到v1,?version=v2路由到v2。
- 路径前缀方式:
内容的提问来源于stack exchange,提问作者RITESH GUPTA
相关产品推荐
相关产品推荐

