com.netflix.hystrix.collapser.RequestCollapserFactory Maven / Gradle / Ivy
Show all versions of hystrix-core Show documentation
/**
* 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.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.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() {
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();
}
}
});
}
}
}