Skip to content

Spring Cloud Discovery Starter

The CoSky Spring Cloud Discovery Starter provides seamless integration between CoSky's Redis-backed service registry and the Spring Cloud Discovery model. It supplies both blocking (DiscoveryClient) and reactive (ReactiveDiscoveryClient) implementations, automatic service registration with heartbeat renewal, and a weighted load balancer -- all without running a separate discovery server. Services register themselves into Redis on startup, discover each other through Redis queries, and automatically deregister on shutdown.

At a Glance

ComponentResponsibilityKey FileSource
CoSkyDiscoveryAutoConfigurationWires core discovery beans (service discovery, topology, event listeners, load balancer)CoSkyDiscoveryAutoConfiguration.ktcosky-spring-cloud-starter-discovery/.../CoSkyDiscoveryAutoConfiguration.kt:47
CoSkyDiscoveryClientBlocking DiscoveryClient adapterCoSkyDiscoveryClient.ktcosky-spring-cloud-starter-discovery/.../CoSkyDiscoveryClient.kt:24
CoSkyReactiveDiscoveryClientReactive ReactiveDiscoveryClient adapterCoSkyReactiveDiscoveryClient.ktcosky-spring-cloud-starter-discovery/.../CoSkyReactiveDiscoveryClient.kt:26
CoSkyServiceRegistryRegisters and deregisters instances via RedisCoSkyServiceRegistry.ktcosky-spring-cloud-starter-discovery/.../CoSkyServiceRegistry.kt:25
CoSkyRegistrationHolds service instance metadata for registrationCoSkyRegistration.ktcosky-spring-cloud-starter-discovery/.../CoSkyRegistration.kt:26
CoSkyAutoServiceRegistrationWeb-app auto-registration (port from embedded server)CoSkyAutoServiceRegistration.ktcosky-spring-cloud-starter-discovery/.../CoSkyAutoServiceRegistration.kt:25
CoSkyAutoServiceRegistrationOfNoneWebNon-web-app auto-registration (uses PID as port)CoSkyAutoServiceRegistrationOfNoneWeb.ktcosky-spring-cloud-starter-discovery/.../CoSkyAutoServiceRegistrationOfNoneWeb.kt:31

Configuration Properties

Discovery Properties

Bound by CoSkyDiscoveryProperties.kt:24 under prefix spring.cloud.cosky.discovery.

PropertyDefaultDescription
spring.cloud.cosky.discovery.enabledtrueEnable or disable the CoSky discovery starter.
spring.cloud.cosky.discovery.order0Order of the discovery client in the composite chain.
spring.cloud.cosky.discovery.timeout2sTimeout for blocking discovery operations.

Registry Properties

Bound by CoSkyRegistryProperties.kt:27 under prefix spring.cloud.cosky.discovery.registry.

PropertyDefaultDescription
spring.cloud.cosky.discovery.registry.service-id${spring.application.name}Service name used for registration. Falls back to the application name.
spring.cloud.cosky.discovery.registry.schemahttpProtocol scheme (http or https).
spring.cloud.cosky.discovery.registry.hostauto-detectedHost IP address. Auto-detected via InetUtils if blank.
spring.cloud.cosky.discovery.registry.port0Service port. For web apps, auto-detected from the embedded server.
spring.cloud.cosky.discovery.registry.weight1Instance weight for weighted load balancing.
spring.cloud.cosky.discovery.registry.is-ephemeraltrueWhether the instance is ephemeral (requires heartbeat renewal).
spring.cloud.cosky.discovery.registry.ttl60sTime-to-live for the instance; must be renewed before expiry.
spring.cloud.cosky.discovery.registry.timeout2sTimeout for blocking registry operations.
spring.cloud.cosky.discovery.registry.initial-statusUPInitial instance status (UP or OUT_OF_SERVICE).
spring.cloud.service-registry.auto-registration.enabledtrueEnable or disable auto-registration.

Auto-Configuration Chain

The discovery starter uses a layered auto-configuration approach. CoSkyDiscoveryAutoConfiguration wires the core infrastructure beans (Redis-based service discovery, event listeners, load balancer). Then, depending on the application type, CoSkyDiscoveryClientConfiguration or CoSkyReactiveDiscoveryClientConfiguration provides the appropriate Spring Cloud discovery client. Finally, CoSkyAutoServiceRegistrationAutoConfiguration handles service registration.

mermaid
flowchart TB
    subgraph Core["Core Discovery Infrastructure"]
        direction TB
        A["CoSkyAutoConfiguration<br>(Redis connection)"]
        A --> B["CoSkyDiscoveryAutoConfiguration"]
    end

    subgraph Clients["Discovery Clients"]
        direction TB
        B --> C["CoSkyDiscoveryClientConfiguration<br>(blocking)"]
        B --> D["CoSkyReactiveDiscoveryClientConfiguration<br>(reactive)"]
    end

    subgraph Registration["Service Registration"]
        direction TB
        B --> E["CoSkyAutoServiceRegistrationAutoConfiguration"]
        E --> F["CoSkyAutoServiceRegistration<br>(web apps)"]
        E --> G["CoSkyAutoServiceRegistrationOfNoneWeb<br>(non-web apps)"]
    end

    B --> H["BinaryWeightRandomLoadBalancer"]

    style Core fill:#161b22,stroke:#30363d,color:#e6edf3
    style Clients fill:#161b22,stroke:#30363d,color:#e6edf3
    style Registration fill:#161b22,stroke:#30363d,color:#e6edf3
    style A fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style B fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style C fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style D fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style E fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style F fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style G fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style H fill:#2d333b,stroke:#6d5dfc,color:#e6edf3

Discovery Client

CoSky provides two discovery client implementations that adapt the CoSky ServiceDiscovery API to Spring Cloud interfaces.

Blocking: CoSkyDiscoveryClient

The CoSkyDiscoveryClient implements Spring's DiscoveryClient. It delegates to the reactive ServiceDiscovery and blocks with the configured timeout (CoSkyDiscoveryClient.kt:33). Each ServiceInstance from CoSky is wrapped in a CoSkyServiceInstance adapter.

Reactive: CoSkyReactiveDiscoveryClient

The CoSkyReactiveDiscoveryClient implements ReactiveDiscoveryClient and returns Flux<ServiceInstance> directly without blocking (CoSkyReactiveDiscoveryClient.kt:32).

CoSkyServiceInstance Adapter

CoSkyServiceInstance is a simple data class that wraps CoSky's ServiceInstance and adapts it to Spring Cloud's ServiceInstance interface, mapping instanceId, serviceId, host, port, isSecure, uri, metadata, and scheme (CoSkyServiceInstance.kt:23).

Service Discovery Lookup Flow

mermaid
sequenceDiagram
    autonumber
    participant App as Application Code
    participant DC as DiscoveryClient
    participant SD as ServiceDiscovery
    participant Redis as Redis

    App->>DC: getInstances("order-service")
    DC->>SD: getInstances(serviceId)
    SD->>Redis: SMEMBERS + HMGET
    Redis-->>SD: instance data
    SD-->>DC: List<ServiceInstance>
    DC->>DC: wrap in CoSkyServiceInstance
    DC-->>App: List<ServiceInstance>

    App->>DC: getServices()
    DC->>SD: getServices()
    SD->>Redis: SMEMBERS services key
    Redis-->>SD: service names
    SD-->>DC: List<String>
    DC-->>App: List<String>

Service Registration

CoSkyServiceRegistry

The CoSkyServiceRegistry implements Spring's ServiceRegistry<CoSkyRegistration> interface. On register(), it delegates to the CoSky ServiceRegistry to persist the instance in Redis, then starts the heartbeat RenewInstanceService (CoSkyServiceRegistry.kt:30). On deregister(), it removes the instance and stops the heartbeat. The close() method also stops the renewal service (CoSkyServiceRegistry.kt:45).

CoSkyRegistration

CoSkyRegistration implements Spring's Registration interface. It carries the serviceId, scheme, host, port, weight, isEphemeral, and metadata needed for registration (CoSkyRegistration.kt:26). The asServiceInstance() method converts it into a CoSky ServiceInstance suitable for the registry call.

Auto-Registration for Web Apps

CoSkyAutoServiceRegistration extends AbstractAutoServiceRegistration. When the embedded web server starts, it captures the dynamically assigned port and calls register() (CoSkyAutoServiceRegistration.kt:48). It also stores the instance in ServiceInstanceContext for downstream use.

Auto-Registration for Non-Web Apps

CoSkyAutoServiceRegistrationOfNoneWeb handles non-web applications (e.g. gRPC services, CLI tools). It listens for ApplicationStartedEvent and, if the application context is not a WebServerApplicationContext, registers the service using the process ID (PID) as the port (CoSkyAutoServiceRegistrationOfNoneWeb.kt:51). This provides a meaningful identifier even when there is no HTTP port.

Service Registration Flow

mermaid
sequenceDiagram
    autonumber
    participant Boot as Spring Boot
    participant ASR as CoSkyAutoServiceRegistration
    participant Reg as CoSkyRegistration
    participant SR as CoSkyServiceRegistry
    participant Redis as Redis
    participant Renew as RenewInstanceService

    Boot->>ASR: WebServerInitializedEvent
    ASR->>Reg: getPort() - capture dynamic port
    ASR->>ASR: ServiceInstanceContext.serviceInstance = ...
    ASR->>SR: register(registration)
    SR->>Reg: asServiceInstance()
    SR->>Redis: register instance (HMSET)
    Redis-->>SR: true
    SR->>Renew: start() - begin heartbeat
    Renew->>Redis: periodic renew (TTL refresh)

Service Deregistration Flow

mermaid
sequenceDiagram
    autonumber
    participant Boot as Spring Boot
    participant SR as CoSkyServiceRegistry
    participant Reg as CoSkyRegistration
    participant Redis as Redis
    participant Renew as RenewInstanceService

    Boot->>SR: close() / @PreDestroy
    SR->>Renew: stop() - end heartbeat
    SR->>Reg: asServiceInstance()
    SR->>Redis: deregister instance
    Redis-->>SR: true

Consistency Layer

The discovery starter uses ConsistencyRedisServiceDiscovery as the primary ServiceDiscovery bean (CoSkyDiscoveryAutoConfiguration.kt:81). This decorator wraps RedisServiceDiscovery with local consistency guarantees by subscribing to:

This event-driven approach ensures that local service caches are invalidated and refreshed promptly without relying on polling.

Load Balancer Integration

CoSky provides a custom BinaryWeightRandomLoadBalancer (CoSkyDiscoveryAutoConfiguration.kt:106) that respects per-instance weights. It uses a binary-search algorithm over a cumulative weight array for O(log n) instance selection. The load balancer extends AbstractLoadBalancer and rebuilds its chooser whenever InstanceEventListenerContainer reports a change in the instance list.

Class Diagram

mermaid
classDiagram
    class CoSkyDiscoveryProperties {
        +enabled: Boolean = true
        +order: Int = 0
        +timeout: Duration = 2s
    }

    class CoSkyRegistryProperties {
        +serviceId: String
        +schema: String = "http"
        +host: String
        +port: Int = 0
        +weight: Int = 1
        +isEphemeral: Boolean = true
        +ttl: Duration = 60s
        +timeout: Duration = 2s
        +initialStatus: String = "UP"
    }

    class CoSkyDiscoveryAutoConfiguration {
        +redisServiceDiscovery() RedisServiceDiscovery
        +redisServiceTopology() RedisServiceTopology
        +consistencyRedisServiceDiscovery()
        +coSkyLoadBalancer()
    }

    class CoSkyDiscoveryClient {
        -serviceDiscovery: ServiceDiscovery
        -coSkyDiscoveryProperties: CoSkyDiscoveryProperties
        +getInstances(serviceId) List~ServiceInstance~
        +getServices() List~String~
    }

    class CoSkyReactiveDiscoveryClient {
        -serviceDiscovery: ServiceDiscovery
        +getInstances(serviceId) Flux~ServiceInstance~
        +getServices() Flux~String~
    }

    class CoSkyServiceInstance {
        +delegate: ServiceInstance
        +getInstanceId() String
        +getServiceId() String
        +getHost() String
        +getPort() Int
    }

    class CoSkyServiceRegistry {
        -serviceRegistry: ServiceRegistry
        -renewInstanceService: RenewInstanceService
        +register(registration)
        +deregister(registration)
        +setStatus(registration, status)
        +close()
    }

    class CoSkyRegistration {
        +serviceId: String
        +scheme: String
        +host: String
        +port: Int
        +weight: Int
        +isEphemeral: Boolean
        +asServiceInstance() ServiceInstance
    }

    class CoSkyAutoServiceRegistration {
        +register()
        +getRegistration() CoSkyRegistration
    }

    class CoSkyAutoServiceRegistrationOfNoneWeb {
        +onApplicationEvent(event)
        +destroy()
    }

    CoSkyDiscoveryAutoConfiguration --> CoSkyDiscoveryProperties : configures
    CoSkyDiscoveryClient --> CoSkyDiscoveryProperties : reads
    CoSkyDiscoveryClient --> CoSkyServiceInstance : wraps
    CoSkyReactiveDiscoveryClient --> CoSkyServiceInstance : wraps
    CoSkyServiceRegistry --> CoSkyRegistration : accepts
    CoSkyAutoServiceRegistration --> CoSkyServiceRegistry : delegates
    CoSkyAutoServiceRegistration --> CoSkyRegistration : provides
    CoSkyAutoServiceRegistrationOfNoneWeb --> CoSkyServiceRegistry : delegates

    style CoSkyDiscoveryProperties fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style CoSkyRegistryProperties fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style CoSkyDiscoveryAutoConfiguration fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style CoSkyDiscoveryClient fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style CoSkyReactiveDiscoveryClient fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style CoSkyServiceInstance fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style CoSkyServiceRegistry fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style CoSkyRegistration fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style CoSkyAutoServiceRegistration fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style CoSkyAutoServiceRegistrationOfNoneWeb fill:#2d333b,stroke:#6d5dfc,color:#e6edf3

Full YAML Configuration Example

yaml
spring:
  application:
    name: order-service
  cloud:
    cosky:
      namespace: production
      discovery:
        enabled: true
        order: 0
        timeout: 2s
        registry:
          service-id: order-service        # defaults to spring.application.name
          schema: http
          host: ""                          # auto-detected via InetUtils
          port: 0                           # auto-detected for web apps
          weight: 1
          is-ephemeral: true
          ttl: 60s
          timeout: 2s
          initial-status: UP
          metadata:
            version: "2.0.0"
            region: "us-east-1"
    service-registry:
      auto-registration:
        enabled: true

With this configuration, the order-service will:

  1. Register itself into Redis under the production namespace on startup.
  2. Start a heartbeat renewal to keep the instance alive (TTL = 60s).
  3. Be discoverable by other services through the blocking or reactive discovery client.
  4. Support weighted load balancing with weight = 1.

References

Released under the Apache License 2.0.