HealthAggregator for springboot

  springboot

Order

For the monitoring of system services, tcp layer is usually performed by heartbeat, the simplest of which is ping-pong. For http layer, springboot’s actuator has built-in endpoint of /health, which is convenient to standardize the api of health status of each service, and HealthIndicator can expand itself to increase the health status of related dependent services, which is very flexible, convenient and extensible.

/health instance

{
  "status": "UP",
  "custom": {
    "status": "UNKNOWN",
    "custom": {
      "status": "UNKNOWN",
      "msg": "mock down to test aggregator"
    }
  },
  "diskSpace": {
    "status": "UP",
    "total": 249779191808,
    "free": 57925111808,
    "threshold": 10485760
  }
}

Status enumeration

    /**
     * Convenient constant value representing unknown state.
     */
    public static final Status UNKNOWN = new Status("UNKNOWN");

    /**
     * Convenient constant value representing up state.
     */
    public static final Status UP = new Status("UP");

    /**
     * Convenient constant value representing down state.
     */
    public static final Status DOWN = new Status("DOWN");

    /**
     * Convenient constant value representing out-of-service state.
     */
    public static final Status OUT_OF_SERVICE = new Status("OUT_OF_SERVICE");

Two sweet points of /health

For the status of multiple HealthIndicator, spring boot aggregrate them by default, and then calculates the value of the top-level status field. For the status is DOWN or OUT_OF_SERVICE, the returned http status code is 503. This is really very thoughtful for the application monitoring system. To sum up again:

  • Automatically aggregate the status of multiple HealthIndicator

  • For status is DOWN or OUT_OF_SERVICE, 503 is returned

In this way, when the monitoring system is applied, the returned results need not be analyzed, and can be judged directly according to the http status code, which is very convenient and too worry-free.

HealthAggregator

/**
 * Base {@link HealthAggregator} implementation to allow subclasses to focus on
 * aggregating the {@link Status} instances and not deal with contextual details etc.
 *
 * @author Christian Dupuis
 * @author Vedran Pavic
 * @since 1.1.0
 */
public abstract class AbstractHealthAggregator implements HealthAggregator {

    @Override
    public final Health aggregate(Map<String, Health> healths) {
        List<Status> statusCandidates = new ArrayList<Status>();
        for (Map.Entry<String, Health> entry : healths.entrySet()) {
            statusCandidates.add(entry.getValue().getStatus());
        }
        Status status = aggregateStatus(statusCandidates);
        Map<String, Object> details = aggregateDetails(healths);
        return new Health.Builder(status, details).build();
    }

    /**
     * Return the single 'aggregate' status that should be used from the specified
     * candidates.
     * @param candidates the candidates
     * @return a single status
     */
    protected abstract Status aggregateStatus(List<Status> candidates);

    /**
     * Return the map of 'aggregate' details that should be used from the specified
     * healths.
     * @param healths the health instances to aggregate
     * @return a map of details
     * @since 1.3.1
     */
    protected Map<String, Object> aggregateDetails(Map<String, Health> healths) {
        return new LinkedHashMap<String, Object>(healths);
    }

}

OrderedHealthAggregator#aggregateStatus

    @Override
    protected Status aggregateStatus(List<Status> candidates) {
        // Only sort those status instances that we know about
        List<Status> filteredCandidates = new ArrayList<Status>();
        for (Status candidate : candidates) {
            if (this.statusOrder.contains(candidate.getCode())) {
                filteredCandidates.add(candidate);
            }
        }
        // If no status is given return UNKNOWN
        if (filteredCandidates.isEmpty()) {
            return Status.UNKNOWN;
        }
        // Sort given Status instances by configured order
        Collections.sort(filteredCandidates, new StatusComparator(this.statusOrder));
        return filteredCandidates.get(0);
    }

It can be seen that the status is sorted and then the first status is taken, where the statusOrder is as follows:

private List<String> statusOrder;

    /**
     * Create a new {@link OrderedHealthAggregator} instance.
     */
    public OrderedHealthAggregator() {
        setStatusOrder(Status.DOWN, Status.OUT_OF_SERVICE, Status.UP, Status.UNKNOWN);
    }

Sort method

/**
     * {@link Comparator} used to order {@link Status}.
     */
    private class StatusComparator implements Comparator<Status> {

        private final List<String> statusOrder;

        StatusComparator(List<String> statusOrder) {
            this.statusOrder = statusOrder;
        }

        @Override
        public int compare(Status s1, Status s2) {
            int i1 = this.statusOrder.indexOf(s1.getCode());
            int i2 = this.statusOrder.indexOf(s2.getCode());
            return (i1 < i2 ? -1 : (i1 == i2 ? s1.getCode().compareTo(s2.getCode()) : 1));
        }

    }

I. e. status.down, status.out _ of _ service, status.up, status.unknown priority decreases in turn. Once DOWN occurs in the status, the overall status is DOWN, and so on.

doc