Spring MVC高可用架构:健康检查+全链路监控+智能流量+灰度发布深度实战

在这里插入图片描述

肖哥弹架构 跟大家“弹弹” Spring MVC设计与实战应用,需要代码关注

欢迎 点赞,点赞,点赞。

关注公号Solomon肖哥弹架构获取更多精彩内容

历史热点文章

本文将揭秘Spring MVC在高可用架构中的四大核心能力:

1️⃣ 响应式健康检查——基于Actuator与自定义Indicator实现深度服务诊断
2️⃣ 请求生命周期监控——从接收到响应的全链路追踪与耗时分析
3️⃣ 灰度发布支持——动态路由+流量染色实现无缝版本切换
4️⃣ 多租户隔离——租户级路由与资源配额控制

🔧 响应式健康检查
▸ 暴露/health端点展示线程池、MQ连接等自定义指标
▸ 集成Prometheus实现自动告警

🔧 请求生命周期监控
▸ 使用MDC实现请求ID全链路传递
▸ 基于Micrometer统计接口百分位延迟

🔧 智能流量管控
▸ 灰度发布:按用户ID、设备类型路由流量
▸ 多租户:Header透传租户标识实现数据隔离

💡 亮点内容
✔️ 自定义HealthIndicator检查数据库连接池、缓存命中率等关键指标
✔️ 基于Filter与AOP实现毫秒级请求生命周期日志追踪
✔️ 流量染色技术在灰度发布中的实战应用(Header传递+存储隔离)
✔️ 多租户系统如何实现动态数据源切换与限流

三、性能与运维

1. 响应式健康检查

在这里插入图片描述

【作用】

提供应用运行状态的实时监控:

  • 基础设施健康状态(DB、Disk等)
  • 外部服务依赖检查
  • 自定义业务指标监控
  • 与Kubernetes就绪/存活探针集成
【解决的问题】
  1. 服务可用性实时感知
  2. 依赖服务故障快速发现
  3. 自动化运维决策支持
  4. 云原生环境适配
【请求/响应示例】

基础健康检查

GET /actuator/health HTTP/1.1

响应

{
  "status": "UP",
  "components": {
    "db": {
      "status": "UP",
      "details": {
        "database": "MySQL",
        "version": "5.7"
      }
    },
    "disk": {
      "status": "UP",
      "details": {
        "total": "500GB",
        "free": "350GB",
        "threshold": "50GB"
      }
    }
  }
}

深度检查(包含敏感信息)

GET /actuator/health HTTP/1.1
Authorization: Bearer admin-token

响应

{
  "status": "UP",
  "components": {
    "db": {
      "status": "UP",
      "details": {
        "connectionTimeout": 200,
        "maxConnections": 50,
        "activeConnections": 12
      }
    },
    "customService": {
      "status": "DOWN",
      "details": {
        "error": "Connection refused",
        "endpoint": "http://external-service/api"
      }
    }
  }
}
【核心实现代码】
1. 基础健康指标配置
@Configuration
public class HealthConfig {

    // 自动配置的JDBC健康指标
    @Bean
    public ReactiveHealthIndicator dbHealthIndicator(DataSource dataSource) {
        return new ReactiveDataSourceHealthIndicator(dataSource)
            .withQuery("SELECT 1 FROM DUAL");
    }

    // 磁盘空间健康指标
    @Bean
    public ReactiveHealthIndicator diskSpaceHealthIndicator() {
        DiskSpaceHealthIndicatorProperties properties = new DiskSpaceHealthIndicatorProperties();
        properties.setThreshold(DataSize.ofMegabytes(500));
        return new DiskSpaceReactiveHealthIndicator(FileSystems.getDefault(), properties);
    }
}
2. 自定义健康指标
@Component
public class ExternalServiceHealthIndicator implements ReactiveHealthIndicator {

    private final WebClient webClient;
    private final CircuitBreaker circuitBreaker;

    public ExternalServiceHealthIndicator(WebClient.Builder builder) {
        this.webClient = builder.baseUrl("http://external-service").build();
        this.circuitBreaker = CircuitBreaker.ofDefaults("externalService");
    }

    @Override
    public Mono<Health> health() {
        return circuitBreaker.run(
            () -> webClient.get()
                .uri("/health")
                .retrieve()
                .bodyToMono(String.class)
                .map(response -> Health.up()
                    .withDetail("response", response)
                    .build())
                .timeout(Duration.ofSeconds(3))
                .onErrorResume(ex -> Mono.just(
                    Health.down(ex)
                        .withDetail("error", ex.getMessage())
                        .build()
                )),
            throwable -> Mono.just(
                Health.outOfService()
                    .withDetail("circuitBreaker", "OPEN")
                    .build()
            )
        );
    }
}
3. 聚合健康指标
@Configuration
public class CompositeHealthConfig {

    @Bean
    public ReactiveHealthContributorRegistry healthContributors(
        List<ReactiveHealthIndicator> indicators) {
        
        Map<String, ReactiveHealthIndicator> map = indicators.stream()
            .collect(Collectors.toMap(
                indicator -> indicator.getClass().getSimpleName()
                    .replace("HealthIndicator", "")
                    .replace("Reactive", "")
                    .toLowerCase(),
                Function.identity()
            ));

        return new DefaultReactiveHealthContributorRegistry(map);
    }
}
4. 安全控制配置
@Configuration
public class HealthSecurityConfig {

    @Bean
    public SecurityWebFilterChain healthSecurity(ServerHttpSecurity http) {
        return http
            .authorizeExchange()
                .pathMatchers("/actuator/health").permitAll()
                .pathMatchers("/actuator/health/**").hasRole("ADMIN")
            .and()
            .httpBasic()
            .and()
            .csrf().disable()
            .build();
    }
}
5. Kubernetes探针适配
# application.yml
management:
  endpoint:
    health:
      probes:
        enabled: true
      show-details: when_authorized
      group:
        readiness:
          include: "db,disk,externalService"
        liveness:
          include: "disk"
6. 自定义健康组
@Configuration
public class HealthGroupConfig {

    @Bean
    public ReactiveHealthContributor customHealthGroup() {
        Map<String, ReactiveHealthIndicator> indicators = new HashMap<>();
        indicators.put("primary", new PrimaryServiceHealthIndicator());
        indicators.put("secondary", new SecondaryServiceHealthIndicator());
        return CompositeReactiveHealthContributor.fromMap(indicators);
    }
}
2.请求生命周期监控

在这里插入图片描述

【作用】

全链路监控请求处理过程:

  • 耗时统计(各阶段时间消耗)
  • 异常捕获(定位故障点)
  • 流量分析(QPS/吞吐量)
  • 依赖调用追踪
【解决的问题】
  1. 慢请求根因分析
  2. 性能瓶颈定位
  3. 异常请求追踪
  4. 调用链路可视化
