io.airlift.discovery.store.DistributedStore Maven / Gradle / Ivy
/*
* Copyright 2010 Proofpoint, 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 io.airlift.discovery.store;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.collect.Iterables;
import io.airlift.units.Duration;
import org.joda.time.DateTime;
import org.weakref.jmx.Managed;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import java.util.Arrays;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Predicates.and;
import static com.google.common.base.Predicates.not;
import static io.airlift.concurrent.Threads.daemonThreadsNamed;
import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
/**
* A simple, eventually consistent, fully replicated, distributed key-value store.
*/
public class DistributedStore
{
private final String name;
private final LocalStore localStore;
private final RemoteStore remoteStore;
private final Supplier timeSupplier;
private final Duration tombstoneMaxAge;
private final Duration garbageCollectionInterval;
private final ScheduledExecutorService garbageCollector;
private final AtomicLong lastGcTimestamp = new AtomicLong();
@Inject
public DistributedStore(
String name,
LocalStore localStore,
RemoteStore remoteStore,
StoreConfig config,
Supplier timeSupplier)
{
this.name = checkNotNull(name, "name is null");
this.localStore = checkNotNull(localStore, "localStore is null");
this.remoteStore = checkNotNull(remoteStore, "remoteStore is null");
this.timeSupplier = checkNotNull(timeSupplier, "timeSupplier is null");
checkNotNull(config, "config is null");
tombstoneMaxAge = config.getTombstoneMaxAge();
garbageCollectionInterval = config.getGarbageCollectionInterval();
garbageCollector = newSingleThreadScheduledExecutor(daemonThreadsNamed("distributed-store-gc-" + name));
}
@PostConstruct
public void start()
{
garbageCollector.scheduleAtFixedRate(new Runnable()
{
@Override
public void run()
{
removeExpiredEntries();
}
}, 0, garbageCollectionInterval.toMillis(), TimeUnit.MILLISECONDS);
}
@Managed
public String getName()
{
return name;
}
@Managed
public long getLastGcTimestamp()
{
return lastGcTimestamp.get();
}
@Managed
public void removeExpiredEntries()
{
for (Entry entry : localStore.getAll()) {
if (isExpired(entry)) {
localStore.delete(entry.getKey(), entry.getVersion());
}
}
lastGcTimestamp.set(System.currentTimeMillis());
}
private boolean isExpired(Entry entry)
{
long ageInMs = timeSupplier.get().getMillis() - entry.getTimestamp();
return entry.getValue() == null && ageInMs > tombstoneMaxAge.toMillis() || // TODO: this is repeated in StoreResource
entry.getMaxAgeInMs() != null && ageInMs > entry.getMaxAgeInMs();
}
@PreDestroy
public void shutdown()
{
garbageCollector.shutdownNow();
}
public void put(byte[] key, byte[] value)
{
checkNotNull(key, "key is null");
checkNotNull(value, "value is null");
long now = timeSupplier.get().getMillis();
Entry entry = new Entry(key, value, new Version(now), now, null);
localStore.put(entry);
remoteStore.put(entry);
}
public void put(byte[] key, byte[] value, Duration maxAge)
{
checkNotNull(key, "key is null");
checkNotNull(value, "value is null");
checkNotNull(maxAge, "maxAge is null");
long now = timeSupplier.get().getMillis();
Entry entry = new Entry(key, value, new Version(now), now, maxAge.toMillis());
localStore.put(entry);
remoteStore.put(entry);
}
public byte[] get(byte[] key)
{
checkNotNull(key, "key is null");
Entry entry = localStore.get(key);
byte[] result = null;
if (entry != null && entry.getValue() != null && !isExpired(entry)) {
result = Arrays.copyOf(entry.getValue(), entry.getValue().length);
}
return result;
}
public void delete(byte[] key)
{
checkNotNull(key, "key is null");
long now = timeSupplier.get().getMillis();
Entry entry = new Entry(key, null, new Version(now), now, null);
localStore.put(entry);
remoteStore.put(entry);
}
public Iterable getAll()
{
return Iterables.filter(localStore.getAll(), and(not(expired()), not(tombstone())));
}
private Predicate super Entry> expired()
{
return new Predicate()
{
public boolean apply(Entry entry)
{
return isExpired(entry);
}
};
}
private static Predicate super Entry> tombstone()
{
return new Predicate()
{
public boolean apply(Entry entry)
{
return entry.getValue() == null;
}
};
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy