Ring-Coordinator

Overview

As mentioned in the previous section, the retry ring-coordinator is a separate service — distinct from your utility microservices — that runs as either a master or a slave, depending on how it is configured. This section walks through how the retry ring-coordinator is set up and how it works.

The Master and Slave retry ring-coordinator instances can also be deployed within the same service instance. Refer to the deployment topologies section for more details.
After adding the stacksaga-ring-coordinator-spring-boot-starter dependency to your project and configuring the retry ring-coordinator as master or slave, you can run the retry ring-coordinator as a Spring Boot application.

How Master and Slave Connect

Before configuring each role, it helps to see how a Master and a Slave find each other. On startup, a Slave opens a persistent RSocket connection to the Master using the host and port configured via stacksaga.coordinator.slave.target-master.*. The Master accepts the connection and adds the Slave to its registry — from its next 30-second publish cycle onward, the Master starts sending that Slave its token-range updates.

ring-coordinator-master-slave-handshake

The Retry-Coordinator-Slave section of the architecture guide covers what happens next — how the Slave divides its range across Orchestrator instances.

Ring-Coordinator As Master

If the retry ring-coordinator is configured as master, it is responsible for managing the entire token ring and sharing partitions of it with every slave retry ring-coordinator connected to it within the same region and cluster. Read more about the Master’s responsibilities here.

Add the stacksaga-ring-coordinator-spring-boot-starter dependency to your project as follows:

pom.xml
<dependencyManagement>
    <dependencies>
        <dependency> <!--Only for stacksaga dependencies version management-->
            <groupId>org.stacksaga</groupId>
            <artifactId>stacksaga-bom</artifactId>
            <version1.0.0-SNAPSHOT</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <dependency>
        <groupId>org.stacksaga</groupId>
        <artifactId>stacksaga-ring-coordinator-spring-boot-starter</artifactId>
    </dependency>
</dependencies>

To run the retry ring-coordinator as a Spring application, you do not need any web-related dependencies such as spring-boot-starter-web or spring-boot-starter-webflux — the retry ring-coordinator exposes no HTTP endpoints of its own, only RSocket endpoints for slave instances to connect to. It can therefore run as a non-web application.

In production, however, you will usually want visibility into its metrics and other monitoring data. Add the spring-boot-starter-actuator dependency to expose those endpoints, and additionally add one of the web starters only if you need the actuator endpoints reachable over HTTP.

Configuration

The retry ring-coordinator can be configured via application.properties or application.yml. Below is a minimal configuration for the retry ring-coordinator as master:

application.properties
#stacksaga-master coordinator configuration
stacksaga.coordinator.instance-type=master (1)
stacksaga.coordinator.target-services=order-service (2)

#stacksaga-instance configuration
stacksaga.instance.cluster=cluster1 (3)
stacksaga.instance.region=us-east-1 (4)
stacksaga.instance.zone=us-east-1a (5)

#rsocket server configuration
spring.rsocket.server.port=1000 (6)
spring.rsocket.server.address=localhost (7)
spring.rsocket.server.transport=tcp (8)
1 Set stacksaga.coordinator.instance-type to master, since this instance runs as the Master.
2 The target service(s) this Master coordinates retries for — order-service in this example. This can be a comma-separated list when a single Master manages multiple services; see the deployment topologies section.
3 The cluster this instance belongs to. Only slave retry ring-coordinator instances and Orchestrators in the same cluster can connect to this Master — see the Virtual Cluster section.
4 The region this instance belongs to. Only slave retry ring-coordinator instances and Orchestrators in the same region can connect to this Master — see the Regional Deployment section.
5 The zone this instance belongs to. It has no functional impact here, but should be set as per the StackSaga core specification.
6 The RSocket server port. slave retry ring-coordinator instances connect to the Master over RSocket — backed by the default RSocket server provided by spring-boot-starter-rsocket — and Orchestrators use the same port to look up which slave instance to connect to.
7 The RSocket network address that the server binds to.
8 The RSocket transport protocol. It must be tcp for retry ring-coordinator services.

Ring-Coordinator As Slave

If the retry ring-coordinator is configured as slave, it is responsible for connecting to the master retry ring-coordinator, subscribing to receive its assigned ring portion, and publishing sub-ring portions to the Orchestrator instances subscribed to it within the same region and cluster. Read more about the Slave’s responsibilities here.

Adding the stacksaga-ring-coordinator-spring-boot-starter dependency is exactly the same as for the Master — both roles use the same starter. Only the configuration differs, as shown below.

Configuration

application.properties
#stacksaga-slave coordinator configuration
stacksaga.coordinator.instance-type=slave (1)
stacksaga.coordinator.target-services=order-service (2)

#stacksaga-instance configuration
stacksaga.instance.cluster=cluster1 (3)
stacksaga.instance.region=us-east-1 (4)
stacksaga.instance.zone=us-east-1a (5)

#rsocket server configuration
spring.rsocket.server.port=1001 (6)
spring.rsocket.server.address=localhost (7)
spring.rsocket.server.transport=tcp (8)

