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