【核心实现代码】
1. 监控过滤器(入口统计)
public class RequestMonitoringFilter extends OncePerRequestFilter {
    private static final String ATTRIBUTE_START_TIME = "X-Request-Start-Time";
    private final MeterRegistry meterRegistry;

    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                  HttpServletResponse response, 
                                  FilterChain chain) throws IOException, ServletException {
        
        // 1. 记录开始时间
        long startTime = System.currentTimeMillis();
        request.setAttribute(ATTRIBUTE_START_TIME, startTime);
        
        // 2. 收集基础信息
        String path = request.getRequestURI();
        String method = request.getMethod();
        
        try {
            chain.doFilter(request, response);
            
            // 3. 正常请求记录
            recordMetrics(request, response, path, method, null);
        } catch (Exception ex) {
            // 4. 异常请求记录
            recordMetrics(request, response, path, method, ex);
            throw ex;
        }
    }

    private void recordMetrics(HttpServletRequest request,
                             HttpServletResponse response,
                             String path,
                             String method,
                             Exception ex) {
        // 5. 计算耗时
        Long startTime = (Long) request.getAttribute(ATTRIBUTE_START_TIME);
        long duration = System.currentTimeMillis() - startTime;
        
        // 6. 记录指标
        Tags tags = Tags.of(
            "method", method,
            "path", path,
            "status", String.valueOf(response.getStatus()),
            "exception", ex != null ? ex.getClass().getSimpleName() : "none"
        );
        
        meterRegistry.timer("http.server.requests", tags)
                   .record(duration, TimeUnit.MILLISECONDS);
        
        // 7. 日志记录(ELK采集)
        log.info("Request completed: {} {} - {}ms (status: {})", 
            method, path, duration, response.getStatus());
    }
}
2. 拦截器(阶段耗时)
public class TimingInterceptor implements HandlerInterceptor {
    private static final String TIMING_ATTRIBUTE = "X-Interceptor-Start";
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) {
        request.setAttribute(TIMING_ATTRIBUTE, System.currentTimeMillis());
        return true;
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request,
                              HttpServletResponse response,
                              Object handler,
                              Exception ex) {
        long start = (Long) request.getAttribute(TIMING_ATTRIBUTE);
        long duration = System.currentTimeMillis() - start;
        
        Metrics.timer("handler.execution.time")
              .record(duration, TimeUnit.MILLISECONDS);
    }
}
3. AOP切面(业务方法监控)
@Aspect
@Component
@RequiredArgsConstructor
public class ServiceMonitoringAspect {
    private final MeterRegistry registry;
    
    @Around("execution(* com.example..service.*.*(..))")
    public Object monitorService(ProceedingJoinPoint pjp) throws Throwable {
        String className = pjp.getTarget().getClass().getSimpleName();
        String methodName = pjp.getSignature().getName();
        Timer.Sample sample = Timer.start(registry);
        
        try {
            return pjp.proceed();
        } catch (Exception ex) {
            registry.counter("service.errors",
                "class", className,
                "method", methodName,
                "exception", ex.getClass().getSimpleName()).increment();
            throw ex;
        } finally {
            sample.stop(registry.timer("service.execution.time",
                "class", className,
                "method", methodName));
        }
    }
}
4. 响应日志增强
@ControllerAdvice
public class ResponseLoggingAdvice implements ResponseBodyAdvice<Object> {
    
    @Override
    public boolean supports(MethodParameter returnType, 
                          Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }
    
    @Override
    public Object beforeBodyWrite(Object body,
                                MethodParameter returnType,
                                MediaType selectedContentType,
                                Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                ServerHttpRequest request,
                                ServerHttpResponse response) {
        
        // 记录响应特征
        if (body != null) {
            log.debug("Response body type: {}", body.getClass().getName());
            if (body instanceof ResponseEntity) {
                ((ResponseEntity<?>) body).getHeaders()
                    .forEach((name, values) -> 
                        log.trace("Response header: {}={}", name, values));
            }
        }
        return body;
    }
}
5. 分布式追踪集成
public class TracingFilter extends OncePerRequestFilter {
    private final Tracer tracer;
    
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                  HttpServletResponse response,
                                  FilterChain chain) throws IOException, ServletException {
        
        Span span = tracer.buildSpan(request.getRequestURI())
            .asChildOf(tracer.activeSpan())
            .start();
        
        try (Scope scope = tracer.activateSpan(span)) {
            span.setTag("http.method", request.getMethod());
            span.setTag("http.path", request.getRequestURI());
            
            chain.doFilter(request, response);
            
            span.setTag("http.status_code", response.getStatus());
        } catch (Exception ex) {
            span.log(Map.of(
                "event", "error",
                "error.object", ex
            ));
            throw ex;
        } finally {
            span.finish();
        }
    }
}
6. 监控配置类
@Configuration
public class MonitoringConfig implements WebMvcConfigurer {
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new TimingInterceptor())
               .addPathPatterns("/**");
    }
    
    @Bean
    public FilterRegistrationBean<RequestMonitoringFilter> monitoringFilter() {
        FilterRegistrationBean<RequestMonitoringFilter> registration = 
            new FilterRegistrationBean<>();
        registration.setFilter(new RequestMonitoringFilter(meterRegistry));
        registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return registration;
    }
    
    @Bean
    public CurrentTraceContext.ScopeDecorator mdcScopeDecorator() {
        return MDCScopeDecorator.newBuilder()
            .clear()
            .add(SingleCorrelationField.create(BaggageFields.TRACE_ID))
            .build();
    }
}
【生产环境集成】
  1. Prometheus指标暴露
@Bean
public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
    return registry -> registry.config()
        .commonTags(
            "application", "order-service",
            "region", System.getenv("AWS_REGION"))
        .meterFilter(
            new MeterFilter() {
                @Override
                public Meter.Id map(Meter.Id id) {
                    // 统一重命名指标
                    if(id.getName().startsWith("http_")) {
                        return id.withName("app." + id.getName());
                    }
                    return id;
                }
            });
}
  1. Grafana监控看板配置
{
  "panels": [{
    "title": "请求耗时分布",
    "type": "heatmap",
    "targets": [{
      "expr": "histogram_quantile(0.95, sum(rate(http_server_requests_seconds_bucket[1m])) by (le, path))",
      "legendFormat": "{{path}}"
    }]
  }]
}
  1. 告警规则示例
groups:
- name: request-monitoring
  rules:
  - alert: HighErrorRate
    expr: rate(http_server_requests_errors_total[1m]) > 0.1
    labels:
      severity: critical
    annotations:
      summary: "High error rate on {{ $labels.path }}"
      description: "Error rate is {{ $value }}"
  1. 日志关联配置(logback-spring.xml)
<appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="net.logstash.logback.encoder.LogstashEncoder">
        <providers>
            <timestamp/>
            <pattern>
                <pattern>
                    { "traceId": "%mdc{traceId}", "spanId": "%mdc{spanId}" }
                </pattern>
            </pattern>
            <message/>
            <loggerName/>
            <threadName/>
            <logLevel/>
            <stackTrace/>
        </providers>
    </encoder>
</appender>
3. 灰度发布支持

在这里插入图片描述

【作用】

提供企业级灰度发布能力:

  • 流量精准控制(按比例/按条件路由)
  • 多维度灰度策略(用户ID、设备、地域等)
  • 实时监控和自动回滚
  • 新旧版本数据一致性保障
【解决的问题】
  1. 新功能上线导致的全系统风险
  2. 无法小范围验证新版本稳定性
  3. 突发流量激增缺乏熔断机制
  4. 多版本并行时的数据兼容问题
【请求/响应示例】

灰度识别请求

GET /api/v1/products HTTP/1.1
X-User-ID: 12345
X-Device-Type: iOS

灰度响应(新版本)

HTTP/1.1 200 OK
X-Version: v2.1-gray
Content-Type: application/json

{
  "feature": "new_ui_style",
  "data": {...}
}
【核心实现代码】
1. 网关灰度路由
@Configuration
public class GrayReleaseConfig {
    
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
            .route("gray_route", r -> r
                .header("X-User-ID", "12345|67890") // 白名单用户
                .or()
                .cookie("gray_group", "true") // Cookie标记
                .or()
                .weight("group1", 80) // 80%流量
                .uri("lb://new-service")
                .id("gray-release"))
            .route("normal_route", r -> r
                .path("/**")
                .uri("lb://old-service")
                .id("default"))
            .build();
    }
}
2. 灰度规则引擎
public class GrayRuleEngine {
    private final Map<String, GrayRule> rules;
    
    public GrayRuleEngine() {
        // 规则可动态配置
        rules = Map.of(
            "user_whitelist", new UserWhitelistRule(),
            "traffic_percent", new TrafficPercentRule(),
            "ab_test", new ABTestRule()
        );
    }
    
    public boolean shouldGray(HttpServletRequest request) {
        return rules.values().stream()
            .anyMatch(rule -> rule.matches(request));
    }
    
    interface GrayRule {
        boolean matches(HttpServletRequest request);
    }
    
    // 用户白名单规则
    static class UserWhitelistRule implements GrayRule {
        private final Set<String> whiteUsers = Set.of("1001", "1002");
        
        @Override
        public boolean matches(HttpServletRequest request) {
            String userId = request.getHeader("X-User-ID");
            return whiteUsers.contains(userId);
        }
    }
    
    // 流量百分比规则
    static class TrafficPercentRule implements GrayRule {
        private final Random random = new Random();
        
        @Override
        public boolean matches(HttpServletRequest request) {
            return random.nextInt(100) < 20; // 20%流量
        }
    }
}
3. 版本数据同步
public class DataSyncService {
    @Transactional
    public void syncGrayData(Object data) {
        // 双写逻辑
        oldService.save(data);
        newService.save(convertData(data));
        
        // 数据校验
        if (!validateConsistency(data)) {
            throw new GrayDataException("Data inconsistency detected");
        }
    }
    
    private boolean validateConsistency(Object data) {
        Object oldData = oldService.getById(getId(data));
        Object newData = newService.getById(getId(data));
        return oldData.equals(newData);
    }
}
4. 监控与自动回滚
@RestController
public class GrayMonitorController {
    @Autowired
    private HealthMetrics healthMetrics;
    
    @Scheduled(fixedRate = 5000)
    public void checkGrayStatus() {
        HealthStatus status = healthMetrics.evaluate();
        
        if (status == HealthStatus.RED) {
            triggerRollback();
        }
    }
    
    private void triggerRollback() {
        // 调用配置中心回滚
        configCenter.rollbackVersion();
        // 通知网关切换路由
        gateway.updateRoutes(getDefaultRoutes());
    }
}

public enum HealthStatus {
    GREEN, YELLOW, RED
}
5. 全链路灰度标记
public class GrayContextInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) {
        // 从请求头获取灰度标记
        String grayTag = request.getHeader("X-Gray-Tag");
        
        // 存入线程上下文
        GrayContextHolder.setTag(grayTag);
        
        // 传递标记给下游服务
        if (grayTag != null) {
            RestTemplate restTemplate = new RestTemplate();
            restTemplate.getInterceptors().add((req, body, execution) -> {
                req.getHeaders().add("X-Gray-Tag", grayTag);
                return execution.execute(req, body);
            });
        }
        return true;
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, 
                              HttpServletResponse response, 
                              Object handler, Exception ex) {
        GrayContextHolder.clear();
    }
}

【关键设计】

  1. 流量染色:通过请求头/ Cookie 标记灰度流量
  2. 动态规则:支持运行时调整灰度策略
  3. 双写验证:确保新旧版本数据一致性
  4. 熔断机制:异常指标超阈值自动回滚
  5. 标记透传:保证灰度流量在微服务间持续传递

四、企业级扩展

1. 多租户请求路由

在这里插入图片描述

【作用】

实现企业级多租户隔离方案:

  • 动态租户识别与路由
  • 物理/逻辑隔离自由组合
  • 租户级资源配额管理
  • 跨租户数据共享控制
【解决的问题】
  1. 不同租户的SLA差异化保障
  2. 敏感数据的物理隔离需求
  3. 共享资源池的公平调度
  4. 租户自定义配置的热加载
【请求/响应示例】

租户请求示例

GET /api/v1/orders HTTP/1.1
X-Tenant-ID: tenant_789
Authorization: Bearer xxxxxx

路由响应示例

HTTP/1.1 200 OK
X-Tenant-Resource-Pool: dedicated-node-01
Content-Type: application/json

{
  "data": [...],
  "quota": {
    "remaining": 1423,
    "limit": 5000
  }
}
【核心实现代码】
1. 租户上下文管理
public class TenantContext {
    private static final ThreadLocal<String> currentTenant = new ThreadLocal<>();
    
    public static void setTenantId(String tenantId) {
        currentTenant.set(tenantId);
    }
    
    public static String getTenantId() {
        return currentTenant.get();
    }
    
    public static void clear() {
        currentTenant.remove();
    }
}

@WebFilter
public class TenantIdentificationFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                       FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        
        // 从多位置获取租户标识(优先级:Header > Cookie > Domain)
        String tenantId = Optional.ofNullable(httpRequest.getHeader("X-Tenant-ID"))
            .or(() -> Optional.ofNullable(getCookieValue(httpRequest, "tenant_id")))
            .or(() -> extractFromDomain(httpRequest))
            .orElseThrow(() -> new TenantMissingException());
            
        try {
            TenantContext.setTenantId(tenantId);
            chain.doFilter(request, response);
        } finally {
            TenantContext.clear();
        }
    }
    
    private String extractFromDomain(HttpServletRequest request) {
        String domain = request.getServerName();
        // 示例:tenant1.app.com → tenant1
        return domain.split("\.")[0]; 
    }
}
2. 动态数据源路由
public class TenantDataSourceRouter extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return TenantContext.getTenantId();
    }
}

@Configuration
public class DataSourceConfig {
    @Bean
    @ConfigurationProperties(prefix = "tenants.datasource")
    public Map<String, DataSource> tenantDataSources() {
        // 自动加载配置文件中所有租户数据源
        return new HashMap<>();
    }
    
    @Bean
    public DataSource routingDataSource(Map<String, DataSource> tenantDataSources) {
        TenantDataSourceRouter router = new TenantDataSourceRouter();
        router.setTargetDataSources(tenantDataSources);
        router.setDefaultTargetDataSource(tenantDataSources.get("default"));
        return router;
    }
}
3. 服务路由控制
@RestController
@RequestMapping("/api/v1")
public class TenantAwareController {
    @Autowired
    private TenantServiceLocator serviceLocator;
    
    @GetMapping("/orders")
    public ResponseEntity<List<Order>> getOrders() {
        // 根据租户获取专属服务实例
        OrderService service = serviceLocator.getService(
            TenantContext.getTenantId(), 
            OrderService.class);
            
        return ResponseEntity.ok(service.listOrders());
    }
}

public class TenantServiceLocator {
    private final Map<String, Map<Class<?>, Object>> tenantServices = new ConcurrentHashMap<>();
    
    @SuppressWarnings("unchecked")
    public <T> T getService(String tenantId, Class<T> serviceType) {
        return (T) tenantServices.computeIfAbsent(tenantId, k -> new HashMap<>())
            .computeIfAbsent(serviceType, clz -> createService(tenantId, clz));
    }
    
    private Object createService(String tenantId, Class<?> serviceType) {
        // 动态创建租户专属服务实例
        return ApplicationContextHolder.getBeanFactory()
            .createBean(serviceType, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, false);
    }
}
4. 租户配额管理
public class TenantQuotaManager {
    private final Map<String, TokenBucket> tenantBuckets = new ConcurrentHashMap<>();
    
    public boolean tryAcquire(String tenantId, int permits) {
        TokenBucket bucket = tenantBuckets.computeIfAbsent(tenantId, 
            k -> new TokenBucket(getTenantQuota(tenantId)));
        
        return bucket.tryAcquire(permits);
    }
    
    private long getTenantQuota(String tenantId) {
        // 从配置中心获取租户配额
        return ConfigCenter.getTenantConfig(tenantId).getQpsLimit();
    }
    
    // 基于令牌桶的限流算法
    private static class TokenBucket {
        private final long capacity;
        private final AtomicLong tokens;
        private final RateLimiter limiter;
        
        TokenBucket(long qps) {
            this.capacity = qps;
            this.tokens = new AtomicLong(qps);
            this.limiter = RateLimiter.create(qps);
        }
        
        boolean tryAcquire(int permits) {
            if (limiter.tryAcquire(permits)) {
                return tokens.updateAndGet(v -> v > permits ? v - permits : v) > 0;
            }
            return false;
        }
    }
}
5. 跨租户共享控制
@Aspect
@Component
public class TenantSharingAspect {
    @Pointcut("@annotation(sharedDataAccess)")
    public void sharedDataAccessPointcut() {}
    
    @Around("sharedDataAccessPointcut()")
    public Object checkSharedAccess(ProceedingJoinPoint pjp) throws Throwable {
        String currentTenant = TenantContext.getTenantId();
        Method method = ((MethodSignature)pjp.getSignature()).getMethod();
        SharedDataAccess annotation = method.getAnnotation(SharedDataAccess.class);
        
        // 验证共享权限
        if (!checkSharingPermission(currentTenant, annotation.value())) {
            throw new TenantAccessDeniedException();
        }
        
        return pjp.proceed();
    }
    
