Backend Development 14 min read

Implementing Traffic Replication in an API Gateway with Sentinel

This article explains how to extend Sentinel's flow‑control capabilities in a Spring Cloud Gateway to implement traffic replication, covering Sentinel fundamentals, its slot‑chain architecture, custom filter code, configuration steps, and practical usage for testing and load‑testing with real traffic.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
Implementing Traffic Replication in an API Gateway with Sentinel

1. Introduction

API gateways emerged alongside the microservice architecture to provide a unified entry point for traffic management, handling cross‑cutting concerns such as authentication, rate limiting, monitoring, and logging. Embedding these functions in each microservice leads to high management overhead, so a centralized gateway is preferred.

Sentinel is a distributed‑system traffic guard that offers flow control, routing, circuit breaking, overload protection, and hotspot traffic defense. By integrating Sentinel into an API gateway and customizing its rules, large‑scale traffic (over 100 million requests per day in the example) can be controlled in real time.

Traffic replication copies incoming requests to an additional service, allowing real‑traffic validation and performance testing. The following sections describe how to customize Sentinel to achieve this capability.

2. Basic Concepts of Sentinel

Sentinel protects resources, which can be services, methods, or arbitrary code blocks. The protection workflow consists of defining a resource, defining rules for that resource, and verifying that the rules take effect.

3. Sentinel Flow Architecture

Each resource is identified by a resourceName and creates an Entry object on each invocation. Entries are generated automatically via framework adapters, annotations, or explicitly through the SphU API. Sentinel uses a responsibility‑chain (slot chain) to process rules, with slots such as:

NodeSelectorSlot : collects call paths for hierarchical flow control.

ClusterBuilderSlot : stores statistical data (RT, QPS, thread count) for multi‑dimensional limiting.

StatisticSlot : records runtime metrics.

FlowSlot : enforces flow‑control rules based on statistics.

AuthoritySlot : applies blacklist/whitelist checks.

DegradeSlot : performs circuit‑breaker degradation.

SystemSlot : limits overall traffic using system metrics like load average.

For gateway flow control, a custom GatewayFlowSlot is added to the slot chain, enabling fine‑grained rule checks and traffic replication.

The diagram shows how the gateway filter parses request attributes, forwards them to the Sentinel slot chain, and applies the custom GatewayFlowSlot logic.

4. Specific Implementation of Traffic Replication

When a request reaches the gateway, SentinelGatewayFilter extracts the API group and invokes the following method:

@Slf4j
public class SentinelGatewayFilter implements GatewayFilter, GlobalFilter, Ordered {
    // ... omitted code ...
    @Override
    public Mono
filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
        Mono
asyncResult = chain.filter(exchange);
        if (route != null) {
            asyncResult = onResourceModeRouteId(exchange, route, asyncResult);
            asyncResult = onResourceModelCustomApiName(exchange, route, asyncResult);
        }
        return asyncResult;
    }
    // ... omitted code ...
    private Mono
onResourceModelCustomApiName(ServerWebExchange exchange, Route route, Mono
asyncResult) {
        Set
matchingApis = pickMatchingApiDefinitions(exchange);
        for (String apiName : matchingApis) {
            Object[] params = paramParser.parseParameterFor(apiName, exchange,
                r -> r.getResourceMode() == SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME);
            Object[] paramsWithConsumer = new Object[params.length + 1];
            System.arraycopy(params, 0, paramsWithConsumer, 0, params.length);
            final List
customizedRuleList = new ArrayList<>();
            paramsWithConsumer[params.length] = (GatewayFlowSlot.GatewayFlowCustomizedRuleContainer)customizedRuleList::add;
            exchange.getAttributes().put(GatewayFlowSlot.CUSTOMIZED_RULE_LIST_KEY, customizedRuleList);
            EntryConfig entryConfig = this.getCustomizedEntryConfig(
                exchange, route, apiName, paramsWithConsumer, null);
            asyncResult = asyncResult.transform(new SentinelReactorTransformer<>(entryConfig));
        }
        return asyncResult;
    }
    // ... omitted code ...
}

The method pickMatchingApiDefinitions matches the request against configured API groups. If a match is found, an EntryConfig is built, which includes callbacks to copyServiceConfig . The GatewayFlowSlot then decides whether to replicate the request.

