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

相关推荐
残花月伴5 小时前
springCloud/Alibaba常用中间件之GateWay网关
spring cloud·中间件·gateway
柚个朵朵16 小时前
Springclound常用五大组件及其使用原理
spring cloud·hystrix·eureka·ribbon·gateway·feign
东阳马生架构1 天前
Nacos源码—6.Nacos升级gRPC分析一
nacos
gxh19921 天前
springboot微服务连接nacos超时
linux·后端·微服务·nacos
东阳马生架构1 天前
Nacos源码—5.Nacos配置中心实现分析二
nacos·注册中心·配置中心
曹朋羽1 天前
spring cloud gateway 断言(Predicates)与过滤器(filters)
spring cloud·gateway
东阳马生架构1 天前
Nacos源码—5.Nacos配置中心实现分析一
nacos·注册中心·配置中心
hzj62 天前
GateWay使用
java·spring·gateway
东阳马生架构2 天前
Nacos源码—5.Nacos配置中心实现分析
nacos
东阳马生架构3 天前
Nacos源码—4.Nacos集群高可用分析三
nacos·注册中心·配置中心