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

com.netflix.eureka2.client.service.InterestChannelImpl Maven / Gradle / Ivy

package com.netflix.eureka2.client.service;

import com.netflix.eureka2.client.registry.EurekaClientRegistry;
import com.netflix.eureka2.client.transport.TransportClient;
import com.netflix.eureka2.interests.ChangeNotification;
import com.netflix.eureka2.interests.Interest;
import com.netflix.eureka2.interests.ModifyNotification;
import com.netflix.eureka2.interests.MultipleInterests;
import com.netflix.eureka2.protocol.discovery.AddInstance;
import com.netflix.eureka2.protocol.discovery.DeleteInstance;
import com.netflix.eureka2.protocol.discovery.InterestRegistration;
import com.netflix.eureka2.protocol.discovery.InterestSetNotification;
import com.netflix.eureka2.protocol.discovery.UpdateInstanceInfo;
import com.netflix.eureka2.registry.Delta;
import com.netflix.eureka2.registry.InstanceInfo;
import com.netflix.eureka2.service.InterestChannel;
import com.netflix.eureka2.transport.MessageConnection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Observable;
import rx.Subscriber;
import rx.functions.Action0;
import rx.functions.Func1;
import rx.observers.SafeSubscriber;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * An implementation of {@link InterestChannel}. It is mandatory that all operations
 * on the channel are serialized, by the external client. This class is not thread safe and all operations on it
 * shall be executed by the same thread.
 *
 * Use {@link InterestChannelInvoker} for serializing operations on this channel.
 *
 * @author Nitesh Kant
 */