#connecting to the master retry ring-coordinator
stacksaga.coordinator.slave.target-master.port=1000 (9)
stacksaga.coordinator.slave.target-master.host=localhost (10)
1 Set stacksaga.coordinator.instance-type to slave, since this instance runs as a Slave.
2 The target service(s) this Slave subscribes to on the Master, receiving their ring portion and publishing sub-ring portions to the subscribed Orchestrator instances. This can be a comma-separated list when a single Slave subscribes for multiple services; see the deployment topologies section.
If a Slave supports multiple services, all of those services must also be supported by the Master. Otherwise, the connection between the Master and Slave will not be established.
3 The cluster this instance belongs to — same semantics as for the Master. The Master, and any Orchestrator instances that want to subscribe to this Slave, must share this cluster name for the connection to succeed.
4 The region this instance belongs to — same semantics as for the Master. The Master, and any subscribing Orchestrator instances, must share this region for the connection to succeed.
5 The zone this instance belongs to. It has no functional impact here, but should be set as per the StackSaga core specification.
6 The RSocket server port. Orchestrator instances connect to this Slave over RSocket to receive their sub-range updates.
7 The RSocket network address that the server binds to.
8 The RSocket transport protocol. It must be tcp for retry ring-coordinator services.
9 The RSocket server port of the Master this Slave connects to, in the same region and cluster.
10 The RSocket server host of the Master this Slave connects to, in the same region and cluster.

Configuration Properties Reference

The following table summarizes all the configuration properties for the retry ring-coordinator. The first group applies to both master and slave instances; the second group applies only to slave instances.

Properties shown with a dash (-) in the Default column are mandatory and have no default — you must set them explicitly.
Property Name Default Type Description

Common — applies to both Master and Slave instances

stacksaga.coordinator.instance-type

-

InstanceType (Enum)

The role this instance runs as — master or slave. This is the primary property that determines the instance’s responsibilities and how it connects to the other components in the architecture.

stacksaga.coordinator.target-services

-

String

A comma-separated list of the service(s) this instance handles. The Master uses it to decide which Slaves may connect to it; a Slave uses it to decide which Master to connect to. Every service supported by a Slave must also be supported by its Master, otherwise the connection is rejected — but a Master may support more services than any single Slave.

spring.rsocket.server.port

-

int

The port the embedded RSocket server binds to. Slaves connect to the Master on this port, and Orchestrators connect to a Slave on this port, so it serves both connection types.

spring.rsocket.server.address

-

String

The network address the embedded RSocket server binds to.

spring.rsocket.server.transport

-

String

The transport protocol for the embedded RSocket server. Must be tcp for retry ring-coordinator services.

stacksaga.instance.cluster

-

String

The cluster this instance belongs to. Components only connect to one another when their cluster values match, so it must be identical across every Master, Slave, and Orchestrator that needs to connect.

stacksaga.instance.region

-

String

The region this instance belongs to. Components only connect to one another when their region values match, so it must be identical across every Master, Slave, and Orchestrator that needs to connect.

stacksaga.instance.zone

-

String

The zone this instance belongs to. It has no functional impact here, but should be set as per the StackSaga core specification.

Slave-only — applies to slave instances

stacksaga.coordinator.slave.target-master.host

-

String

The host of the Master this Slave connects to (in the same region and cluster). Used to establish the RSocket connection from the Slave to the Master.

stacksaga.coordinator.slave.target-master.port

-

int

The port of the Master this Slave connects to (in the same region and cluster). Used to establish the RSocket connection from the Slave to the Master.

stacksaga.coordinator.slave.target-master.reconnect.max-retries

10

int

Maximum number of TCP reconnect attempts when establishing the connection to the Master. If the Slave still cannot connect after this many attempts, the instance shuts down.

stacksaga.coordinator.slave.target-master.reconnect.backoff-duration

2s

Duration

The initial backoff between TCP reconnect attempts. After a failed attempt the Slave waits this long before retrying, growing on each subsequent attempt up to max-backoff-duration.

stacksaga.coordinator.slave.target-master.reconnect.max-backoff-duration

6s

Duration

The upper bound on the TCP reconnect backoff. The growing backoff between reconnect attempts never exceeds this value.

stacksaga.coordinator.slave.target-master.retry.max-retries

Long.MAX_VALUE

long

Maximum number of RSocket stream retry attempts before giving up. This governs recovery of the RSocket stream itself, and is distinct from the TCP reconnect attempts above. Defaults to Long.MAX_VALUE (effectively infinite), which is recommended in production so the stream is always eventually recovered.

stacksaga.coordinator.slave.target-master.retry.backoff-duration

2s

Duration

The initial backoff between RSocket stream retry attempts. After a failed attempt the Slave waits this long before retrying, growing on each subsequent attempt up to max-backoff-duration.

stacksaga.coordinator.slave.target-master.retry.max-backoff-duration

6s

Duration

The upper bound on the RSocket stream retry backoff. The growing backoff between retry attempts never exceeds this value.

stacksaga.coordinator.slave.target-master.retry.jitter

0.5

double

The jitter factor applied to the RSocket stream retry backoff. Randomizing the backoff avoids a thundering-herd problem when many Slaves reconnect to the Master at once. Must be between 0 (no jitter) and 1 (full jitter — a random value between 0 and the backoff duration).

stacksaga.coordinator.slave.lookup.ip-prefer

false

boolean

Controls what the Master hands back to Orchestrators when they look up the available Slaves to connect to. When true, the Master returns Slave IP addresses; when false (the default), it returns Slave hostnames.