com.netflix.eureka2.server.registry.EurekaServerRegistryImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of eureka-server Show documentation
Show all versions of eureka-server Show documentation
eureka-server developed by Netflix
/*
* 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();
}
}