All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.scalecube.services.discovery.ScalecubeServiceDiscovery Maven / Gradle / Ivy

package io.scalecube.services.discovery;

import io.scalecube.cluster.Cluster;
import io.scalecube.cluster.ClusterConfig;
import io.scalecube.cluster.Member;
import io.scalecube.cluster.membership.MembershipEvent;
import io.scalecube.services.ServiceEndpoint;
import io.scalecube.services.ServiceGroup;
import io.scalecube.services.discovery.api.ServiceDiscovery;
import io.scalecube.services.discovery.api.ServiceDiscoveryEvent;
import io.scalecube.services.discovery.api.ServiceGroupDiscoveryEvent;
import io.scalecube.services.transport.api.Address;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.UnaryOperator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.FluxSink;
import reactor.core.publisher.Mono;

public class ScalecubeServiceDiscovery implements ServiceDiscovery {

  private static final Logger LOGGER = LoggerFactory.getLogger(ScalecubeServiceDiscovery.class);

  private final ServiceEndpoint serviceEndpoint;
  private final ClusterConfig clusterConfig;

  private Cluster cluster;

  private Map> groups = new HashMap<>();

  /**
   * Constructor.
   *
   * @param serviceEndpoint service endpoiintg
   * @param clusterConfig slcaluecibe cluster config
   */
  public ScalecubeServiceDiscovery(ServiceEndpoint serviceEndpoint, ClusterConfig clusterConfig) {
    this.serviceEndpoint = serviceEndpoint;
    this.clusterConfig = clusterConfig;
    // Add myself to the group if 'groupness' is defined
    ServiceGroup serviceGroup = serviceEndpoint.serviceGroup();
    if (serviceGroup != null) {
      addToGroup(serviceGroup, serviceEndpoint);
    }
  }

  /**
   * Constructror with default {@code ClusterConfig.defaultLanConfig}.
   *
   * @param endpoint service endpoiint
   */
  public ScalecubeServiceDiscovery(ServiceEndpoint endpoint) {
    this(endpoint, ClusterConfig.defaultLanConfig());
  }

  private ScalecubeServiceDiscovery(ScalecubeServiceDiscovery that, ClusterConfig clusterConfig) {
    this(that.serviceEndpoint, clusterConfig);
  }

  private ClusterConfig.Builder copyFrom(ClusterConfig config) {
    return ClusterConfig.builder()
        .seedMembers(config.getSeedMembers())
        .metadataTimeout(config.getMetadataTimeout())
        .metadata(config.getMetadata())
        .memberHost(config.getMemberHost())
        .memberPort(config.getMemberPort())
        .gossipFanout(config.getGossipFanout())
        .gossipInterval(config.getGossipInterval())
        .gossipRepeatMult(config.getGossipRepeatMult())
        .pingInterval(config.getPingInterval())
        .pingReqMembers(config.getPingReqMembers())
        .pingTimeout(config.getPingTimeout())
        .suspicionMult(config.getSuspicionMult())
        .syncGroup(config.getSyncGroup())
        .syncInterval(config.getSyncInterval())
        .syncTimeout(config.getSyncTimeout())
        .transportConfig(config.getTransportConfig());
  }

  public ScalecubeServiceDiscovery options(UnaryOperator opts) {
    return new ScalecubeServiceDiscovery(this, opts.apply(copyFrom(clusterConfig)).build());
  }

  @Override
  public Address address() {
    return Address.create(cluster.address().host(), cluster.address().port());
  }

  @Override
  public ServiceEndpoint serviceEndpoint() {
    return serviceEndpoint;
  }

  /**
   * Starts scalecube service discoevery. Joins a cluster with local services as metadata.
   *
   * @return mono result
   */
  @Override
  public Mono start() {
    return Mono.defer(
        () -> {
          Map metadata =
              serviceEndpoint != null
                  ? Collections.singletonMap(
                      serviceEndpoint.id(), ClusterMetadataCodec.encodeMetadata(serviceEndpoint))
                  : Collections.emptyMap();

          ClusterConfig newClusterConfig = //
              copyFrom(clusterConfig).addMetadata(metadata).build();

          ScalecubeServiceDiscovery serviceDiscovery =
              new ScalecubeServiceDiscovery(this, newClusterConfig);

          return Cluster.join(newClusterConfig)
              .doOnSuccess(cluster -> serviceDiscovery.cluster = cluster)
              .thenReturn(serviceDiscovery);
        });
  }

  @Override
  public Flux listenDiscovery() {
    return cluster
        .listenMembership()
        .flatMap(event -> Flux.create(sink -> onMembershipEvent(event, sink)));
  }

  @Override
  public Flux listenGroupDiscovery() {
    return listenDiscovery().flatMap(event -> Flux.create(sink -> onDiscoveryEvent(event, sink)));
  }

