本文目录导读:

我将为您提供一个完整的Java灰度路由案例,包括核心实现和示例代码。
核心路由实现
package com.example.grayroute;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Random;
/**
* 灰度路由管理器
*/
public class GrayRouteManager {
// 灰度配置存储
private final Map<String, GrayRule> grayRules = new ConcurrentHashMap<>();
/**
* 注册灰度规则
*/
public void registerRule(String serviceName, GrayRule rule) {
grayRules.put(serviceName, rule);
}
/**
* 路由决策
*/
public RouteResult route(String serviceName, RequestContext context) {
GrayRule rule = grayRules.get(serviceName);
if (rule == null) {
return RouteResult.normal();
}
// 判断是否命中灰度
if (rule.match(context)) {
return RouteResult.gray(rule.getGrayVersion());
}
return RouteResult.normal();
}
/**
* 灰度规则定义
*/
public static class GrayRule {
private String grayVersion; // 灰度版本号
private double grayRatio; // 灰度比例 0-100
private String userIdExpression; // 用户ID表达式
private String ipExpression; // IP表达式
private String headerExpression; // Header表达式
public boolean match(RequestContext context) {
// 1. 按比例灰度
if (grayRatio > 0) {
if (isRatioMatch(context)) {
return true;
}
}
// 2. 按用户ID灰度
if (userIdExpression != null && isUserIdMatch(context)) {
return true;
}
// 3. 按IP灰度
if (ipExpression != null && isIpMatch(context)) {
return true;
}
// 4. 按Header灰度
if (headerExpression != null && isHeaderMatch(context)) {
return true;
}
return false;
}
private boolean isRatioMatch(RequestContext context) {
// 使用一致性哈希或随机数进行比例分配
String key = context.getUserId() != null ?
context.getUserId() :
String.valueOf(System.nanoTime());
int hash = Math.abs(key.hashCode() % 1000);
return hash < grayRatio * 10;
}
private boolean isUserIdMatch(RequestContext context) {
if (context.getUserId() == null) return false;
// 简化实现,实际可支持正则表达式
return context.getUserId().matches(userIdExpression);
}
private boolean isIpMatch(RequestContext context) {
if (context.getIp() == null) return false;
// 支持IP段匹配,如: 192.168.1.*
String ip = context.getIp();
if (ipExpression.endsWith("*")) {
String prefix = ipExpression.substring(0, ipExpression.length() - 1);
return ip.startsWith(prefix);
}
return ip.equals(ipExpression);
}
private boolean isHeaderMatch(RequestContext context) {
Map<String, String> headers = context.getHeaders();
String[] parts = headerExpression.split("=");
if (parts.length != 2) return false;
String headerValue = headers.get(parts[0].trim());
return headerValue != null && headerValue.equals(parts[1].trim());
}
// Getter和Setter
public String getGrayVersion() { return grayVersion; }
public void setGrayVersion(String grayVersion) { this.grayVersion = grayVersion; }
public double getGrayRatio() { return grayRatio; }
public void setGrayRatio(double grayRatio) {
if (grayRatio < 0 || grayRatio > 100) {
throw new IllegalArgumentException("灰度比例必须在0-100之间");
}
this.grayRatio = grayRatio;
}
public String getUserIdExpression() { return userIdExpression; }
public void setUserIdExpression(String userIdExpression) { this.userIdExpression = userIdExpression; }
public String getIpExpression() { return ipExpression; }
public void setIpExpression(String ipExpression) { this.ipExpression = ipExpression; }
public String getHeaderExpression() { return headerExpression; }
public void setHeaderExpression(String headerExpression) { this.headerExpression = headerExpression; }
}
}
请求上下文类
package com.example.grayroute;
import java.util.HashMap;
import java.util.Map;
/**
* 请求上下文
*/
public class RequestContext {
private String userId;
private String ip;
private Map<String, String> headers = new HashMap<>();
public RequestContext() {}
public RequestContext(String userId, String ip) {
this.userId = userId;
this.ip = ip;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public Map<String, String> getHeaders() {
return headers;
}
public void setHeaders(Map<String, String> headers) {
this.headers = headers;
}
public void addHeader(String key, String value) {
this.headers.put(key, value);
}
}
路由结果类
package com.example.grayroute;
/**
* 路由结果
*/
public class RouteResult {
private boolean isGray;
private String version;
private RouteResult(boolean isGray, String version) {
this.isGray = isGray;
this.version = version;
}
public static RouteResult normal() {
return new RouteResult(false, null);
}
public static RouteResult gray(String grayVersion) {
return new RouteResult(true, grayVersion);
}
public boolean isGray() {
return isGray;
}
public String getVersion() {
return version;
}
}
灰度路由配置类
package com.example.grayroute;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 灰度配置中心(模拟)
*/
public class GrayConfigCenter {
private static final Logger logger = LoggerFactory.getLogger(GrayConfigCenter.class);
private final Map<String, String> configStore = new ConcurrentHashMap<>();
// 单例模式
private static volatile GrayConfigCenter instance;
private GrayConfigCenter() {
// 初始化默认配置
loadDefaultConfig();
}
public static GrayConfigCenter getInstance() {
if (instance == null) {
synchronized (GrayConfigCenter.class) {
if (instance == null) {
instance = new GrayConfigCenter();
}
}
}
return instance;
}
private void loadDefaultConfig() {
// 模拟从配置中心加载配置
// 实际场景可接入 Nacos、Apollo 等配置中心
configStore.put("user-service.gray.ratio", "20");
configStore.put("user-service.gray.version", "v2.0");
configStore.put("user-service.gray.users", "user001,user002");
configStore.put("user-service.gray.ips", "192.168.1.*");
configStore.put("user-service.gray.headers", "X-Gray-Test=true");
}
/**
* 获取灰度配置
*/
public GrayRouteManager.GrayRule getGrayRule(String serviceName) {
GrayRouteManager.GrayRule rule = new GrayRouteManager.GrayRule();
// 从配置中心获取配置
String ratio = configStore.get(serviceName + ".gray.ratio");
String version = configStore.get(serviceName + ".gray.version");
String users = configStore.get(serviceName + ".gray.users");
String ips = configStore.get(serviceName + ".gray.ips");
String headers = configStore.get(serviceName + ".gray.headers");
if (ratio != null) {
rule.setGrayRatio(Double.parseDouble(ratio));
}
if (version != null) {
rule.setGrayVersion(version);
}
if (users != null) {
// 将用户列表转换为匹配表达式
rule.setUserIdExpression(convertToExpression(users));
}
if (ips != null) {
rule.setIpExpression(ips);
}
if (headers != null) {
rule.setHeaderExpression(headers);
}
return rule;
}
/**
* 更新配置(模拟动态刷新)
*/
public void updateConfig(String key, String value) {
configStore.put(key, value);
logger.info("Config updated: {} = {}", key, value);
// 实际场景可发布事件通知各服务刷新
}
private String convertToExpression(String users) {
// 将 "user001,user002" 转换为 "(user001|user002)"
String[] userArray = users.split(",");
return "(" + String.join("|", userArray) + ")";
}
}
服务实现示例
package com.example.grayroute.service;
import com.example.grayroute.*;
import org.springframework.stereotype.Service;
/**
* 用户服务示例
*/
@Service
public class UserService {
private final GrayRouteManager grayRouteManager;
private final GrayConfigCenter configCenter;
public UserService() {
this.grayRouteManager = new GrayRouteManager();
this.configCenter = GrayConfigCenter.getInstance();
initGrayRules();
}
/**
* 初始化灰度规则
*/
private void initGrayRules() {
GrayRouteManager.GrayRule rule = configCenter.getGrayRule("user-service");
grayRouteManager.registerRule("user-service", rule);
}
/**
* 路由到具体版本服务
*/
public String handleRequest(RequestContext context) {
RouteResult result = grayRouteManager.route("user-service", context);
if (result.isGray()) {
// 路由到灰度版本
return callGrayService(context, result.getVersion());
} else {
// 路由到稳定版本
return callStableService(context);
}
}
/**
* 调用稳定版本服务
*/
private String callStableService(RequestContext context) {
// 实际场景可调用远程服务
return "Stable Version Response for user: " + context.getUserId();
}
/**
* 调用灰度版本服务
*/
private String callGrayService(RequestContext context, String grayVersion) {
// 实际场景可调用灰度服务
return "Gray Version " + grayVersion + " Response for user: " + context.getUserId();
}
}
测试用例
package com.example.grayroute;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import java.util.HashMap;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
/**
* 灰度路由测试类
*/
public class GrayRouteTest {
private GrayRouteManager routeManager;
@BeforeEach
public void setUp() {
routeManager = new GrayRouteManager();
// 创建灰度规则
GrayRouteManager.GrayRule rule = new GrayRouteManager.GrayRule();
rule.setGrayVersion("v2.0");
rule.setGrayRatio(30); // 30% 的流量进入灰度
routeManager.registerRule("user-service", rule);
}
@Test
public void testRatioGrayRouting() {
int grayCount = 0;
int total = 1000;
for (int i = 0; i < total; i++) {
RequestContext context = new RequestContext(
"user" + i,
"192.168.1." + (i % 255)
);
RouteResult result = routeManager.route("user-service", context);
if (result.isGray()) {
grayCount++;
}
}
// 统计灰度命中比例
double actualRatio = (grayCount * 100.0) / total;
System.out.println("Expected: 30%, Actual: " + actualRatio + "%");
// 允许一定误差
assertTrue(actualRatio > 25 && actualRatio < 35);
}
@Test
public void testUserSpecificGray() {
// 配置指定用户的灰度规则
GrayRouteManager.GrayRule rule = new GrayRouteManager.GrayRule();
rule.setGrayVersion("v2.0");
rule.setUserIdExpression("(user001|user002)");
routeManager.registerRule("user-service", rule);
// 测试指定用户
RequestContext grayUser = new RequestContext("user001", "192.168.1.100");
assertTrue(routeManager.route("user-service", grayUser).isGray());
// 测试非指定用户
RequestContext normalUser = new RequestContext("user003", "192.168.1.100");
assertFalse(routeManager.route("user-service", normalUser).isGray());
}
@Test
public void testIPBasedGray() {
// 配置指定IP段的灰度规则
GrayRouteManager.GrayRule rule = new GrayRouteManager.GrayRule();
rule.setGrayVersion("v2.0");
rule.setIpExpression("192.168.1.*");
routeManager.registerRule("user-service", rule);
// 测试匹配的IP
RequestContext matchingIP = new RequestContext("user001", "192.168.1.50");
assertTrue(routeManager.route("user-service", matchingIP).isGray());
// 测试不匹配的IP
RequestContext nonMatchingIP = new RequestContext("user001", "10.0.0.1");
assertFalse(routeManager.route("user-service", nonMatchingIP).isGray());
}
@Test
public void testHeaderBasedGray() {
// 配置Header灰度规则
GrayRouteManager.GrayRule rule = new GrayRouteManager.GrayRule();
rule.setGrayVersion("v2.0");
rule.setHeaderExpression("X-Gray-Test=true");
routeManager.registerRule("user-service", rule);
// 测试带Header的请求
RequestContext withHeader = new RequestContext("user001", "192.168.1.100");
withHeader.addHeader("X-Gray-Test", "true");
assertTrue(routeManager.route("user-service", withHeader).isGray());
// 测试不带Header的请求
RequestContext withoutHeader = new RequestContext("user001", "192.168.1.100");
assertFalse(routeManager.route("user-service", withoutHeader).isGray());
}
@Test
public void testMultipleConditions() {
// 组合条件灰度
GrayRouteManager.GrayRule rule = new GrayRouteManager.GrayRule();
rule.setGrayVersion("v2.0");
rule.setGrayRatio(50);
rule.setIpExpression("192.168.*.*");
routeManager.registerRule("user-service", rule);
// 测试命中用户灰度
RequestContext grayUser = new RequestContext("specialUser", "192.168.1.100");
assertTrue(routeManager.route("user-service", grayUser).isGray());
// 测试命中IP灰度
RequestContext grayIP = new RequestContext("normalUser", "192.168.1.101");
assertTrue(routeManager.route("user-service", grayIP).isGray());
// 测试未命中
RequestContext nonGray = new RequestContext("normalUser", "10.0.0.1");
// 根据比例可能命中,这里只进行简单验证
RouteResult result = routeManager.route("user-service", nonGray);
System.out.println("Non-gray result: " + result.isGray());
}
}
Spring Boot集成示例
package com.example.grayroute.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 灰度路由Spring配置
*/
@Configuration
public class GrayRouteConfig implements WebMvcConfigurer {
@Bean
public GrayRouteManager grayRouteManager() {
return new GrayRouteManager();
}
@Bean
public GrayRouteInterceptor grayRouteInterceptor() {
return new GrayRouteInterceptor(grayRouteManager());
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(grayRouteInterceptor())
.addPathPatterns("/api/**");
}
/**
* 灰度路由拦截器
*/
public static class GrayRouteInterceptor implements HandlerInterceptor {
private final GrayRouteManager routeManager;
public GrayRouteInterceptor(GrayRouteManager routeManager) {
this.routeManager = routeManager;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 构建请求上下文
RequestContext context = new RequestContext();
context.setUserId(request.getParameter("userId"));
context.setIp(getClientIp(request));
// 复制头部信息
request.getHeaderNames().asIterator()
.forEachRemaining(header -> context.addHeader(
header,
request.getHeader(header)
));
// 路由决策
String serviceName = request.getRequestURI().split("/")[2];
RouteResult result = routeManager.route(serviceName, context);
// 将路由结果放入请求属性
request.setAttribute("grayRouteResult", result);
// 设置灰度版本标识
if (result.isGray()) {
request.setAttribute("X-Gray-Version", result.getVersion());
response.setHeader("X-Gray-Version", result.getVersion());
}
return true;
}
private String getClientIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.isEmpty()) {
ip = request.getRemoteAddr();
}
return ip;
}
}
}
使用示例
package com.example.grayroute;
/**
* 使用示例
*/
public class UsageExample {
public static void main(String[] args) {
// 1. 初始化路由管理器
GrayRouteManager routeManager = new GrayRouteManager();
// 2. 配置灰度规则
GrayRouteManager.GrayRule rule = new GrayRouteManager.GrayRule();
rule.setGrayVersion("v2.0");
rule.setGrayRatio(20); // 20%流量
rule.setUserIdExpression("(test|beta)");
rule.setIpExpression("192.168.1.*");
rule.setHeaderExpression("X-Group=gray");
routeManager.registerRule("user-service", rule);
// 3. 模拟请求路由
RequestContext userRequest = new RequestContext("testUser", "192.168.1.100");
RouteResult result = routeManager.route("user-service", userRequest);
if (result.isGray()) {
System.out.println("Route to gray version: " + result.getVersion());
} else {
System.out.println("Route to stable version");
}
// 4. 动态更新配置
GrayConfigCenter configCenter = GrayConfigCenter.getInstance();
configCenter.updateConfig("user-service.gray.ratio", "50");
// 5. 新增规则
GrayRouteManager.GrayRule newRule = configCenter.getGrayRule("user-service");
routeManager.registerRule("order-service", newRule);
}
}
- 多维度灰度策略:支持按用户ID、IP、Header、比例等维度组合灰度
- 动态配置:支持运行时动态更新灰度配置
- 版本管理:支持多版本灰度发布
- RESTful API:方便集成和使用
- 测试覆盖:提供完整测试用例验证功能
这个案例可以作为灰度发布的基础框架,实际生产环境中可扩展接入配置中心(如Apollo、Nacos)、负载均衡、服务发现等组件。