/*pkg-private: Used by EurekaClientService only*/class InterestChannelImpl
        extends AbstractChannel implements ClientInterestChannel {

    private static final Logger logger = LoggerFactory.getLogger(InterestChannelImpl.class);

    private static final IllegalStateException INTEREST_NOT_REGISTERED_EXCEPTION =
            new IllegalStateException("No interest is registered on this channel.");

    /**
     * Since we assume single threaded access to this channel, no need for concurrency control
     */
    protected MultipleInterests channelInterest;
    protected Observable> channelInterestStream;

    protected Subscriber> channelInterestSubscriber;

    protected enum STATES {Idle, Open, Closed}

    private final InterestChannelMetrics metrics;

    protected EurekaClientRegistry registry;

    /**
     * A local copy of instances received by this channel from the server. This is used for:
     *
     * 
  • Updates on the wire: Since we only get the delta on the wire, we use this map to get the last seen {@link InstanceInfo} and apply the delta on it to get the new {@link InstanceInfo}
  • Deletes on the wire: Since we only get the identifier for the instance deleted, we use this map to get the last seen {@link InstanceInfo}
* *

Thread safety

* * Since this channel directly leverages the underlying {@link MessageConnection} and our underlying stack guarantees * that there are not concurrent updates sent to the input reader, we can safely assume that this code is single * threaded. */ private final Map idVsInstance = new HashMap<>(); InterestChannelImpl(final EurekaClientRegistry registry, TransportClient client, InterestChannelMetrics metrics) { super(STATES.Idle, client); this.registry = registry; this.metrics = metrics; metrics.incrementStateCounter(STATES.Idle); channelInterest = new MultipleInterests<>(); // blank channelInterest to start with channelInterestSubscriber = new ChannelInterestSubscriber(registry); channelInterestStream = createInterestStream(); } // channel contract means this will be invoked in serial. @Override public Observable change(final Interest newInterest) { Observable serverRequest = connect() // Connect is idempotent and does not connect on every call. .switchMap(new Func1>() { @Override public Observable call(MessageConnection serverConnection) { return serverConnection.submitWithAck(new InterestRegistration(newInterest)) .doOnCompleted(new UpdateLocalInterest(newInterest)); } }); return Observable.create(new Observable.OnSubscribe() { @Override public void call(Subscriber subscriber) { if (STATES.Closed == state.get()) { subscriber.onError(CHANNEL_CLOSED_EXCEPTION); } else if (moveToState(STATES.Idle, STATES.Open)) { logger.debug("First time registration"); channelInterestStream.subscribe(channelInterestSubscriber); } else { logger.debug("Channel changes"); } subscriber.onCompleted(); } }).concatWith(serverRequest); } @Override public Observable appendInterest(Interest toAppend) { if (null == channelInterest) { return Observable.error(INTEREST_NOT_REGISTERED_EXCEPTION); } return change(channelInterest.copyAndAppend(toAppend)); } @Override public Observable removeInterest(Interest toRemove) { if (null == channelInterest) { return Observable.error(INTEREST_NOT_REGISTERED_EXCEPTION); } return change(channelInterest.copyAndRemove(toRemove)); } @Override protected void _close() { if (state.get() != STATES.Closed) { moveToState(state.get(), STATES.Closed); idVsInstance.clear(); super._close(); } } protected Observable> createInterestStream() { return connect().switchMap(new Func1>>() { @Override public Observable> call(final MessageConnection connection) { return connection.incoming().filter(new Func1() { @Override public Boolean call(Object message) { boolean isKnown = message instanceof InterestSetNotification; if (!isKnown) { logger.warn("Unrecognized discovery protocol message of type " + message.getClass()); } return isKnown; } }).map(new Func1>() { @Override public ChangeNotification call(Object message) { ChangeNotification changeNotification; InterestSetNotification notification = (InterestSetNotification) message; if (notification instanceof AddInstance) { changeNotification = addMessageToChangeNotification((AddInstance) notification); } else if (notification instanceof UpdateInstanceInfo) { changeNotification = updateMessageToChangeNotification((UpdateInstanceInfo) notification); } else if (notification instanceof DeleteInstance) { changeNotification = deleteMessageToChangeNotification((DeleteInstance) notification); } else { throw new IllegalArgumentException("Unknown message received on the interest channel. Type: " + message.getClass().getName()); } sendAckOnConnection(connection); return changeNotification; } }).filter(new Func1, Boolean>() { @Override public Boolean call(ChangeNotification notification) { return null != notification; } }); } }); } /** * For an AddInstance msg, * - if it does not exist in cache, this is an Add Notification to the store * - if it exist in cache but had a different version, this is a modify notification to the store. * For simplicity we treat this as an add if the new msg have a GREATER version number, * and ignore it otherwise */ private ChangeNotification addMessageToChangeNotification(AddInstance msg) { ChangeNotification notification = null; InstanceInfo incoming = msg.getInstanceInfo(); InstanceInfo cached = idVsInstance.get(incoming.getId()); if (cached == null) { idVsInstance.put(incoming.getId(), incoming); notification = new ChangeNotification<>(ChangeNotification.Kind.Add, incoming); } else if (incoming.getVersion() <= cached.getVersion()) { logger.debug("Skipping <= version of the instanceInfo. Cached: {}, Incoming: {}", cached, incoming); } else { logger.debug("Received newer version of an existing instanceInfo as Add"); idVsInstance.put(incoming.getId(), incoming); notification = new ChangeNotification<>(ChangeNotification.Kind.Add, incoming); } return notification; } /** * For an UpdateInstanceInfo msg, * - if it does not exist in cache, we ignore this message as we do not have enough information to restore it * - if it exist in cache but is different, this is a modify notification to the store. * We only apply changes to cached instance if it has a version number GREATER THAN the cached * version number. */ @SuppressWarnings("unchecked") private ChangeNotification updateMessageToChangeNotification(UpdateInstanceInfo msg) { ModifyNotification notification = null; Delta delta = msg.getDelta(); InstanceInfo cached = idVsInstance.get(delta.getId()); if (cached == null) { if (logger.isWarnEnabled()) { logger.warn("Update notification received for non-existent instance id " + delta.getId()); } } else if (delta.getVersion() <= cached.getVersion()) { logger.debug("Skipping <= version of the delta. Cached: {}, Delta: {}", cached, delta); } else { InstanceInfo updatedInfo = cached.applyDelta(delta); idVsInstance.put(updatedInfo.getId(), updatedInfo); notification = new ModifyNotification(updatedInfo, Collections.singleton(delta)); } return notification; } /** * For a DeleteInstance msg, * - if it does not exist in cache, we ignore this message as it is irrelevant * - else just remove it. We handle conflicts with delete and add on the registration side */ private ChangeNotification deleteMessageToChangeNotification(DeleteInstance msg) { ChangeNotification notification = null; String instanceId = msg.getInstanceId(); InstanceInfo removedInstance = idVsInstance.remove(instanceId); if (removedInstance != null) { notification = new ChangeNotification<>(ChangeNotification.Kind.Delete, removedInstance); } else if (logger.isWarnEnabled()) { logger.warn("Delete notification received for non-existent instance id:" + instanceId); } return notification; } protected boolean moveToState(STATES from, STATES to) { if (state.compareAndSet(from, to)) { metrics.decrementStateCounter(from); metrics.incrementStateCounter(to); return true; } return false; } protected static class ChannelInterestSubscriber extends SafeSubscriber> { public ChannelInterestSubscriber(final EurekaClientRegistry registry) { super(new Subscriber>() { @Override public void onCompleted() { // TODO: handle logger.debug("Channel interest completed"); } @Override public void onError(Throwable e) { // TODO: handle/do failover/fallback logger.error("Channel interest throw error", e); } @Override public void onNext(ChangeNotification notification) { switch (notification.getKind()) { // these are in-mem blocking ops case Add: registry.register(notification.getData()); break; case Modify: ModifyNotification modifyNotification = (ModifyNotification) notification; registry.update(modifyNotification.getData(), modifyNotification.getDelta()); break; case Delete: registry.unregister(notification.getData()); break; default: logger.error("Unrecognized notification kind"); } } }); } } private class UpdateLocalInterest implements Action0 { private final Interest interest; public UpdateLocalInterest(Interest interest) { this.interest = interest; } @Override public void call() { channelInterest = new MultipleInterests<>(interest); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy