1、Spring Cloud Gateway配置文件
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: trueroutes:- id: operator-bffuri: lb://operator-bffpredicates:- Path=/operatorPortal/**- id: agent-bffuri: lb://agent-bffpredicates:- Path=/agentPortal/**
2、Spring Cloud Gateway配置類
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請求上下文持有器
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>>() {@Overrideprotected 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攔截器
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 {@Overridepublic 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());}@Overridepublic int getOrder() {return -300;}}
5、Spring Cloud Gateway自定義 Ribbon 灰度規則
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 {@Autowiredprivate GatewayConfigProperties gatewayConfigProperties;@Overridepublic 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元數據的灰度標識
spring:application:# 服務名name: @artifactId@cloud:# nacos注冊和配置中心相關配置nacos:discovery:server-addr: localhost:8848metadata:traffic-group: gray
上述方案即可實現根據用戶ID進入灰度。ID不存在或者ID<1000進入灰度服務,否則進入普通服務。確保agent-bff和operator-bff的Ribbon客戶端配置獨立,避免共享實例列表。