Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.netflix.archaius.api.PropertyRepository;
import com.netflix.evcache.EVCacheInMemoryCache.DataNotFoundException;
import com.netflix.evcache.EVCacheLatch.Policy;
import com.netflix.evcache.config.EVCacheTranscoderProperties;
import com.netflix.evcache.dto.KeyMapDto;
import com.netflix.evcache.event.EVCacheEvent;
import com.netflix.evcache.event.EVCacheEventListener;
Expand Down Expand Up @@ -76,6 +77,7 @@
public class EVCacheImpl implements EVCache, EVCacheImplMBean {

private static final Logger log = LoggerFactory.getLogger(EVCacheImpl.class);
private static final int COMPRESSION_THRESHOLD_BYTES = Integer.MAX_VALUE;

private final Clock clock;
private final String _appName;
Expand Down Expand Up @@ -164,9 +166,9 @@ public class EVCacheImpl implements EVCache, EVCacheImplMBean {
this.maxHashLength = propertyRepository.get(appName + ".max.hash.length", Integer.class).orElse(-1);
this.encoderBase = propertyRepository.get(appName + ".hash.encoder", String.class).orElse("base64");
this.autoHashKeys = propertyRepository.get(_appName + ".auto.hash.keys", Boolean.class).orElseGet("evcache.auto.hash.keys").orElse(false);
this.evcacheValueTranscoder = new EVCacheTranscoder();
evcacheValueTranscoder.setCompressionThreshold(Integer.MAX_VALUE);

final EVCacheTranscoderProperties evCacheTranscoderProperties = new EVCacheTranscoderProperties(_appName, propertyRepository);
this.evcacheValueTranscoder = new EVCacheTranscoder(evCacheTranscoderProperties);
evcacheValueTranscoder.setCompressionThreshold(COMPRESSION_THRESHOLD_BYTES);
// default max key length is 200, instead of using what is defined in MemcachedClientIF.MAX_KEY_LENGTH (250). This is to accommodate
// auto key prepend with appname for duet feature.
this.maxKeyLength = propertyRepository.get(_appName + ".max.key.length", Integer.class).orElseGet("evcache.max.key.length").orElse(200);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@

package com.netflix.evcache;

import com.netflix.evcache.config.EVCacheTranscoderProperties;
import com.netflix.evcache.metrics.EVCacheMetricsFactory;
import com.netflix.evcache.pool.ServerGroup;
import com.netflix.evcache.util.EVCacheConfig;
import com.netflix.spectator.api.BasicTag;
import com.netflix.spectator.api.Tag;
import com.netflix.spectator.api.Timer;
Expand Down Expand Up @@ -68,6 +70,8 @@ public class EVCacheSerializingTranscoder extends BaseSerializingTranscoder impl
private final TranscoderUtils tu = new TranscoderUtils(true);
private Timer timer;

protected final EVCacheTranscoderProperties properties;

/**
* Get a serializing transcoder with the default max data size.
*/
Expand All @@ -76,10 +80,23 @@ public EVCacheSerializingTranscoder() {
}

/**
* Get a serializing transcoder that specifies the max data size.
* Get a serializing transcoder that specifies the max data size. Builds a default
* {@link EVCacheTranscoderProperties} bundle from
* {@link EVCacheConfig#getInstance()} — subclasses/callers that want per-app
* resolution should use {@link #EVCacheSerializingTranscoder(EVCacheTranscoderProperties, int)}.
*/
public EVCacheSerializingTranscoder(int max) {
this(new EVCacheTranscoderProperties(null, EVCacheConfig.getInstance().getPropertyRepository()), max);
}

/**
* Get a serializing transcoder with the supplied transcoder-property bundle. The bundle is
* exposed to subclasses via {@link #properties} so downstream transcoders can consult the
* same three-level (per-app → global → static default) resolution chain.
*/
public EVCacheSerializingTranscoder(EVCacheTranscoderProperties properties, int max) {
super(max);
this.properties = properties;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,53 @@
package com.netflix.evcache;

import com.netflix.evcache.config.EVCacheTranscoderProperties;
import com.netflix.evcache.pool.EVCacheValue;
import com.netflix.evcache.pool.EVCacheValueSerde;
import com.netflix.evcache.util.EVCacheConfig;

import net.spy.memcached.CachedData;

import static com.netflix.evcache.config.EVCacheTranscoderProperties.DEFAULT_COMPRESSION_THRESHOLD_BYTES;
import static com.netflix.evcache.config.EVCacheTranscoderProperties.DEFAULT_MAX_DATA_SIZE_BYTES;
import static com.netflix.evcache.config.EVCacheTranscoderProperties.Key.COMPRESSION_THRESHOLD_BYTES;
import static com.netflix.evcache.config.EVCacheTranscoderProperties.Key.MAX_DATA_SIZE_BYTES;

public class EVCacheTranscoder extends EVCacheSerializingTranscoder {

/**
* @param properties the transcoder property bundle.
* {@link EVCacheTranscoderProperties.Key#MAX_DATA_SIZE_BYTES} and
* {@link EVCacheTranscoderProperties.Key#COMPRESSION_THRESHOLD_BYTES}
* are read from the bundle at construction and set as fields on the
* underlying {@link EVCacheSerializingTranscoder} for legacy reasons;
* new properties should be added to {@link EVCacheTranscoderProperties}
* rather than plumbed through further constructor arguments.
*/
public EVCacheTranscoder(EVCacheTranscoderProperties properties) {
this(properties.getProperty(MAX_DATA_SIZE_BYTES, Integer.class, DEFAULT_MAX_DATA_SIZE_BYTES).get(),
properties.getProperty(COMPRESSION_THRESHOLD_BYTES, Integer.class, DEFAULT_COMPRESSION_THRESHOLD_BYTES).get(),
properties
);
}

public EVCacheTranscoder() {
this(EVCacheConfig.getInstance().getPropertyRepository().get("default.evcache.max.data.size", Integer.class).orElse(20 * 1024 * 1024).get());
this(new EVCacheTranscoderProperties(null, EVCacheConfig.getInstance().getPropertyRepository()));
}

public EVCacheTranscoder(int max) {
this(max, EVCacheConfig.getInstance().getPropertyRepository().get("default.evcache.compression.threshold", Integer.class).orElse(120).get());
this(max, new EVCacheTranscoderProperties(null, EVCacheConfig.getInstance().getPropertyRepository()));
}

public EVCacheTranscoder(int max, int compressionThreshold) {
super(max);
this(max, compressionThreshold, new EVCacheTranscoderProperties(null, EVCacheConfig.getInstance().getPropertyRepository()));
}

private EVCacheTranscoder(int max, EVCacheTranscoderProperties properties) {
this(max, properties.getProperty(COMPRESSION_THRESHOLD_BYTES, Integer.class, DEFAULT_COMPRESSION_THRESHOLD_BYTES).get(), properties);
}

private EVCacheTranscoder(int max, int compressionThreshold, EVCacheTranscoderProperties properties) {
super(properties, max);
setCompressionThreshold(compressionThreshold);
}

Expand All @@ -35,4 +67,20 @@ public CachedData encode(Object o) {
return super.encode(o);
}

@Override
protected byte[] serialize(Object o) {
if (this.properties.isBinarySerializationEnabled() && o instanceof EVCacheValue) {
return EVCacheValueSerde.serialize((EVCacheValue) o);
}
return super.serialize(o);
}

@Override
protected Object deserialize(byte[] in) {
if (EVCacheValueSerde.isBinaryFormat(in)) {
return EVCacheValueSerde.deserialize(in);
}
return super.deserialize(in);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package com.netflix.evcache.config;

import com.netflix.archaius.api.Property;
import com.netflix.archaius.api.PropertyRepository;

import static com.netflix.evcache.config.EVCacheTranscoderProperties.Key.BINARY_SERIALIZATION_ENABLED;

/**
* Properties related to {@link com.netflix.evcache.EVCacheTranscoder}
* behavior.
*
* <p>Every property resolves at construction through:
*
* <ol>
* <li><b>Per-app override:</b> {@code <appName>.<appKeySuffix>}</li>
* <li><b>Global default:</b> {@code <globalKey>}</li>
* <li><b>Static default:</b> default value supplied as an argument</li>
* </ol>
*
* <p>
* Static properties should be cached as a field for fast access.
* Dynamic properties get be accessed {@link #getProperty(Key, Class, Object)}
*
*/
public final class EVCacheTranscoderProperties {

public static final boolean DEFAULT_BINARY_SERIALIZATION_ENABLED = false;
public static final int DEFAULT_MAX_DATA_SIZE_BYTES = 20 * 1024 * 1024;
public static final int DEFAULT_COMPRESSION_THRESHOLD_BYTES = 120;

public enum Key {
BINARY_SERIALIZATION_ENABLED("binary.serialization.enabled", "default.evcache.binary.serialization.enabled"),
MAX_DATA_SIZE_BYTES("max.data.size", "default.evcache.max.data.size"),
COMPRESSION_THRESHOLD_BYTES("compression.threshold", "default.evcache.compression.threshold");

final String appKeySuffix;
final String globalKey;

Key(String appKeySuffix, String globalKey) {
this.appKeySuffix = appKeySuffix;
this.globalKey = globalKey;
}
}

private final String appName;
private final PropertyRepository propertyRepository;

private final boolean binarySerializationEnabled;

/**
* Construct the bundle and snapshot every property via the three-level resolution chain.
*
* @param appName the EVCache app name used as the per-app override prefix
* (e.g. {@code "EVCACHE_FOO"}). When {@code null} or empty the
* per-app step is skipped and resolution starts at the global
* key — useful for the no-app transcoder constructors and for
* callers that only want fleet-wide defaults.
* @param propertyRepository the Archaius2 PropertyRepository to resolve against. Never null;
* pass {@code EVCacheConfig.getInstance().getPropertyRepository()}
* for the production wiring.
*/
public EVCacheTranscoderProperties(String appName, PropertyRepository propertyRepository) {
this.appName = appName;
this.propertyRepository = propertyRepository;
this.binarySerializationEnabled = getProperty(appName, propertyRepository,
BINARY_SERIALIZATION_ENABLED, Boolean.class, DEFAULT_BINARY_SERIALIZATION_ENABLED).get();
}

public boolean isBinarySerializationEnabled() {
return binarySerializationEnabled;
}

/**
* Resolve the Archaius {@link Property} handle for the given key. Callers should hold the
* handle (as a final field, typically) and invoke {@link Property#get()} when they need the
* current value; every {@code .get()} re-reads through the same per-app → global → static-default
* chain, so live FP updates propagate without re-resolving. Returning the handle rather than
* the resolved value makes it obvious that this is a dynamic property, not a static snapshot.
*/
public <T> Property<T> getProperty(Key key, Class<T> type, T defaultValue) {
return getProperty(appName, propertyRepository, key, type, defaultValue);
}

private static <T> Property<T> getProperty(String appName, PropertyRepository propertyRepository,
Key key, Class<T> type, T defaultValue) {
if (appName == null || appName.isEmpty()) {
return propertyRepository.get(key.globalKey, type).orElse(defaultValue);
}
return propertyRepository.get(appName + "." + key.appKeySuffix, type)
.orElseGet(key.globalKey)
.orElse(defaultValue);
}
}
Loading
Loading