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

com.netflix.eureka2.server.registry.EurekaServerRegistryImpl Maven / Gradle / Ivy

There is a newer version: 2.0.0-DP4
Show newest version
/*
 * Copyright 2014 Netflix, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.netflix.eureka2.server.registry;

import com.netflix.eureka2.datastore.NotificationsSubject;
import com.netflix.eureka2.interests.ChangeNotification;
import com.netflix.eureka2.interests.IndexRegistry;
import com.netflix.eureka2.interests.IndexRegistryImpl;
import com.netflix.eureka2.interests.InstanceInfoInitStateHolder;
import com.netflix.eureka2.interests.Interest;
import com.netflix.eureka2.interests.MultipleInterests;
import com.netflix.eureka2.registry.Delta;
import com.netflix.eureka2.registry.InstanceInfo;
import com.netflix.eureka2.server.registry.NotifyingInstanceInfoHolder.NotificationTaskInvoker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Observable;
import rx.Subscriber;
import rx.functions.Action1;
import rx.functions.Func1;
import rx.subjects.ReplaySubject;

import javax.inject.Inject;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;

/**
 * TODO: fix race in adding/removing from store and sending notification to notificationSubject
 * TODO: threadpool for async add/put to internalStore?
 * @author David Liu
 */
public class EurekaServerRegistryImpl implements EurekaServerRegistry {

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

    /**
     * TODO: define a better contract for base implementation and decorators
     */
    protected final ConcurrentHashMap> internalStore;
    private final NotificationsSubject notificationSubject;  // subject for all changes in the registry
    private final IndexRegistry indexRegistry;
    private final EurekaServerRegistryMetrics metrics;
    private final NotificationTaskInvoker invoker = new NotificationTaskInvoker();

    @Inject
    public EurekaServerRegistryImpl(EurekaServerRegistryMetrics metrics) {
        this.metrics = metrics;
        this.metrics.setRegistrySizeMonitor(new Callable() {
            @Override
            public Integer call() throws Exception {
                return internalStore.size();
            }
        });

        internalStore = new ConcurrentHashMap<>();
        indexRegistry = new IndexRegistryImpl<>();
        notificationSubject = NotificationsSubject.create();
    }

    // -------------------------------------------------
    // Registry manipulation
    // -------------------------------------------------

    @Override
    public Observable register(final InstanceInfo instanceInfo) {
        return register(instanceInfo, Source.localSource());
    }

    @Override
    public Observable register(InstanceInfo instanceInfo, Source source) {
        MultiSourcedDataHolder newHolder = new NotifyingInstanceInfoHolder(notificationSubject, invoker, instanceInfo.getId());
        MultiSourcedDataHolder currentHolder = internalStore.putIfAbsent(instanceInfo.getId(), newHolder);

        Observable toReturn;
        if (currentHolder != null) {
            toReturn = currentHolder.update(source, instanceInfo);
        } else {
            toReturn = newHolder.update(source, instanceInfo);
            metrics.incrementRegistrationCounter(source.getOrigin());  // this is a true new registration
        }
        return subscribeToUpdateResult(toReturn);
    }

    @Override
    public Observable unregister(final InstanceInfo instanceInfo) {
        return unregister(instanceInfo, Source.localSource());
    }

    @Override
    public Observable unregister(final InstanceInfo instanceInfo, final Source source) {
        final MultiSourcedDataHolder currentHolder = internalStore.get(instanceInfo.getId());
        if (currentHolder == null) {
            return Observable.just(Status.RemoveExpired);
        }

        Observable result = currentHolder.remove(source, instanceInfo).doOnNext(new Action1() {
            @Override
            public void call(Status status) {
                if (status != Status.RemoveExpired) {
                    metrics.incrementUnregistrationCounter(source.getOrigin());
                }
            }
        });
        return subscribeToUpdateResult(result);
    }

    @Override
    public Observable update(InstanceInfo updatedInfo, Set> deltas) {
        return update(updatedInfo, deltas, Source.localSource());
    }

    @Override
    public Observable update(InstanceInfo updatedInfo, Set> deltas, Source source) {
        MultiSourcedDataHolder newHolder = new NotifyingInstanceInfoHolder(notificationSubject, invoker, updatedInfo.getId());
        MultiSourcedDataHolder currentHolder = internalStore.putIfAbsent(updatedInfo.getId(), newHolder);

        Observable toReturn;
        if (currentHolder != null) {
            toReturn = currentHolder.update(source, updatedInfo);
        } else { // this is an add
            toReturn = newHolder.update(source, updatedInfo);
        }

        metrics.incrementUpdateCounter();
        return subscribeToUpdateResult(toReturn);
    }