@SpiOrder(-4000)
public class GatewayFlowSlot extends AbstractLinkedProcessorSlot
{
    // ... omitted code ...
    private void checkGatewayParamFlow(ResourceWrapper resourceWrapper, int count, Object... args) throws BlockException {
        if (args == null) return;
        List
rules = GatewayRuleManager.getConvertedParamRules(resourceWrapper.getName());
        if (rules == null || rules.isEmpty()) return;
        for (ParamFlowRule rule : rules) {
            ParameterMetricStorage.initParamMetricsFor(resourceWrapper, rule);
            if (!ParamFlowChecker.passCheck(resourceWrapper, rule, count, args)) {
                if (rule.isCustomized()) {
                    for (Object object : args) {
                        if (object instanceof GatewayFlowSlot.GatewayFlowCustomizedRuleContainer) {
                            ((GatewayFlowSlot.GatewayFlowCustomizedRuleContainer) object).accept(rule);
                        }
                    }
                } else {
                    String triggeredParam = "";
                    if (args.length > rule.getParamIdx()) {
                        Object value = args[rule.getParamIdx()];
                        triggeredParam = String.valueOf(value);
                    }
                    throw new ParamFlowException(resourceWrapper.getName(), triggeredParam, rule);
                }
            }
        }
    }
    // ... omitted code ...
}

The CopyServiceFilter located after the gateway actually forwards the duplicated request to the target service:

@Slf4j
public class CopyServiceFilter implements GlobalFilter, Ordered {
    // ... omitted code ...
    @Override
    public Mono
filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        List
copyServiceIds = this.getCopyServiceIds(exchange);
        if (copyServiceIds.isEmpty()) {
            return chain.filter(exchange);
        }
        exchange.getAttributes().put(COPY_SERVICE_ENABLED, true);
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            for (String copyServiceId : copyServiceIds) {
                Mono.defer(() -> execCopyServiceRequest(exchange, copyServiceId,
                    ResponseVO.builder()
                        .originServiceId(((Route) Objects.requireNonNull(exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR))).getUri().getHost())
                        .originalRequestUri(exchange.getRequest().getURI())
                        .originRequestBody(exchange.getAttribute(RequestResponseLoggingFilter.REQUEST_BODY))
                        .originResponseBody(exchange.getAttribute(RequestResponseLoggingFilter.RESPONSE_BODY))
                        .originResponseStatusCode(Optional.of(exchange)
                            .map(ServerWebExchange::getResponse)
                            .map(ServerHttpResponse::getRawStatusCode)
                            .orElse(200))
                        .originResponseHeaders(exchange.getResponse().getHeaders().toSingleValueMap())
                        .build()
                )).onErrorResume(error -> {
                    log.warn(error.getMessage(), error);
                    return Mono.empty();
                }).subscribe(responseVO -> {
                    if (Boolean.TRUE.equals(exchange.getAttribute(GatewayFlowSlot.COPY_SERVICE_ID_COMPARE_KEY))) {
                        this.publishCopyServiceResponseBody(responseVO);
                    }
                });
            }
        }));
    }
    // ... omitted code ...
}

5. How to Use Traffic Replication

Define the API group whose traffic should be replicated (e.g., /user/user-space-api.online/account/v1/accountSimple ).

Configure a replication rule that copies a percentage (e.g., 10%) of the selected API's traffic to a target service (e.g., UGC.SPACES-USER-API-READ.ONLINE ) via the Sentinel rule management UI.

After saving, the rule is stored as a JSON string in Apollo configuration, pushed to each gateway node, and read by Sentinel to activate traffic copying.

Resulting traffic charts show the original service’s traffic and the duplicated traffic on the target service.

6. Conclusion

By replicating live traffic to a UAT or test environment, developers can validate functional correctness and perform large‑scale performance testing with real user requests, and later compare responses from the original and replica services for consistency.

7. References

1. https://github.com/alibaba/Sentinel 2. https://sentinelguard.io/zh-cn/docs/introduction.html

JavamicroservicesAPI GatewaySentinelflow controlSpring Cloud Gatewaytraffic replication
Sohu Tech Products
Written by

Sohu Tech Products

A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.