Spring Cloud Gateway 使用ribbon以及nacos实现灰度发布

1、Spring Cloud Gateway配置文件

java 复制代码
gateway:
  userId-limit: 1000
agent-bff:
  ribbon:
    NFLoadBalancerRuleClassName: com.anlitech.gateway.gray.GrayRule
operator-bff:
  ribbon:
    NFLoadBalancerRuleClassName: com.anlitech.gateway.gray.GrayRule
spring:
  cloud:
    gateway:
      locator:
        enabled: true
      routes:
        - id: operator-bff
          uri: lb://operator-bff
          predicates:
            - Path=/operatorPortal/**
        - id: agent-bff
          uri: lb://agent-bff
          predicates:
            - Path=/agentPortal/**

2、Spring Cloud Gateway配置类

java 复制代码
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;


@Data
@Component
@RefreshScope
@ConfigurationProperties("gateway")
public class GatewayConfigProperties {
    /**
     * 用户id灰度阈值
     */
    private Long userIdLimit;

}

3、Spring Cloud Gateway的Ribbon请求上下文持有器

java 复制代码
import com.alibaba.ttl.TransmittableThreadLocal;
import lombok.experimental.UtilityClass;

import java.util.HashMap;
import java.util.Map;

/**
 * @author ronshi
 * @date 2025/3/17 14:38
 */
@UtilityClass
public class RibbonRequestContextHolder {
    private final ThreadLocal<Map<String, String>> CONTEXT_HOLDER = new TransmittableThreadLocal<Map<String, String>>() {
        @Override
        protected Map<String, String> initialValue() {
            return new HashMap<>(16);
        }
    };

    /**
     * 获取当前线程的上下文Map
     */
    public Map<String, String> getCurrentContext() {
        return CONTEXT_HOLDER.get();
    }

    /**
     * 向当前上下文添加键值
     */
    public void put(String key, String value) {
        getCurrentContext().put(key, value);
    }

    /**
     * 从当前上下文获取值
     */
    public static String get(String key) {
        return getCurrentContext().get(key);
    }

    /**
     * 清理当前线程上下文(防止内存泄漏)
     */
    public void clear() {
        CONTEXT_HOLDER.remove();
    }
}

4、Spring Cloud Gateway的用户ID拦截器

java 复制代码
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;


@Slf4j
@Component
@RequiredArgsConstructor
public class CommonGlobalFilter implements GlobalFilter, Ordered {


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String idStr = request.getQueryParams().getFirst("userId");
        RibbonRequestContextHolder.put("traffic-userId", idStr);
        return chain.filter(exchange).doFinally(signal -> RibbonRequestContextHolder.clear());
    }

    @Override
    public int getOrder() {
        return -300;
    }


}

5、Spring Cloud Gateway自定义 Ribbon 灰度规则

java 复制代码
import com.alibaba.cloud.nacos.ribbon.NacosServer;
import com.netflix.loadbalancer.BaseLoadBalancer;
import com.netflix.loadbalancer.RoundRobinRule;
import com.netflix.loadbalancer.Server;
import com.anlitech.gateway.config.GatewayConfigProperties;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author ronshi
 * @date 2025/3/18 14:24
 */
public class GrayRule extends RoundRobinRule {
    @Autowired
    private GatewayConfigProperties gatewayConfigProperties;

    @Override
    public Server choose(Object key) {
        Long userIdLimit= gatewayConfigProperties.getUserIdLimit();
        String userId = RibbonRequestContextHolder.get("traffic-userId");
        String grayTag = userId == null || Long.parseLong(userId) < userIdLimit ? "gray" : "normal";
        List<Server> servers = getLoadBalancer().getReachableServers();
        List<Server> matchedServers = servers.stream()
            .filter(server -> {
                Map<String, String> metadata = ((NacosServer) server).getInstance().getMetadata();
                return metadata.getOrDefault("traffic-group", "normal").equals(grayTag);
            })
            .collect(Collectors.toList());

        // 处理空列表情况:回退到原始负载均衡器
        if (matchedServers.isEmpty()) {
            //return super.choose(getLoadBalancer(), key);
        }

        // 创建临时负载均衡器,仅包含匹配的服务器
        BaseLoadBalancer loadBalancer = new BaseLoadBalancer();
        loadBalancer.addServers(matchedServers);
        return super.choose(loadBalancer, key);
    }
}

6、BFF服务增加nacos元数据的灰度标识

java 复制代码
spring:
  application:
    # 服务名
    name: @artifactId@
  cloud:
    # nacos注册和配置中心相关配置
    nacos:
      discovery:
        server-addr: localhost:8848
        metadata:
          traffic-group: gray

上述方案即可实现根据用户ID进入灰度。ID不存在或者ID<1000进入灰度服务,否则进入普通服务。确保agent-bff和operator-bff的Ribbon客户端配置独立,避免共享实例列表。

相关推荐
Raas1008 小时前
MAI Gateway(魔芋企业级AI网关)功能全解:AI网关支持流式输出吗?一文看懂AI网关能力矩阵
大数据·人工智能·gateway·mai gateway·企业级网关
毅炼1 天前
不写多语言 SDK,怎么让 Python、Go、Node 服务接入注册中心?
java·后端·系统架构·gateway
cnkeysky1 天前
SpringBoot 整合 nacos 配置中心
nacos·springboot
cnkeysky2 天前
docker 运行 nacos
docker·nacos
Raas1004 天前
AI网关支持哪些模型?MAI Gateway(魔芋企业级AI网关)功能实测与最佳实践
大数据·人工智能·gateway·mai gateway·企业级产品
Raas1004 天前
MAI Gateway(魔芋企业级AI网关)技术揭秘:AI网关支持DeepSeek吗?从原理到落地
大数据·人工智能·gateway·ai网关·mai gateway·魔芋·企业级产品
Raas1005 天前
MAI Gateway(魔芋企业级AI网关)技术揭秘:AI网关支持哪些模型?从原理到落地
大数据·人工智能·网关·gateway·mai gateway·企业级产品
多云行者5 天前
什么是LLM Gateway?定义、技术栈与落地方式详解
网关·llm·gateway·api·传统
毅炼6 天前
Rover-Suite 开源发布:轻量服务注册中心与网关一体化方案
java·后端·系统架构·gateway