You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过请求URL路由Eureka中注册的不同版本服务?

如何通过URL指定Eureka服务版本实现路由?

嘿,这个场景我太熟了!很多团队在做服务版本迭代的时候都会遇到这个需求——要让不同版本的服务共存,还能通过URL精准路由到对应版本的实例。核心是得靠服务网关(比如Spring Cloud Gateway,现在主流用这个)来搭桥,因为Eureka本身只是个注册中心,不管路由转发的事儿。下面给你一步步拆解实现方案:

一、核心逻辑梳理

Eureka会把所有服务实例的元数据(包括你配置的versions=v1/v2)都存起来,但它不会帮你筛选实例路由。所以我们要做的是:

  1. 网关拦截请求,从URL里提取版本标识(不管是路径前缀还是请求参数);
  2. 从Eureka拉取的服务实例列表中,筛选出metadataMap.versions和提取到的版本匹配的实例;
  3. 把请求转发给筛选后的实例。

二、具体实现(以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传递给负载均衡器就行,这样负载均衡器会直接帮你选对应版本的实例,逻辑更清晰。

三、验证测试

  1. 启动Eureka Server,分别启动v1和v2版本的服务(记得配置eureka.instance.metadataMap.versions=v1和v2);
  2. 启动配置好的Spring Cloud Gateway;
  3. 发请求测试:
    • 路径前缀方式:http://网关地址/v1/服务名/api/users 应该打到v1实例,/v2/... 打到v2;
    • 参数方式:http://网关地址/服务名/api/users?version=v1 路由到v1,?version=v2 路由到v2。

内容的提问来源于stack exchange,提问作者RITESH GUPTA

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 08:21:09