肖哥弹架构 跟大家“弹弹” Spring MVC设计与实战应用,需要代码关注
欢迎 点赞,点赞,点赞。
关注公号Solomon肖哥弹架构获取更多精彩内容
历史热点文章
- MyCat应用实战:分布式数据库中间件的实践与优化(篇幅一)
- 图解深度剖析:MyCat 架构设计与组件协同 (篇幅二)
- 一个项目代码讲清楚DO/PO/BO/AO/E/DTO/DAO/ POJO/VO
- 写代码总被Dis:5个项目案例带你掌握SOLID技巧,代码有架构风格
- 里氏替换原则在金融交易系统中的实践,再不懂你咬我
本文将揭秘Spring MVC在高可用架构中的四大核心能力:
1️⃣ 响应式健康检查——基于Actuator与自定义Indicator实现深度服务诊断
2️⃣ 请求生命周期监控——从接收到响应的全链路追踪与耗时分析
3️⃣ 灰度发布支持——动态路由+流量染色实现无缝版本切换
4️⃣ 多租户隔离——租户级路由与资源配额控制
🔧 响应式健康检查
▸ 暴露/health端点展示线程池、MQ连接等自定义指标
▸ 集成Prometheus实现自动告警
🔧 请求生命周期监控
▸ 使用MDC实现请求ID全链路传递
▸ 基于Micrometer统计接口百分位延迟
🔧 智能流量管控
▸ 灰度发布:按用户ID、设备类型路由流量
▸ 多租户:Header透传租户标识实现数据隔离
💡 亮点内容:
✔️ 自定义HealthIndicator检查数据库连接池、缓存命中率等关键指标
✔️ 基于Filter与AOP实现毫秒级请求生命周期日志追踪
✔️ 流量染色技术在灰度发布中的实战应用(Header传递+存储隔离)
✔️ 多租户系统如何实现动态数据源切换与限流
三、性能与运维
1. 响应式健康检查
【作用】
提供应用运行状态的实时监控:
- 基础设施健康状态(DB、Disk等)
- 外部服务依赖检查
- 自定义业务指标监控
- 与Kubernetes就绪/存活探针集成
【解决的问题】
- 服务可用性实时感知
- 依赖服务故障快速发现
- 自动化运维决策支持
- 云原生环境适配
【请求/响应示例】
基础健康检查:
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. 监控过滤器(入口统计)
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();
}
}
【生产环境集成】
- 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;
}
});
}
- Grafana监控看板配置:
{
"panels": [{
"title": "请求耗时分布",
"type": "heatmap",
"targets": [{
"expr": "histogram_quantile(0.95, sum(rate(http_server_requests_seconds_bucket[1m])) by (le, path))",
"legendFormat": "{{path}}"
}]
}]
}
- 告警规则示例:
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 }}"
- 日志关联配置(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、设备、地域等)
- 实时监控和自动回滚
- 新旧版本数据一致性保障
【解决的问题】
- 新功能上线导致的全系统风险
- 无法小范围验证新版本稳定性
- 突发流量激增缺乏熔断机制
- 多版本并行时的数据兼容问题
【请求/响应示例】
灰度识别请求:
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();
}
}
【关键设计】
- 流量染色:通过请求头/ Cookie 标记灰度流量
- 动态规则:支持运行时调整灰度策略
- 双写验证:确保新旧版本数据一致性
- 熔断机制:异常指标超阈值自动回滚
- 标记透传:保证灰度流量在微服务间持续传递
四、企业级扩展
1. 多租户请求路由
【作用】
实现企业级多租户隔离方案:
- 动态租户识别与路由
- 物理/逻辑隔离自由组合
- 租户级资源配额管理
- 跨租户数据共享控制
【解决的问题】
- 不同租户的SLA差异化保障
- 敏感数据的物理隔离需求
- 共享资源池的公平调度
- 租户自定义配置的热加载
【请求/响应示例】
租户请求示例:
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);
}
}
【关键设计】
- 标识传播:通过ThreadLocal + 拦截器实现全链路租户标识透传
- 动态路由:AbstractRoutingDataSource实现数据源动态切换
- 服务隔离:每个租户获取独立的Spring Bean实例
- 分级限流:租户级+全局两级流量控制
- 共享控制:注解驱动的方式声明跨租户访问权限
2. API 流量染色
【作用】
实现全链路流量管控:
- 请求生命周期追踪
- 灰度流量精准路由
- 压测流量自动隔离
- 故障注入模拟演练
【解决的问题】
- 生产环境无法区分真实/测试流量
- 全链路追踪时请求关联困难
- 压测流量污染生产数据
- 无法模拟特定异常场景
【请求/响应示例】
染色请求示例:
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;
}
}
【关键设计】
- 标记生成:结合随机策略+请求特征生成唯一染色标识
- 透传机制:通过请求头+MDC实现全链路传递
- 存储隔离:根据标签自动路由到影子库/测试库
- 差异化处理:服务根据染色标识执行特殊逻辑
- 监控分析:实时统计各染色流量的质量指标