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客户端配置独立,避免共享实例列表。

相关推荐
成为你的宁宁1 小时前
【APISIX:部署、路由与 Nacos 集成】
微服务·nacos·apisix·api网关
Raas1001 天前
MAI Gateway (魔芋企业级AI网关)支持哪些模型?AI网关多模型统一接入实战指南
人工智能·安全·gateway·企业级·ai网关
Raas1001 天前
MAI Gateway (魔芋企业级AI网关) 私有化部署全攻略:AI网关怎么部署?附架构图
人工智能·安全·大模型·gateway·ai网关·mai gateway
晏宁科技YaningAI2 天前
企业通信系统架构设计方法论
性能优化·系统架构·gateway·paas
成为你的宁宁2 天前
【运维读懂 Spring Cloud Gateway】
gateway·api网关
Raas1003 天前
MAI Gateway(魔芋企业级AI网关):企业AI网关的合规审计与调用留痕设计
大数据·人工智能·gateway·企业·api网关·mai gateway·企业级网关
linweidong3 天前
钉钉Java面经及参考答案
nacos·协程·垃圾收集器·jvm调优·spring ioc·脏读·mysql索引
Sammyyyyy3 天前
AI Gateway 与直连 LLM API 的应该怎么选,一篇文章说明白
人工智能·ai·gateway·ai编程·ai-native·servbay
snow@li3 天前
Gateway网关全景深度分析:外部请求进入微服务全链路解析
gateway