Java灰度路由案例

wen java案例 1

本文目录导读:

Java灰度路由案例

  1. 核心路由实现
  2. 请求上下文类
  3. 路由结果类
  4. 灰度路由配置类
  5. 服务实现示例
  6. 测试用例
  7. Spring Boot集成示例
  8. 使用示例

我将为您提供一个完整的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);
    }
}
  1. 多维度灰度策略:支持按用户ID、IP、Header、比例等维度组合灰度
  2. 动态配置:支持运行时动态更新灰度配置
  3. 版本管理:支持多版本灰度发布
  4. RESTful API:方便集成和使用
  5. 测试覆盖:提供完整测试用例验证功能

这个案例可以作为灰度发布的基础框架,实际生产环境中可扩展接入配置中心(如Apollo、Nacos)、负载均衡、服务发现等组件。

抱歉,评论功能暂时关闭!