  @Override
  public Mono shutdown() {
    return Mono.defer(
        () -> Optional.ofNullable(cluster).map(Cluster::shutdown).orElse(Mono.empty()));
  }

  private void onMembershipEvent(
      MembershipEvent membershipEvent, FluxSink sink) {
    final Member member = membershipEvent.member();

    Map metadata = null;
    if (membershipEvent.isAdded()) {
      metadata = membershipEvent.newMetadata();
      LOGGER.info("Service endpoint added, since member {} has joined the cluster", member);
    }
    if (membershipEvent.isRemoved()) {
      metadata = membershipEvent.oldMetadata();
      LOGGER.info("Service endpoint removed, since member {} have left the cluster", member);
    }

    Optional.ofNullable(metadata).orElse(Collections.emptyMap()).values().stream()
        .map(ClusterMetadataCodec::decodeMetadata)
        .filter(Objects::nonNull)
        .forEach(
            serviceEndpoint -> {
              ServiceDiscoveryEvent discoveryEvent = null;

              if (membershipEvent.isAdded()) {
                discoveryEvent = ServiceDiscoveryEvent.newEndpointAdded(serviceEndpoint);
              }
              if (membershipEvent.isRemoved()) {
                discoveryEvent = ServiceDiscoveryEvent.newEndpointRemoved(serviceEndpoint);
              }

              if (discoveryEvent != null) {
                sink.next(discoveryEvent);
              }
            });
  }

  private void onDiscoveryEvent(
      ServiceDiscoveryEvent discoveryEvent, FluxSink sink) {

    ServiceEndpoint serviceEndpoint = discoveryEvent.serviceEndpoint();
    ServiceGroup serviceGroup = serviceEndpoint.serviceGroup();
    if (serviceGroup == null) {
      LOGGER.trace(
          "Discovered service endpoint {}, but not registering it (serviceGroup is null)",
          serviceEndpoint.id());
      return;
    }

    ServiceGroupDiscoveryEvent groupDiscoveryEvent = null;
    String groupId = serviceGroup.id();

    if (discoveryEvent.isEndpointAdded()) {
      if (!addToGroup(serviceGroup, serviceEndpoint)) {
        LOGGER.warn(
            "Failed to add service endpoint {} to group {}, group is full aready",
            serviceEndpoint.id(),
            groupId);
        return;
      }

      Collection endpoints = getEndpointsFromGroup(serviceGroup);

      sink.next(
          ServiceGroupDiscoveryEvent.newEndpointAddedToGroup(groupId, serviceEndpoint, endpoints));

      LOGGER.trace(
          "Added service endpoint {} to group {} (size now {})",
          serviceEndpoint.id(),
          groupId,
          endpoints.size());

      if (endpoints.size() == serviceGroup.size()) {
        LOGGER.info("Service group {} added to the cluster", serviceGroup);
        groupDiscoveryEvent = ServiceGroupDiscoveryEvent.newGroupAdded(groupId, endpoints);
      }
    }
    if (discoveryEvent.isEndpointRemoved()) {
      if (!removeFromGroup(serviceGroup, serviceEndpoint)) {
        LOGGER.warn(
            "Failed to remove service endpoint {} from group {}, "
                + "there were no such group or service endpoint was never registered in group",
            serviceEndpoint.id(),
            groupId);
        return;
      }

      Collection endpoints = getEndpointsFromGroup(serviceGroup);

      sink.next(
          ServiceGroupDiscoveryEvent.newEndpointRemovedFromGroup(
              groupId, serviceEndpoint, endpoints));

      LOGGER.trace(
          "Removed service endpoint {} from group {} (size now {})",
          serviceEndpoint.id(),
          groupId,
          endpoints.size());

      if (endpoints.isEmpty()) {
        LOGGER.info("Service group {} removed from the cluster", serviceGroup);
        groupDiscoveryEvent = ServiceGroupDiscoveryEvent.newGroupRemoved(groupId);
      }
    }

    if (groupDiscoveryEvent != null) {
      sink.next(groupDiscoveryEvent);
    }
  }

  public Collection getEndpointsFromGroup(ServiceGroup group) {
    return groups.getOrDefault(group, Collections.emptyList());
  }

  private boolean addToGroup(ServiceGroup group, ServiceEndpoint endpoint) {
    Collection endpoints =
        groups.computeIfAbsent(group, group1 -> new ArrayList<>());
    // check an actual group size is it still ok to add
    return endpoints.size() < group.size() && endpoints.add(endpoint);
  }

  private boolean removeFromGroup(ServiceGroup group, ServiceEndpoint endpoint) {
    if (!groups.containsKey(group)) {
      return false;
    }
    Collection endpoints = getEndpointsFromGroup(group);
    boolean removed = endpoints.removeIf(input -> input.id().equals(endpoint.id()));
    if (removed && endpoints.isEmpty()) {
      groups.remove(group); // cleanup
    }
    return removed;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy