
com.netflix.hystrix.collapser.RequestCollapserFactory Maven / Gradle / Ivy
/**
* Copyright 2015 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.hystrix.collapser;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.netflix.hystrix.HystrixCollapser;
import com.netflix.hystrix.HystrixCollapserKey;
import com.netflix.hystrix.HystrixCollapserProperties;
import com.netflix.hystrix.strategy.HystrixPlugins;
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableHolder;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableLifecycle;
import com.netflix.hystrix.strategy.properties.HystrixPropertiesFactory;
import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy;
import com.netflix.hystrix.util.HystrixTimer;
/**
* Factory for retrieving the correct instance of a RequestCollapser.
*
* @param
* @param
* @param
*/
public class RequestCollapserFactory {
private static final Logger logger = LoggerFactory.getLogger(RequestCollapserFactory.class);
private final CollapserTimer timer;
private final HystrixCollapserKey collapserKey;
private final HystrixCollapserProperties properties;
private final HystrixConcurrencyStrategy concurrencyStrategy;
private final Scope scope;
public static interface Scope {
String name();
}
// internally expected scopes, dealing with the not-so-fun inheritance issues of enum when shared between classes
private static enum Scopes implements Scope {
REQUEST, GLOBAL
}
public RequestCollapserFactory(HystrixCollapserKey collapserKey, Scope scope, CollapserTimer timer, HystrixCollapserProperties.Setter propertiesBuilder) {
this(collapserKey, scope, timer, HystrixPropertiesFactory.getCollapserProperties(collapserKey, propertiesBuilder));
}
public RequestCollapserFactory(HystrixCollapserKey collapserKey, Scope scope, CollapserTimer timer, HystrixCollapserProperties properties) {
/* strategy: ConcurrencyStrategy */
this.concurrencyStrategy = HystrixPlugins.getInstance().getConcurrencyStrategy();
this.timer = timer;
this.scope = scope;
this.collapserKey = collapserKey;
this.properties = properties;
}
public HystrixCollapserKey getCollapserKey() {
return collapserKey;
}
public Scope getScope() {
return scope;
}
public HystrixCollapserProperties getProperties() {
return properties;
}
public RequestCollapser getRequestCollapser(HystrixCollapserBridge commandCollapser) {
if (Scopes.REQUEST == Scopes.valueOf(getScope().name())) {
return getCollapserForUserRequest(commandCollapser);
} else if (Scopes.GLOBAL == Scopes.valueOf(getScope().name())) {
return getCollapserForGlobalScope(commandCollapser);
} else {
logger.warn("Invalid Scope: {} Defaulting to REQUEST scope.", getScope());
return getCollapserForUserRequest(commandCollapser);
}
}
/**
* Static global cache of RequestCollapsers for Scope.GLOBAL
*/
// String is CollapserKey.name() (we can't use CollapserKey directly as we can't guarantee it implements hashcode/equals correctly)
private static ConcurrentHashMap> globalScopedCollapsers = new ConcurrentHashMap>();
@SuppressWarnings("unchecked")
private RequestCollapser getCollapserForGlobalScope(HystrixCollapserBridge commandCollapser) {
RequestCollapser, ?, ?> collapser = globalScopedCollapsers.get(collapserKey.name());
if (collapser != null) {
return (RequestCollapser) collapser;
}
// create new collapser using 'this' first instance as the one that will get cached for future executions ('this' is stateless so we can do that)
RequestCollapser newCollapser = new RequestCollapser(commandCollapser, properties, timer, concurrencyStrategy);
RequestCollapser, ?, ?> existing = globalScopedCollapsers.putIfAbsent(collapserKey.name(), newCollapser);
if (existing == null) {
// we won
return newCollapser;
} else {
// we lost ... another thread beat us
// shutdown the one we created but didn't get stored
newCollapser.shutdown();
// return the existing one
return (RequestCollapser) existing;
}
}
/**
* Static global cache of RequestVariables with RequestCollapsers for Scope.REQUEST
*/
// String is HystrixCollapserKey.name() (we can't use HystrixCollapserKey directly as we can't guarantee it implements hashcode/equals correctly)
private static ConcurrentHashMap>> requestScopedCollapsers = new ConcurrentHashMap>>();
/* we are casting because the Map needs to be , ?> but we know it is for this thread */
@SuppressWarnings("unchecked")
private RequestCollapser getCollapserForUserRequest(HystrixCollapserBridge commandCollapser) {
return (RequestCollapser) getRequestVariableForCommand(commandCollapser).get(concurrencyStrategy);
}
/**
* Lookup (or create and store) the RequestVariable for a given HystrixCollapserKey.
*
* @param commandCollapser collapser to retrieve {@link HystrixRequestVariableHolder} for
* @return HystrixRequestVariableHolder
*/
@SuppressWarnings("unchecked")
private HystrixRequestVariableHolder> getRequestVariableForCommand(final HystrixCollapserBridge commandCollapser) {
HystrixRequestVariableHolder> requestVariable = requestScopedCollapsers.get(commandCollapser.getCollapserKey().name());
if (requestVariable == null) {
// create new collapser using 'this' first instance as the one that will get cached for future executions ('this' is stateless so we can do that)
@SuppressWarnings({ "rawtypes" })
HystrixRequestVariableHolder newCollapser = new RequestCollapserRequestVariable(commandCollapser, properties, timer, concurrencyStrategy);
HystrixRequestVariableHolder> existing = requestScopedCollapsers.putIfAbsent(commandCollapser.getCollapserKey().name(), newCollapser);
if (existing == null) {
// this thread won, so return the one we just created
requestVariable = newCollapser;
} else {
// another thread beat us (this should only happen when we have concurrency on the FIRST request for the life of the app for this HystrixCollapser class)
requestVariable = existing;
/*
* This *should* be okay to discard the created object without cleanup as the RequestVariable implementation
* should properly do lazy-initialization and only call initialValue() the first time get() is called.
*
* If it does not correctly follow this contract then there is a chance of a memory leak here.
*/
}
}
return requestVariable;
}
/**
* Clears all state. If new requests come in instances will be recreated and metrics started from scratch.
*/
public static void reset() {
defaultNameCache.clear();
globalScopedCollapsers.clear();
requestScopedCollapsers.clear();
HystrixTimer.reset();
}
/**
* Used for testing
*/
public static void resetRequest() {
requestScopedCollapsers.clear();
}
/**
* Used for testing
*/
public static HystrixRequestVariableHolder> getRequestVariable(String key) {
return requestScopedCollapsers.get(key);
}
/**
* Request scoped RequestCollapser that lives inside a RequestVariable.
*
* This depends on the RequestVariable getting reset before each user request in NFFilter to ensure the RequestCollapser is new for each user request.
*/
private final class RequestCollapserRequestVariable extends HystrixRequestVariableHolder> {
/**
* NOTE: There is only 1 instance of this for the life of the app per HystrixCollapser instance. The state changes on each request via the initialValue()/get() methods.
*
* Thus, do NOT put any instance variables in this class that are not static for all threads.
*/
private RequestCollapserRequestVariable(final HystrixCollapserBridge commandCollapser, final HystrixCollapserProperties properties, final CollapserTimer timer, final HystrixConcurrencyStrategy concurrencyStrategy) {
super(new HystrixRequestVariableLifecycle>() {
@Override
public RequestCollapser initialValue() {
// this gets calls once per request per HystrixCollapser instance
return new RequestCollapser(commandCollapser, properties, timer, concurrencyStrategy);
}
@Override
public void shutdown(RequestCollapser currentCollapser) {
// shut down the RequestCollapser (the internal timer tasks)
if (currentCollapser != null) {
currentCollapser.shutdown();
}
}
});
}
}
/**
* Fluent interface for arguments to the {@link HystrixCollapser} constructor.
*
* The required arguments are set via the 'with' factory method and optional arguments via the 'and' chained methods.
*
* Example:
*
{@code
* Setter.withCollapserKey(HystrixCollapserKey.Factory.asKey("CollapserName"))
.andScope(Scope.REQUEST);
* }
*
* @NotThreadSafe
*/
public static class Setter {
private final HystrixCollapserKey collapserKey;
private Scope scope = Scopes.REQUEST; // default if nothing is set
private HystrixCollapserProperties.Setter propertiesSetter;
private Setter(HystrixCollapserKey collapserKey) {
this.collapserKey = collapserKey;
}
/**
* Setter factory method containing required values.
*
* All optional arguments can be set via the chained methods.
*
* @param collapserKey
* {@link HystrixCollapserKey} that identifies this collapser and provides the key used for retrieving properties, request caches, publishing metrics etc.
* @return Setter for fluent interface via method chaining
*/
public static Setter withCollapserKey(HystrixCollapserKey collapserKey) {
return new Setter(collapserKey);
}
/**
* {@link Scope} defining what scope the collapsing should occur within
*
* @param scope collapser scope
*
* @return Setter for fluent interface via method chaining
*/
public Setter andScope(Scope scope) {
this.scope = scope;
return this;
}
/**
* @param propertiesSetter
* {@link HystrixCollapserProperties.Setter} that allows instance specific property overrides (which can then be overridden by dynamic properties, see
* {@link HystrixPropertiesStrategy} for
* information on order of precedence).
*
* Will use defaults if left NULL.
* @return Setter for fluent interface via method chaining
*/
public Setter andCollapserPropertiesDefaults(HystrixCollapserProperties.Setter propertiesSetter) {
this.propertiesSetter = propertiesSetter;
return this;
}
}
// this is a micro-optimization but saves about 1-2microseconds (on 2011 MacBook Pro)
// on the repetitive string processing that will occur on the same classes over and over again
@SuppressWarnings("rawtypes")
private static ConcurrentHashMap, String> defaultNameCache = new ConcurrentHashMap, String>();
}