    private boolean checkSharingPermission(String tenantId, String[] allowedTenants) {
        // 检查租户是否在允许的共享列表中
        return Arrays.asList(allowedTenants).contains(tenantId) 
            || isAdminTenant(tenantId);
    }
}

【关键设计】

  1. 标识传播:通过ThreadLocal + 拦截器实现全链路租户标识透传
  2. 动态路由:AbstractRoutingDataSource实现数据源动态切换
  3. 服务隔离:每个租户获取独立的Spring Bean实例
  4. 分级限流:租户级+全局两级流量控制
  5. 共享控制:注解驱动的方式声明跨租户访问权限
2. API 流量染色

在这里插入图片描述

【作用】

实现全链路流量管控:

  • 请求生命周期追踪
  • 灰度流量精准路由
  • 压测流量自动隔离
  • 故障注入模拟演练
【解决的问题】
  1. 生产环境无法区分真实/测试流量
  2. 全链路追踪时请求关联困难
  3. 压测流量污染生产数据
  4. 无法模拟特定异常场景
【请求/响应示例】

染色请求示例

POST /api/v1/orders HTTP/1.1
X-Request-ID: 789abc
X-Traffic-Tag: pressure-test
X-Trace-Source: canary

染色响应示例

HTTP/1.1 201 Created
X-Traffic-Tag: pressure-test
X-Trace-Id: 789abc-xyz123
Content-Type: application/json

{
  "status": "created",
  "trafficGroup": "pressure-test",
  "tracePath": "gateway>order>payment"
}
【核心实现代码】
1. 染色标记生成
public class TrafficTagger {
    private static final String[] COLORS = {"red", "blue", "green", "canary"};
    private static final String[] TYPES = {"prod", "test", "pressure", "mock"};
    
    public TrafficTag generateTag(HttpServletRequest request) {
        // 已有标签则继承(实现标签透传)
        String existingTag = request.getHeader("X-Traffic-Tag");
        if (existingTag != null) {
            return TrafficTag.parse(existingTag);
        }
        
        // 新标签生成逻辑
        return new TrafficTag(
            COLORS[ThreadLocalRandom.current().nextInt(COLORS.length)],
            TYPES[ThreadLocalRandom.current().nextInt(TYPES.length)],
            request.getHeader("User-Agent"),
            System.currentTimeMillis()
        );
    }
    
    @Data
    @AllArgsConstructor
    public static class TrafficTag {
        private String color;
        private String type;
        private String source;
        private long timestamp;
        
        public String toString() {
            return color + "-" + type + "-" + timestamp;
        }
        
        public static TrafficTag parse(String tagStr) {
            String[] parts = tagStr.split("-");
            return new TrafficTag(parts[0], parts[1], parts.length > 3 ? parts[2] : "", 
                Long.parseLong(parts[parts.length - 1]));
        }
    }
}
2. 网关染色过滤器
@Component
public class TrafficColorFilter implements GlobalFilter, Ordered {
    @Autowired
    private TrafficTagger tagger;
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        
        // 1. 生成/获取流量标签
        TrafficTag tag = tagger.generateTag(adaptToHttpRequest(request));
        
        // 2. 标签写入请求头
        ServerHttpRequest mutatedRequest = request.mutate()
            .header("X-Traffic-Tag", tag.toString())
            .header("X-Trace-Id", generateTraceId(tag))
            .build();
        
        // 3. 标签写入MDC用于日志追踪
        MDC.put("trafficTag", tag.toString());
        
        return chain.filter(exchange.mutate().request(mutatedRequest).build())
            .doFinally(signal -> MDC.remove("trafficTag"));
    }
    
    private String generateTraceId(TrafficTag tag) {
        return tag.getColor() + "-" + UUID.randomUUID().toString().substring(0, 8);
    }
    
    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}
3. 服务间标签透传
@Configuration
public class FeignConfig {
    @Bean
    public RequestInterceptor trafficTagInterceptor() {
        return template -> {
            // 从当前线程上下文获取标签
            String trafficTag = MDC.get("trafficTag");
            if (trafficTag != null) {
                template.header("X-Traffic-Tag", trafficTag);
            }
            
            // 传递追踪ID
            String traceId = MDC.get("X-Trace-Id");
            if (traceId != null) {
                template.header("X-Trace-Id", traceId);
            }
        };
    }
}

@RestController
public class OrderController {
    @PostMapping("/orders")
    public ResponseEntity<?> createOrder(@RequestHeader("X-Traffic-Tag") String trafficTag) {
        // 根据标签执行差异化逻辑
        if (trafficTag.contains("pressure-test")) {
            return ResponseEntity.ok().body(Collections.emptyMap());
        }
        
        // 正常业务逻辑...
    }
}
4. 存储路由策略
public class TrafficAwareDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        // 根据流量标签选择数据源
        String tag = MDC.get("trafficTag");
        
        if (tag != null && tag.contains("test")) {
            return "testDB";
        } else if (tag != null && tag.contains("pressure")) {
            return "shadowDB";
        }
        return "primaryDB";
    }
}

@Aspect
@Component
public class TrafficRepositoryAspect {
    @Pointcut("execution(* org.springframework.data.repository.Repository+.*(..))")
    public void repositoryMethods() {}
    
    @Around("repositoryMethods()")
    public Object routeStorage(ProceedingJoinPoint pjp) throws Throwable {
        String trafficTag = MDC.get("trafficTag");
        
        try {
            if (trafficTag != null && trafficTag.contains("mock")) {
                return mockData(pjp);
            }
            return pjp.proceed();
        } finally {
            // 清理操作...
        }
    }
    
    private Object mockData(ProceedingJoinPoint pjp) {
        Method method = ((MethodSignature)pjp.getSignature()).getMethod();
        Class<?> returnType = method.getReturnType();
        
        // 根据方法返回类型生成模拟数据
        if (List.class.isAssignableFrom(returnType)) {
            return Collections.emptyList();
        } else if (Optional.class.isAssignableFrom(returnType)) {
            return Optional.empty();
        }
        return null;
    }
}
5. 监控分析模块
@RestController
@RequestMapping("/traffic")
public class TrafficMonitorController {
    @Autowired
    private TrafficMetricsCollector collector;
    
    @GetMapping("/metrics")
    public Map<String, TrafficStats> getMetrics() {
        return collector.getRealTimeStats();
    }
}

@Component
public class TrafficMetricsCollector {
    private final Map<String, AtomicLong> requestCounts = new ConcurrentHashMap<>();
    private final Map<String, AtomicLong> errorCounts = new ConcurrentHashMap<>();
    
    public void recordRequest(String trafficTag) {
        requestCounts.computeIfAbsent(trafficTag, k -> new AtomicLong()).incrementAndGet();
    }
    
    public void recordError(String trafficTag) {
        errorCounts.computeIfAbsent(trafficTag, k -> new AtomicLong()).incrementAndGet();
    }
    
    public Map<String, TrafficStats> getRealTimeStats() {
        return requestCounts.entrySet().stream()
            .collect(Collectors.toMap(
                Map.Entry::getKey,
                e -> new TrafficStats(
                    e.getValue().get(),
                    errorCounts.getOrDefault(e.getKey(), new AtomicLong()).get()
                )
            ));
    }
    
    @Data
    @AllArgsConstructor
    public static class TrafficStats {
        private long totalRequests;
        private long errorCounts;
    }
}
【关键设计】
  1. 标记生成:结合随机策略+请求特征生成唯一染色标识
  2. 透传机制:通过请求头+MDC实现全链路传递
  3. 存储隔离:根据标签自动路由到影子库/测试库
  4. 差异化处理:服务根据染色标识执行特殊逻辑
  5. 监控分析:实时统计各染色流量的质量指标
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Solomon_肖哥弹架构

你的欣赏就是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值