    /**
     * TODO: do we have to eagerly subscribe? This code is inefficient.
     */
    private static Observable subscribeToUpdateResult(Observable status) {
        final ReplaySubject result = ReplaySubject.create();
        status.subscribe(new Subscriber() {
            @Override
            public void onCompleted() {
                result.onCompleted();
            }

            @Override
            public void onError(Throwable e) {
                logger.error("Registry update failure", e);
                result.onError(e);
            }

            @Override
            public void onNext(Status status) {
                logger.debug("Registray updated completed with status {}", status);
                result.onNext(status);
            }
        });
        return result;
    }

    @Override
    public int size() {
        return internalStore.size();
    }

    /**
     * Return a snapshot of the current registry for the passed {@code interest} as a stream of {@link InstanceInfo}s.
     * This view of the snapshot is eventual consistent and any instances that successfully registers while the
     * stream is being processed might be added to the stream. Note that this stream terminates as opposed to
     * forInterest.
     *
     * @return A stream of {@link InstanceInfo}s for the passed {@code interest}. The stream represent a snapshot
     * of the registry for the interest.
     */
    @Override
    public Observable forSnapshot(final Interest interest) {
        return Observable.from(internalStore.values())
                .map(new Func1, InstanceInfo>() {
                    @Override
                    public InstanceInfo call(MultiSourcedDataHolder holder) {
                        ChangeNotification notification = holder.getChangeNotification();
                        return notification == null ? null : notification.getData();
                    }
                })
                .filter(new Func1() {
                    @Override
                    public Boolean call(InstanceInfo instanceInfo) {
                        return instanceInfo != null && interest.matches(instanceInfo);
                    }
                });
    }

    /**
     * Return an observable of all matching InstanceInfo for the current registry snapshot,
     * as {@link ChangeNotification}s
     * @param interest
     * @return an observable of all matching InstanceInfo for the current registry snapshot,
     * as {@link ChangeNotification}s
     */
    @Override
    public Observable> forInterest(Interest interest) {
        try {
            // TODO: this method can be run concurrently from different channels, unless we run everything on single server event loop.
            notificationSubject.pause(); // Pause notifications till we get a snapshot of current registry (registry.values())
            if (interest instanceof MultipleInterests) {
                return indexRegistry.forCompositeInterest((MultipleInterests) interest, this);
            } else {
                return indexRegistry.forInterest(interest, notificationSubject,
                        new InstanceInfoInitStateHolder(getSnapshotForInterest(interest)));
            }
        } finally {
            notificationSubject.resume();
        }
    }

    @Override
    public Observable> forInterest(Interest interest, final Source source) {
        return forInterest(interest).filter(new Func1, Boolean>() {
            @Override
            public Boolean call(ChangeNotification changeNotification) {
                MultiSourcedDataHolder holder = internalStore.get(changeNotification.getData().getId());
                return holder != null && source.equals(holder.getSource());
            }
        });
    }

    @Override
    public Observable shutdown() {
        return indexRegistry.shutdown();
    }

    private Iterator> getSnapshotForInterest(final Interest interest) {
        final Collection> eurekaHolders = internalStore.values();
        return new FilteredIterator(interest, eurekaHolders.iterator());
    }

    private static class FilteredIterator implements Iterator> {

        private final Interest interest;
        private final Iterator> delegate;
        private ChangeNotification next;

        private FilteredIterator(Interest interest, Iterator> delegate) {
            this.interest = interest;
            this.delegate = delegate;
        }

        @Override
        public boolean hasNext() {
            if (null != next) {
                return true;
            }

            while (delegate.hasNext()) { // Iterate till we get a matching item.
                MultiSourcedDataHolder possibleNext = delegate.next();
                ChangeNotification notification = possibleNext.getChangeNotification();
                if (notification != null && interest.matches(notification.getData())) {
                    next = notification;
                    return true;
                }
            }
            return false;
        }

        @Override
        public ChangeNotification next() {
            if (hasNext()) {
                ChangeNotification next = this.next;
                this.next = null; // Forces hasNext() to peek the next item.
                return next;
            }
            throw new NoSuchElementException("No more notifications.");
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException("Remove not supported for this iterator.");
        }
    }

    // pretty print for debugging
    @Override
    public String toString() {
        return prettyString();
    }

    private String prettyString() {
        StringBuilder sb = new StringBuilder("EurekaRegistryImpl\n");
        for (Map.Entry> entry : internalStore.entrySet()) {
            sb.append(entry).append("\n");
        }
        sb.append(indexRegistry.toString());

        return sb.toString();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy