# How CoSky Handles Service Registration and Deregistration Lifecycle

> Discover how CoSky manages service registration and deregistration lifecycle using Redis TTLs, heartbeats, and event subscriptions for robust service discovery.

- Repository: [Ahoo Wang/cosky](https://github.com/ahoo-wang/cosky)
- Tags: deep-dive
- Published: 2026-02-23

---

**CoSky implements a Redis-backed service registry where instances register with a TTL, emit heartbeat messages to renew leases, and publish explicit deregistration events, while discovery nodes subscribe to a heartbeat channel to maintain a consistent local cache of available services.**

Managing the lifecycle of microservice instances requires a robust mechanism for registration, health checking, and cleanup. In the `ahoo-wang/cosky` repository, the service registration and deregistration lifecycle is built on a lightweight Redis architecture that combines time-to-live (TTL) keys with pub/sub messaging to ensure real-time consistency across the cluster.

## Service Registration Architecture in CoSky

### The Core Registration Flow

In [`cosky-discovery/src/main/java/me/ahoo/cosky/discovery/redis/RedisServiceRegistry.java`](https://github.com/ahoo-wang/cosky/blob/main/cosky-discovery/src/main/java/me/ahoo/cosky/discovery/redis/RedisServiceRegistry.java), the `register()` method persists service instance metadata as JSON to Redis and immediately publishes the instance key to the `HEARTBEAT_CHANNEL`.

The implementation follows this sequence:

1. Constructs a Redis key using the pattern `cosky:service:{serviceId}:{instanceId}`.
2. Serializes the `ServiceInstance` to JSON format.
3. Stores the value with a configurable TTL defined by `ServiceRegistryProperties#getTtl()`.
4. Publishes the key to the `cosky:service:heartbeat` channel via `redisTemplate.convertAndSend()`.

This pub/sub notification ensures that discovery clients receive immediate updates when new services join the cluster, eliminating polling delays.

### Interface Contract

The `ServiceRegistry` interface in [`cosky-discovery/src/main/java/me/ahoo/cosky/discovery/ServiceRegistry.java`](https://github.com/ahoo-wang/cosky/blob/main/cosky-discovery/src/main/java/me/ahoo/cosky/discovery/ServiceRegistry.java) defines the reactive contract with three primary operations:

- `Mono<ServiceInstance> register(ServiceInstance instance)`
- `Mono<Void> deregister(String serviceId, String instanceId)`
- `Mono<Void> renew(String serviceId, String instanceId)`

## Heartbeat Mechanism and Lease Renewal

### Renewing Service Leases

To prevent TTL expiration while a service remains healthy, the `renew()` method in `RedisServiceRegistry` refreshes the key's expiration time using `redisTemplate.expire(key, properties.getTtl())`. This lightweight operation extends the instance's lease without rewriting the entire value.

### Scheduled Heartbeat Emission

While the registry includes a `@Scheduled` method `sendHeartbeat` to periodically publish heartbeat messages, the primary mechanism for keeping instances visible involves the initial registration publish and subsequent renewals. The heartbeat channel serves as the event bus for both new registrations and periodic keep-alives.

## Service Deregistration and Cache Consistency

### Explicit Deregistration

When a service shuts down gracefully, the `deregister()` method in `RedisServiceRegistry` performs two critical operations:

1. Deletes the instance key from Redis using `redisTemplate.delete(key)`.
2. Publishes a deregistration message with the prefix `deregister:` followed by the key to the heartbeat channel.

This dual action ensures that both the persistent store and all subscribed discovery clients remove the instance simultaneously.

### Discovery Side Reaction

The `RedisServiceDiscovery` class in [`cosky-discovery/src/main/java/me/ahoo/cosky/discovery/redis/RedisServiceDiscovery.java`](https://github.com/ahoo-wang/cosky/blob/main/cosky-discovery/src/main/java/me/ahoo/cosky/discovery/redis/RedisServiceDiscovery.java) implements `MessageListener` to subscribe to the heartbeat channel. In the `onMessage()` method, it distinguishes between heartbeat and deregistration events:

- **Heartbeat messages**: Update the local cache with fresh instance data and emit an `InstanceChangedEvent` of type `HEARTBEAT`.
- **Deregistration messages**: Remove the instance from the local cache using `localCache.remove(key)` and emit an `InstanceChangedEvent` of type `DEREGISTERED`.

This design maintains a strongly consistent local view of the service topology without requiring repeated full scans of Redis.

## Practical Implementation Examples

The following examples demonstrate how to interact with the CoSky service registration and deregistration lifecycle in a Spring Boot application.

Registering a service at startup:

```java
@Component
@RequiredArgsConstructor
public class ServiceRegistrationRunner implements ApplicationRunner {

    private final ServiceRegistry serviceRegistry;

    @Override
    public void run(ApplicationArguments args) {
        ServiceInstance instance = ServiceInstance.builder()
                .serviceId("payment-service")
                .instanceId(UUID.randomUUID().toString())
                .host("192.168.1.100")
                .port(8080)
                .metadata(Map.of("region", "us-east-1", "version", "2.1.0"))
                .build();

        serviceRegistry.register(instance)
                .doOnSuccess(i -> System.out.println("Registered: " + i))
                .subscribe();
    }
}

```

Deregistering gracefully on shutdown:

```java
@Component
@RequiredArgsConstructor
public class ServiceShutdownHook {

    private final ServiceRegistry serviceRegistry;
    private final ServiceInstance currentInstance; // Injected or constructed

    @PreDestroy
    public void destroy() {
        serviceRegistry.deregister(
                currentInstance.getServiceId(),
                currentInstance.getInstanceId()
        ).doOnSuccess(v -> System.out.println("Deregistered successfully"))
         .subscribe();
    }
}

```

Reacting to instance lifecycle events:

```java
@Component
public class InstanceChangeHandler {

    @EventListener
    public void handleInstanceChanged(InstanceChangedEvent event) {
        if (event.getType() == InstanceChangedEvent.Type.DEREGISTERED) {
            System.out.println("Service instance removed: " + event.getInstance());
        } else if (event.getType() == InstanceChangedEvent.Type.HEARTBEAT) {
            System.out.println("Heartbeat received from: " + event.getInstance());
        }
    }
}

```

## Summary

- CoSky stores service instances in Redis with configurable TTLs to ensure automatic cleanup when services fail or network partitions occur.
- The `RedisServiceRegistry` class manages the lifecycle through the `register()`, `deregister()`, and `renew()` methods, utilizing the `HEARTBEAT_CHANNEL` for event propagation.
- Service discovery nodes maintain a local cache via `RedisServiceDiscovery`, which subscribes to heartbeat messages and updates its view in real-time.
- Explicit deregistration triggers immediate cache eviction across all consumers through the pub/sub mechanism, while TTL expiration handles implicit cleanup for crashed instances.

## Frequently Asked Questions

### How does CoSky ensure high availability during service registration?

CoSky leverages Redis as a centralized, highly available data store with TTL-based lease management. If a registering service loses connectivity after registration, the TTL expiration automatically removes the stale entry, preventing failed instances from remaining in the registry indefinitely.

### What happens if a service crashes without calling deregister?

The Redis key associated with the service instance will expire after the TTL duration configured in `ServiceRegistryProperties` (defaulting to 30 seconds). Once expired, subsequent discovery queries will not return the instance, effectively handling the deregistration implicitly without requiring explicit cleanup.

### How does the heartbeat interval affect service discovery performance?

The heartbeat interval, configured via `cosky.service.registry.heartbeat-interval`, determines the frequency of cache refresh events across the cluster. Shorter intervals reduce the time to detect new or failed instances but increase Redis pub/sub traffic, while longer intervals trade off discovery latency for reduced network overhead.

### Can CoSky work with Redis Sentinel or Cluster for production deployments?

Yes, the implementation uses standard `StringRedisTemplate` operations, making it compatible with Redis Sentinel and Cluster configurations. The pub/sub mechanism for heartbeats and the TTL-based storage work transparently across these topologies, ensuring the service registration and deregistration lifecycle remains robust in distributed Redis environments.