# SDS Heartbeat Mechanism and Strategy Distribution: A Complete Interaction Flow Guide

> Understand the SDS heartbeat mechanism and strategy distribution interaction flow. Learn how clients sync runtime metrics and get updated strategies ensuring real-time propagation with low latency.

- Repository: [DiDi/sds](https://github.com/didi/sds)
- Tags: deep-dive
- Published: 2026-02-28

---

**The SDS heartbeat mechanism and strategy distribution interaction flow involves a bidirectional sync where clients periodically upload runtime metrics to the server while pulling updated downgrade strategies every 5 seconds, ensuring real-time strategy propagation with minimal latency.**

In the didi/sds repository, the client-side heartbeat service and server-side strategy distribution work together to maintain a self-healing degradation system. This article breaks down the exact interaction flow between the SDS heartbeat mechanism and strategy distribution, referencing actual source file paths and method implementations from the codebase.

## How the SDS Heartbeat Mechanism Uploads Runtime Metrics

### Initializing the Heartbeat Service

The interaction begins when the client initializes the singleton heartbeat service. In [`sds-client/src/main/java/com/didiglobal/sds/client/service/SdsHeartBeatService.java`](https://github.com/didi/sds/blob/main/sds-client/src/main/java/com/didiglobal/sds/client/service/SdsHeartBeatService.java), the `createOnlyOne(..)` method builds the server URL list and triggers the first pull of strategies.

```java
public static void createOnlyOne(String appGroupName,
                                 String appName,
                                 String serverAddrList) {
    // Singleton initialization logic
    // Builds server URL list and triggers initial strategy pull
}

```

### Periodic Upload of Cycle Statistics

Once initialized, the client periodically uploads runtime statistics. The `uploadHeartbeatData()` method collects the latest cycle statistics from `SdsPowerfulCounterService`, builds a `HeartbeatRequest`, and POSTs it to `/sds/heartbeat/add`.

```java
public void uploadHeartbeatData() {
    Date now = new Date();
    Map<String, SdsCycleInfo> pointInfoMap = buildPointCycleInfo(now.getTime());

    HeartbeatRequest clientRequest = new HeartbeatRequest();
    clientRequest.setAppGroupName(appGroupName);
    clientRequest.setAppName(appName);
    clientRequest.setIp(IpUtils.getIp());
    clientRequest.setHostname(IpUtils.getHostname());
    clientRequest.setStatisticsCycleTime(
        new Date(DateUtils.getLastCycleEndTime(CYCLE_BUCKET_NUM * BUCKET_TIME, now.getTime())));
    clientRequest.setPointInfoMap(pointInfoMap);
    
    HttpUtils.post(getCurUploadUrl(), param);
}

```

### Server-Side Persistence

On the server side, [`sds-admin/src/main/java/com/didiglobal/sds/admin/controller/HeartbeatController.java`](https://github.com/didi/sds/blob/main/sds-admin/src/main/java/com/didiglobal/sds/admin/controller/HeartbeatController.java) receives the request via the `heartbeat()` method. This parses the JSON, validates the request, and forwards to `HeartbeatService.saveHeartbeatInfo`.

In [`sds-admin/src/main/java/com/didiglobal/sds/admin/service/impl/HeartbeatServiceImpl.java`](https://github.com/didi/sds/blob/main/sds-admin/src/main/java/com/didiglobal/sds/admin/service/impl/HeartbeatServiceImpl.java), the `saveHeartbeatInfo()` method persists the heartbeat data via `HeartbeatDao` and updates the point-dictionary table, making the metrics visible to the monitoring UI.

## Strategy Distribution: Pulling the Latest Downgrade Configuration

### Client-Side Strategy Pull Request

Every 5 seconds, the client checks for updated strategies. The `updatePointStrategyFromWebServer()` method in `SdsHeartBeatService` sends a `HeartbeatRequest` containing the client's current **version** and the list of points it cares about to `/sds/heartbeat/pullstrategy`.

```java
public void updatePointStrategyFromWebServer() {
    HeartbeatRequest clientRequest = new HeartbeatRequest();
    clientRequest.setAppGroupName(appGroupName);
    clientRequest.setAppName(appName);
    clientRequest.setIp(IpUtils.getIp());
    clientRequest.setHostname(IpUtils.getHostname());
    clientRequest.setVersion(version);                // client's known version
    clientRequest.setPointList(allPointsInUse);       // from SdsPowerfulCounterService
    
    // HTTP POST to /sds/heartbeat/pullstrategy
    HeartBeatResponse response = JSON.parseObject(body, HeartBeatResponse.class);
    if (response.isChanged()) {
        version = response.getVersion();
        resetPointInfo(convertToMap(response.getStrategies()));
    }
}

```

### Server-Side Version Comparison

The server endpoint `HeartbeatController#pullPointStrategy()` parses the request and calls `HeartbeatService.checkAndGetNewestPointStrategy()`. In `HeartbeatServiceImpl`, the server compares the client's version with the stored `AppInfo.version`.

If the versions differ, the server fetches the latest `PointStrategy` and `PointReturnValue` from the database via `PointStrategyDao` and `PointReturnValueDao`, builds a `HeartBeatResponse` with `changed=true`, and returns the new version and strategy list.

```java
public HeartBeatResponse checkAndGetNewestPointStrategy(HeartbeatRequest heartbeatRequest) {
    // validate request
    AppInfoDO appInfoDO = appInfoDao.queryAppInfo(...);
    if (Objects.equals(heartbeatRequest.getVersion(), appInfoDO.getVersion())) {
        response.setChanged(false);
        return response;
    }
    // version changed → fetch PointStrategy and ReturnValue
    List<PointStrategyDO> pointStrategyDOList = pointStrategyDao.queryPointStrategyBatch(...);
    // build SdsStrategy list
    response.setChanged(true);
    response.setVersion(appInfoDO.getVersion());
    response.setStrategies(strategies);
    return response;
}

```

### Client Cache Refresh

When the client receives a response with `isChanged()` set to true, it resets its internal maps. The `resetPointInfo()` method calls `SdsStrategyService.resetAll(strategies)` to update the strategy cache and `SdsDowngradeReturnValueService.reset(strategyMap)` to update return-value configuration.

All subsequent downgrade checks use the fresh strategy data from `SdsStrategyService`, which holds the latest point-to-strategy mapping used by the executor chain during downgrade checks.

## Complete Code Example: Setting Up the Heartbeat Client

```java
// ---------------------------------------------------
// 1️⃣ Initialise the SDS client (once per JVM)
String appGroup = "order-service";
String appName  = "order-create";
String servers  = "http://sds-server-1:8080,http://sds-server-2:8080";

SdsClient client = SdsClientFactory.getOrCreateSdsClient(
        appGroup, appName, servers);   // internally calls SdsHeartBeatService.createOnlyOne

// ---------------------------------------------------
// 2️⃣ Start the periodic heartbeat (already scheduled by CycleDataService)
// No extra code needed – uploadHeartbeatData() is invoked automatically every cycle.

// ---------------------------------------------------
// 3️⃣ (Optional) Manually trigger a strategy pull
SdsHeartBeatService.getInstance().updatePointStrategyFromWebServer();

```

All the heavy lifting is performed inside the heartbeat service; the client developer only needs to create the client once.

## Key Source Files in the SDS Repository

| File | Responsibility | Link |
|------|----------------|------|
| [`sds-client/src/main/java/com/didiglobal/sds/client/service/SdsHeartBeatService.java`](https://github.com/didi/sds/blob/main/sds-client/src/main/java/com/didiglobal/sds/client/service/SdsHeartBeatService.java) | Core client heartbeat & pull logic | [view](https://github.com/didi/sds/blob/master/sds-client/src/main/java/com/didiglobal/sds/client/service/SdsHeartBeatService.java) |
| [`sds-admin/src/main/java/com/didiglobal/sds/admin/controller/HeartbeatController.java`](https://github.com/didi/sds/blob/main/sds-admin/src/main/java/com/didiglobal/sds/admin/controller/HeartbeatController.java) | HTTP entry points (`add`, `pullstrategy`) | [view](https://github.com/didi/sds/blob/master/sds-admin/src/main/java/com/didiglobal/sds/admin/controller/HeartbeatController.java) |
| [`sds-admin/src/main/java/com/didiglobal/sds/admin/service/impl/HeartbeatServiceImpl.java`](https://github.com/didi/sds/blob/main/sds-admin/src/main/java/com/didiglobal/sds/admin/service/impl/HeartbeatServiceImpl.java) | Business logic for persisting heartbeats & delivering strategies | [view](https://github.com/didi/sds/blob/master/sds-admin/src/main/java/com/didiglobal/sds/admin/service/impl/HeartbeatServiceImpl.java) |
| [`sds-client/src/main/java/com/didiglobal/sds/client/service/SdsStrategyService.java`](https://github.com/didi/sds/blob/main/sds-client/src/main/java/com/didiglobal/sds/client/service/SdsStrategyService.java) | In-memory cache of point → `SdsStrategy` mapping, refreshed on pull | [view](https://github.com/didi/sds/blob/master/sds-client/src/main/java/com/didiglobal/sds/client/service/SdsStrategyService.java) |
| [`sds-client/src/main/java/com/didiglobal/sds/client/bean/HeartbeatRequest.java`](https://github.com/didi/sds/blob/main/sds-client/src/main/java/com/didiglobal/sds/client/bean/HeartbeatRequest.java) & [`HeartBeatResponse.java`](https://github.com/didi/sds/blob/main/HeartBeatResponse.java) | Data contract between client and server | [request](https://github.com/didi/sds/blob/master/sds-client/src/main/java/com/didiglobal/sds/client/bean/HeartbeatRequest.java) / [response](https://github.com/didi/sds/blob/master/sds-client/src/main/java/com/didiglobal/sds/client/bean/HeartBeatResponse.java) |
| [`sds-admin/src/main/java/com/didiglobal/sds/admin/dao/PointStrategyDao.java`](https://github.com/didi/sds/blob/main/sds-admin/src/main/java/com/didiglobal/sds/admin/dao/PointStrategyDao.java) | Fetches latest strategies for a given app | (interface path) [`sds-admin/src/main/java/com/didiglobal/sds/admin/dao/PointStrategyDao.java`](https://github.com/didi/sds/blob/main/sds-admin/src/main/java/com/didiglobal/sds/admin/dao/PointStrategyDao.java) |

## Summary

- **Bidirectional Sync**: The SDS heartbeat mechanism and strategy distribution operate as a continuous loop—clients upload metrics while simultaneously pulling configuration updates.
- **Version-Based Updates**: The server compares the client's known version against the stored `AppInfo.version` to determine if new strategies must be returned, minimizing unnecessary data transfer.
- **Automatic Refresh**: Upon detecting a version change, the client invokes `SdsStrategyService.resetAll()` and `SdsDowngradeReturnValueService.reset()` to immediately apply new downgrade rules without requiring application restart.
- **5-Second Pull Interval**: The strategy pull runs on a fixed 5-second schedule, ensuring that critical degradation configuration changes propagate to all client instances with minimal latency.

## Frequently Asked Questions

### How often does the SDS client pull strategy updates?

The SDS client pulls strategy updates every **5 seconds** via the `updatePointStrategyFromWebServer()` method in `SdsHeartBeatService`. This fixed interval ensures that any changes made in the SDS admin console propagate to client applications with minimal delay, while the version comparison logic prevents unnecessary data processing when configurations remain unchanged.

### What data is included in the SDS heartbeat request?

The `HeartbeatRequest` object sent from client to server includes the **application group name**, **application name**, **client IP and hostname**, **current version number**, and a **map of point statistics** (`pointInfoMap`) containing cycle counters from `SdsPowerfulCounterService`. When pulling strategies, the request also includes the list of points the client currently cares about (`pointList`), allowing the server to return only relevant configurations.

### How does the server detect if a client needs new strategies?

The server detects the need for new strategies through **version comparison** in `HeartbeatServiceImpl.checkAndGetNewestPointStrategy()`. The server queries the `AppInfo` table to retrieve the current global version for the application group and name, then compares it against the version sent by the client in the `HeartbeatRequest`. If `Objects.equals()` returns false, the server fetches the latest `PointStrategy` and `PointReturnValue` records from the database and returns them with `changed=true`.

### What happens when the SDS client receives updated strategies?

When the client receives a response with `isChanged()` set to true, it immediately refreshes its internal caches through the `resetPointInfo()` method. This invokes `SdsStrategyService.resetAll(strategies)` to update the point-to-strategy mapping and `SdsDowngradeReturnValueService.reset(strategyMap)` to update return-value configurations. These changes take effect instantly for all subsequent downgrade checks without requiring application restart, as the executor chain reads directly from the refreshed `SdsStrategyService` cache.