init
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel;
|
||||
|
||||
import com.alibaba.csp.sentinel.context.Context;
|
||||
import com.alibaba.csp.sentinel.context.NullContext;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.slotchain.ProcessorSlot;
|
||||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
|
||||
|
||||
/**
|
||||
* The entry for asynchronous resources.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public class AsyncEntry extends CtEntry {
|
||||
|
||||
private Context asyncContext;
|
||||
|
||||
AsyncEntry(ResourceWrapper resourceWrapper, ProcessorSlot<Object> chain, Context context) {
|
||||
super(resourceWrapper, chain, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove current entry from local context, but does not exit.
|
||||
*/
|
||||
void cleanCurrentEntryInLocal() {
|
||||
if (context instanceof NullContext) {
|
||||
return;
|
||||
}
|
||||
Context originalContext = context;
|
||||
if (originalContext != null) {
|
||||
Entry curEntry = originalContext.getCurEntry();
|
||||
if (curEntry == this) {
|
||||
Entry parent = this.parent;
|
||||
originalContext.setCurEntry(parent);
|
||||
if (parent != null) {
|
||||
((CtEntry)parent).child = null;
|
||||
}
|
||||
} else {
|
||||
String curEntryName = curEntry == null ? "none"
|
||||
: curEntry.resourceWrapper.getName() + "@" + curEntry.hashCode();
|
||||
String msg = String.format("Bad async context state, expected entry: %s, but actual: %s",
|
||||
getResourceWrapper().getName() + "@" + hashCode(), curEntryName);
|
||||
throw new IllegalStateException(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Context getAsyncContext() {
|
||||
return asyncContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* The async context should not be initialized until the node for current resource has been set to current entry.
|
||||
*/
|
||||
void initAsyncContext() {
|
||||
if (asyncContext == null) {
|
||||
if (context instanceof NullContext) {
|
||||
asyncContext = context;
|
||||
return;
|
||||
}
|
||||
this.asyncContext = Context.newAsyncContext(context.getEntranceNode(), context.getName())
|
||||
.setOrigin(context.getOrigin())
|
||||
.setCurEntry(this);
|
||||
} else {
|
||||
RecordLog.warn(
|
||||
"[AsyncEntry] Duplicate initialize of async context for entry: " + resourceWrapper.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void clearEntryContext() {
|
||||
super.clearEntryContext();
|
||||
this.asyncContext = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Entry trueExit(int count, Object... args) throws ErrorEntryFreeException {
|
||||
exitForContext(asyncContext, count, args);
|
||||
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel;
|
||||
|
||||
import com.alibaba.csp.sentinel.node.ClusterNode;
|
||||
import com.alibaba.csp.sentinel.node.DefaultNode;
|
||||
import com.alibaba.csp.sentinel.node.EntranceNode;
|
||||
import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper;
|
||||
import com.alibaba.csp.sentinel.util.VersionUtil;
|
||||
|
||||
/**
|
||||
* Universal constants of Sentinel.
|
||||
*
|
||||
* @author qinan.qn
|
||||
* @author youji.zj
|
||||
* @author jialiang.linjl
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public final class Constants {
|
||||
|
||||
public static final String SENTINEL_VERSION = VersionUtil.getVersion("1.8.3");
|
||||
|
||||
public final static int MAX_CONTEXT_NAME_SIZE = 2000;
|
||||
public final static int MAX_SLOT_CHAIN_SIZE = 6000;
|
||||
|
||||
public final static String ROOT_ID = "machine-root";
|
||||
public final static String CONTEXT_DEFAULT_NAME = "sentinel_default_context";
|
||||
|
||||
/**
|
||||
* A virtual resource identifier for total inbound statistics (since 1.5.0).
|
||||
*/
|
||||
public final static String TOTAL_IN_RESOURCE_NAME = "__total_inbound_traffic__";
|
||||
|
||||
/**
|
||||
* A virtual resource identifier for cpu usage statistics (since 1.6.1).
|
||||
*/
|
||||
public final static String CPU_USAGE_RESOURCE_NAME = "__cpu_usage__";
|
||||
|
||||
/**
|
||||
* A virtual resource identifier for system load statistics (since 1.6.1).
|
||||
*/
|
||||
public final static String SYSTEM_LOAD_RESOURCE_NAME = "__system_load__";
|
||||
|
||||
/**
|
||||
* Global ROOT statistic node that represents the universal parent node.
|
||||
*/
|
||||
public final static DefaultNode ROOT = new EntranceNode(new StringResourceWrapper(ROOT_ID, EntryType.IN),
|
||||
new ClusterNode(ROOT_ID, ResourceTypeConstants.COMMON));
|
||||
|
||||
/**
|
||||
* Global statistic node for inbound traffic. Usually used for {@code SystemRule} checking.
|
||||
*/
|
||||
public final static ClusterNode ENTRY_NODE = new ClusterNode(TOTAL_IN_RESOURCE_NAME, ResourceTypeConstants.COMMON);
|
||||
|
||||
/**
|
||||
* The global switch for Sentinel.
|
||||
*/
|
||||
public static volatile boolean ON = true;
|
||||
|
||||
/**
|
||||
* Order of default processor slots
|
||||
*/
|
||||
public static final int ORDER_NODE_SELECTOR_SLOT = -10000;
|
||||
public static final int ORDER_CLUSTER_BUILDER_SLOT = -9000;
|
||||
public static final int ORDER_LOG_SLOT = -8000;
|
||||
public static final int ORDER_STATISTIC_SLOT = -7000;
|
||||
public static final int ORDER_AUTHORITY_SLOT = -6000;
|
||||
public static final int ORDER_SYSTEM_SLOT = -5000;
|
||||
public static final int ORDER_FLOW_SLOT = -2000;
|
||||
public static final int ORDER_DEGRADE_SLOT = -1000;
|
||||
|
||||
private Constants() {}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
import com.alibaba.csp.sentinel.context.Context;
|
||||
import com.alibaba.csp.sentinel.context.ContextUtil;
|
||||
import com.alibaba.csp.sentinel.context.NullContext;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.node.Node;
|
||||
import com.alibaba.csp.sentinel.slotchain.ProcessorSlot;
|
||||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
|
||||
import com.alibaba.csp.sentinel.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* Linked entry within current context.
|
||||
*
|
||||
* @author jialiang.linjl
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
class CtEntry extends Entry {
|
||||
|
||||
protected Entry parent = null;
|
||||
protected Entry child = null;
|
||||
|
||||
protected ProcessorSlot<Object> chain;
|
||||
protected Context context;
|
||||
protected LinkedList<BiConsumer<Context, Entry>> exitHandlers;
|
||||
|
||||
CtEntry(ResourceWrapper resourceWrapper, ProcessorSlot<Object> chain, Context context) {
|
||||
super(resourceWrapper);
|
||||
this.chain = chain;
|
||||
this.context = context;
|
||||
|
||||
setUpEntryFor(context);
|
||||
}
|
||||
|
||||
private void setUpEntryFor(Context context) {
|
||||
// The entry should not be associated to NullContext.
|
||||
if (context instanceof NullContext) {
|
||||
return;
|
||||
}
|
||||
this.parent = context.getCurEntry();
|
||||
if (parent != null) {
|
||||
((CtEntry) parent).child = this;
|
||||
}
|
||||
context.setCurEntry(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exit(int count, Object... args) throws ErrorEntryFreeException {
|
||||
trueExit(count, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: the exit handlers will be called AFTER onExit of slot chain.
|
||||
*/
|
||||
private void callExitHandlersAndCleanUp(Context ctx) {
|
||||
if (exitHandlers != null && !exitHandlers.isEmpty()) {
|
||||
for (BiConsumer<Context, Entry> handler : this.exitHandlers) {
|
||||
try {
|
||||
handler.accept(ctx, this);
|
||||
} catch (Exception e) {
|
||||
RecordLog.warn("Error occurred when invoking entry exit handler, current entry: "
|
||||
+ resourceWrapper.getName(), e);
|
||||
}
|
||||
}
|
||||
exitHandlers = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected void exitForContext(Context context, int count, Object... args) throws ErrorEntryFreeException {
|
||||
if (context != null) {
|
||||
// Null context should exit without clean-up.
|
||||
if (context instanceof NullContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.getCurEntry() != this) {
|
||||
String curEntryNameInContext = context.getCurEntry() == null ? null
|
||||
: context.getCurEntry().getResourceWrapper().getName();
|
||||
// Clean previous call stack.
|
||||
CtEntry e = (CtEntry) context.getCurEntry();
|
||||
while (e != null) {
|
||||
e.exit(count, args);
|
||||
e = (CtEntry) e.parent;
|
||||
}
|
||||
String errorMessage = String.format("The order of entry exit can't be paired with the order of entry"
|
||||
+ ", current entry in context: <%s>, but expected: <%s>", curEntryNameInContext,
|
||||
resourceWrapper.getName());
|
||||
throw new ErrorEntryFreeException(errorMessage);
|
||||
} else {
|
||||
// Go through the onExit hook of all slots.
|
||||
if (chain != null) {
|
||||
chain.exit(context, resourceWrapper, count, args);
|
||||
}
|
||||
// Go through the existing terminate handlers (associated to this invocation).
|
||||
callExitHandlersAndCleanUp(context);
|
||||
|
||||
// Restore the call stack.
|
||||
context.setCurEntry(parent);
|
||||
if (parent != null) {
|
||||
((CtEntry) parent).child = null;
|
||||
}
|
||||
if (parent == null) {
|
||||
// Default context (auto entered) will be exited automatically.
|
||||
if (ContextUtil.isDefaultContext(context)) {
|
||||
ContextUtil.exit();
|
||||
}
|
||||
}
|
||||
// Clean the reference of context in current entry to avoid duplicate exit.
|
||||
clearEntryContext();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void clearEntryContext() {
|
||||
this.context = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void whenTerminate(BiConsumer<Context, Entry> handler) {
|
||||
if (this.exitHandlers == null) {
|
||||
this.exitHandlers = new LinkedList<>();
|
||||
}
|
||||
this.exitHandlers.add(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Entry trueExit(int count, Object... args) throws ErrorEntryFreeException {
|
||||
exitForContext(context, count, args);
|
||||
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getLastNode() {
|
||||
return parent == null ? null : parent.getCurNode();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.context.Context;
|
||||
import com.alibaba.csp.sentinel.context.ContextUtil;
|
||||
import com.alibaba.csp.sentinel.context.NullContext;
|
||||
import com.alibaba.csp.sentinel.slotchain.MethodResourceWrapper;
|
||||
import com.alibaba.csp.sentinel.slotchain.ProcessorSlot;
|
||||
import com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain;
|
||||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
|
||||
import com.alibaba.csp.sentinel.slotchain.SlotChainProvider;
|
||||
import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper;
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
import com.alibaba.csp.sentinel.slots.block.Rule;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @author jialiang.linjl
|
||||
* @author leyou(lihao)
|
||||
* @author Eric Zhao
|
||||
* @see Sph
|
||||
*/
|
||||
public class CtSph implements Sph {
|
||||
|
||||
private static final Object[] OBJECTS0 = new Object[0];
|
||||
|
||||
/**
|
||||
* Same resource({@link ResourceWrapper#equals(Object)}) will share the same
|
||||
* {@link ProcessorSlotChain}, no matter in which {@link Context}.
|
||||
*/
|
||||
private static volatile Map<ResourceWrapper, ProcessorSlotChain> chainMap
|
||||
= new HashMap<ResourceWrapper, ProcessorSlotChain>();
|
||||
|
||||
private static final Object LOCK = new Object();
|
||||
|
||||
private AsyncEntry asyncEntryWithNoChain(ResourceWrapper resourceWrapper, Context context) {
|
||||
AsyncEntry entry = new AsyncEntry(resourceWrapper, null, context);
|
||||
entry.initAsyncContext();
|
||||
// The async entry will be removed from current context as soon as it has been created.
|
||||
entry.cleanCurrentEntryInLocal();
|
||||
return entry;
|
||||
}
|
||||
|
||||
private AsyncEntry asyncEntryWithPriorityInternal(ResourceWrapper resourceWrapper, int count, boolean prioritized,
|
||||
Object... args) throws BlockException {
|
||||
Context context = ContextUtil.getContext();
|
||||
if (context instanceof NullContext) {
|
||||
// The {@link NullContext} indicates that the amount of context has exceeded the threshold,
|
||||
// so here init the entry only. No rule checking will be done.
|
||||
return asyncEntryWithNoChain(resourceWrapper, context);
|
||||
}
|
||||
if (context == null) {
|
||||
// Using default context.
|
||||
context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);
|
||||
}
|
||||
|
||||
// Global switch is turned off, so no rule checking will be done.
|
||||
if (!Constants.ON) {
|
||||
return asyncEntryWithNoChain(resourceWrapper, context);
|
||||
}
|
||||
|
||||
ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
|
||||
|
||||
// Means processor cache size exceeds {@link Constants.MAX_SLOT_CHAIN_SIZE}, so no rule checking will be done.
|
||||
if (chain == null) {
|
||||
return asyncEntryWithNoChain(resourceWrapper, context);
|
||||
}
|
||||
|
||||
AsyncEntry asyncEntry = new AsyncEntry(resourceWrapper, chain, context);
|
||||
try {
|
||||
chain.entry(context, resourceWrapper, null, count, prioritized, args);
|
||||
// Initiate the async context only when the entry successfully passed the slot chain.
|
||||
asyncEntry.initAsyncContext();
|
||||
// The asynchronous call may take time in background, and current context should not be hanged on it.
|
||||
// So we need to remove current async entry from current context.
|
||||
asyncEntry.cleanCurrentEntryInLocal();
|
||||
} catch (BlockException e1) {
|
||||
// When blocked, the async entry will be exited on current context.
|
||||
// The async context will not be initialized.
|
||||
asyncEntry.exitForContext(context, count, args);
|
||||
throw e1;
|
||||
} catch (Throwable e1) {
|
||||
// This should not happen, unless there are errors existing in Sentinel internal.
|
||||
// When this happens, async context is not initialized.
|
||||
RecordLog.warn("Sentinel unexpected exception in asyncEntryInternal", e1);
|
||||
|
||||
asyncEntry.cleanCurrentEntryInLocal();
|
||||
}
|
||||
return asyncEntry;
|
||||
}
|
||||
|
||||
private AsyncEntry asyncEntryInternal(ResourceWrapper resourceWrapper, int count, Object... args)
|
||||
throws BlockException {
|
||||
return asyncEntryWithPriorityInternal(resourceWrapper, count, false, args);
|
||||
}
|
||||
|
||||
private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
|
||||
throws BlockException {
|
||||
Context context = ContextUtil.getContext();
|
||||
if (context instanceof NullContext) {
|
||||
// The {@link NullContext} indicates that the amount of context has exceeded the threshold,
|
||||
// so here init the entry only. No rule checking will be done.
|
||||
return new CtEntry(resourceWrapper, null, context);
|
||||
}
|
||||
|
||||
if (context == null) {
|
||||
// Using default context.
|
||||
context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);
|
||||
}
|
||||
|
||||
// Global switch is close, no rule checking will do.
|
||||
if (!Constants.ON) {
|
||||
return new CtEntry(resourceWrapper, null, context);
|
||||
}
|
||||
|
||||
ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
|
||||
|
||||
/*
|
||||
* Means amount of resources (slot chain) exceeds {@link Constants.MAX_SLOT_CHAIN_SIZE},
|
||||
* so no rule checking will be done.
|
||||
*/
|
||||
if (chain == null) {
|
||||
return new CtEntry(resourceWrapper, null, context);
|
||||
}
|
||||
|
||||
Entry e = new CtEntry(resourceWrapper, chain, context);
|
||||
try {
|
||||
chain.entry(context, resourceWrapper, null, count, prioritized, args);
|
||||
} catch (BlockException e1) {
|
||||
e.exit(count, args);
|
||||
throw e1;
|
||||
} catch (Throwable e1) {
|
||||
// This should not happen, unless there are errors existing in Sentinel internal.
|
||||
RecordLog.info("Sentinel unexpected exception", e1);
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do all {@link Rule}s checking about the resource.
|
||||
*
|
||||
* <p>Each distinct resource will use a {@link ProcessorSlot} to do rules checking. Same resource will use
|
||||
* same {@link ProcessorSlot} globally. </p>
|
||||
*
|
||||
* <p>Note that total {@link ProcessorSlot} count must not exceed {@link Constants#MAX_SLOT_CHAIN_SIZE},
|
||||
* otherwise no rules checking will do. In this condition, all requests will pass directly, with no checking
|
||||
* or exception.</p>
|
||||
*
|
||||
* @param resourceWrapper resource name
|
||||
* @param count tokens needed
|
||||
* @param args arguments of user method call
|
||||
* @return {@link Entry} represents this call
|
||||
* @throws BlockException if any rule's threshold is exceeded
|
||||
*/
|
||||
public Entry entry(ResourceWrapper resourceWrapper, int count, Object... args) throws BlockException {
|
||||
return entryWithPriority(resourceWrapper, count, false, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get {@link ProcessorSlotChain} of the resource. new {@link ProcessorSlotChain} will
|
||||
* be created if the resource doesn't relate one.
|
||||
*
|
||||
* <p>Same resource({@link ResourceWrapper#equals(Object)}) will share the same
|
||||
* {@link ProcessorSlotChain} globally, no matter in which {@link Context}.<p/>
|
||||
*
|
||||
* <p>
|
||||
* Note that total {@link ProcessorSlot} count must not exceed {@link Constants#MAX_SLOT_CHAIN_SIZE},
|
||||
* otherwise null will return.
|
||||
* </p>
|
||||
*
|
||||
* @param resourceWrapper target resource
|
||||
* @return {@link ProcessorSlotChain} of the resource
|
||||
*/
|
||||
ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
|
||||
ProcessorSlotChain chain = chainMap.get(resourceWrapper);
|
||||
if (chain == null) {
|
||||
synchronized (LOCK) {
|
||||
chain = chainMap.get(resourceWrapper);
|
||||
if (chain == null) {
|
||||
// Entry size limit.
|
||||
if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
chain = SlotChainProvider.newSlotChain();
|
||||
Map<ResourceWrapper, ProcessorSlotChain> newMap = new HashMap<ResourceWrapper, ProcessorSlotChain>(
|
||||
chainMap.size() + 1);
|
||||
newMap.putAll(chainMap);
|
||||
newMap.put(resourceWrapper, chain);
|
||||
chainMap = newMap;
|
||||
}
|
||||
}
|
||||
}
|
||||
return chain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current size of created slot chains.
|
||||
*
|
||||
* @return size of created slot chains
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public static int entrySize() {
|
||||
return chainMap.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the slot chain map. Only for internal test.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*/
|
||||
static void resetChainMap() {
|
||||
chainMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Only for internal test.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*/
|
||||
static Map<ResourceWrapper, ProcessorSlotChain> getChainMap() {
|
||||
return chainMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is used for skip context name checking.
|
||||
*/
|
||||
private final static class InternalContextUtil extends ContextUtil {
|
||||
static Context internalEnter(String name) {
|
||||
return trueEnter(name, "");
|
||||
}
|
||||
|
||||
static Context internalEnter(String name, String origin) {
|
||||
return trueEnter(name, origin);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry entry(String name) throws BlockException {
|
||||
StringResourceWrapper resource = new StringResourceWrapper(name, EntryType.OUT);
|
||||
return entry(resource, 1, OBJECTS0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry entry(Method method) throws BlockException {
|
||||
MethodResourceWrapper resource = new MethodResourceWrapper(method, EntryType.OUT);
|
||||
return entry(resource, 1, OBJECTS0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry entry(Method method, EntryType type) throws BlockException {
|
||||
MethodResourceWrapper resource = new MethodResourceWrapper(method, type);
|
||||
return entry(resource, 1, OBJECTS0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry entry(String name, EntryType type) throws BlockException {
|
||||
StringResourceWrapper resource = new StringResourceWrapper(name, type);
|
||||
return entry(resource, 1, OBJECTS0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry entry(Method method, EntryType type, int count) throws BlockException {
|
||||
MethodResourceWrapper resource = new MethodResourceWrapper(method, type);
|
||||
return entry(resource, count, OBJECTS0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry entry(String name, EntryType type, int count) throws BlockException {
|
||||
StringResourceWrapper resource = new StringResourceWrapper(name, type);
|
||||
return entry(resource, count, OBJECTS0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry entry(Method method, int count) throws BlockException {
|
||||
MethodResourceWrapper resource = new MethodResourceWrapper(method, EntryType.OUT);
|
||||
return entry(resource, count, OBJECTS0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry entry(String name, int count) throws BlockException {
|
||||
StringResourceWrapper resource = new StringResourceWrapper(name, EntryType.OUT);
|
||||
return entry(resource, count, OBJECTS0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry entry(Method method, EntryType type, int count, Object... args) throws BlockException {
|
||||
MethodResourceWrapper resource = new MethodResourceWrapper(method, type);
|
||||
return entry(resource, count, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry entry(String name, EntryType type, int count, Object... args) throws BlockException {
|
||||
StringResourceWrapper resource = new StringResourceWrapper(name, type);
|
||||
return entry(resource, count, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncEntry asyncEntry(String name, EntryType type, int count, Object... args) throws BlockException {
|
||||
StringResourceWrapper resource = new StringResourceWrapper(name, type);
|
||||
return asyncEntryInternal(resource, count, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry entryWithPriority(String name, EntryType type, int count, boolean prioritized) throws BlockException {
|
||||
StringResourceWrapper resource = new StringResourceWrapper(name, type);
|
||||
return entryWithPriority(resource, count, prioritized);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry entryWithPriority(String name, EntryType type, int count, boolean prioritized, Object... args)
|
||||
throws BlockException {
|
||||
StringResourceWrapper resource = new StringResourceWrapper(name, type);
|
||||
return entryWithPriority(resource, count, prioritized, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry entryWithType(String name, int resourceType, EntryType entryType, int count, Object[] args)
|
||||
throws BlockException {
|
||||
return entryWithType(name, resourceType, entryType, count, false, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry entryWithType(String name, int resourceType, EntryType entryType, int count, boolean prioritized,
|
||||
Object[] args) throws BlockException {
|
||||
StringResourceWrapper resource = new StringResourceWrapper(name, entryType, resourceType);
|
||||
return entryWithPriority(resource, count, prioritized, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncEntry asyncEntryWithType(String name, int resourceType, EntryType entryType, int count,
|
||||
boolean prioritized, Object[] args) throws BlockException {
|
||||
StringResourceWrapper resource = new StringResourceWrapper(name, entryType, resourceType);
|
||||
return asyncEntryWithPriorityInternal(resource, count, prioritized, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel;
|
||||
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
import com.alibaba.csp.sentinel.util.TimeUtil;
|
||||
import com.alibaba.csp.sentinel.util.function.BiConsumer;
|
||||
import com.alibaba.csp.sentinel.context.ContextUtil;
|
||||
import com.alibaba.csp.sentinel.node.Node;
|
||||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
|
||||
import com.alibaba.csp.sentinel.context.Context;
|
||||
|
||||
/**
|
||||
* Each {@link SphU}#entry() will return an {@link Entry}. This class holds information of current invocation:<br/>
|
||||
*
|
||||
* <ul>
|
||||
* <li>createTime, the create time of this entry, using for rt statistics.</li>
|
||||
* <li>current {@link Node}, that is statistics of the resource in current context.</li>
|
||||
* <li>origin {@link Node}, that is statistics for the specific origin. Usually the
|
||||
* origin could be the Service Consumer's app name, see
|
||||
* {@link ContextUtil#enter(String name, String origin)} </li>
|
||||
* <li>{@link ResourceWrapper}, that is resource name.</li>
|
||||
* <br/>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* A invocation tree will be created if we invoke SphU#entry() multi times in the same {@link Context},
|
||||
* so parent or child entry may be held by this to form the tree. Since {@link Context} always holds
|
||||
* the current entry in the invocation tree, every {@link Entry#exit()} call should modify
|
||||
* {@link Context#setCurEntry(Entry)} as parent entry of this.
|
||||
* </p>
|
||||
*
|
||||
* @author qinan.qn
|
||||
* @author jialiang.linjl
|
||||
* @author leyou(lihao)
|
||||
* @author Eric Zhao
|
||||
* @see SphU
|
||||
* @see Context
|
||||
* @see ContextUtil
|
||||
*/
|
||||
public abstract class Entry implements AutoCloseable {
|
||||
|
||||
private static final Object[] OBJECTS0 = new Object[0];
|
||||
|
||||
private final long createTimestamp;
|
||||
private long completeTimestamp;
|
||||
|
||||
private Node curNode;
|
||||
/**
|
||||
* {@link Node} of the specific origin, Usually the origin is the Service Consumer.
|
||||
*/
|
||||
private Node originNode;
|
||||
|
||||
private Throwable error;
|
||||
private BlockException blockError;
|
||||
|
||||
protected final ResourceWrapper resourceWrapper;
|
||||
|
||||
public Entry(ResourceWrapper resourceWrapper) {
|
||||
this.resourceWrapper = resourceWrapper;
|
||||
this.createTimestamp = TimeUtil.currentTimeMillis();
|
||||
}
|
||||
|
||||
public ResourceWrapper getResourceWrapper() {
|
||||
return resourceWrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete the current resource entry and restore the entry stack in context.
|
||||
*
|
||||
* @throws ErrorEntryFreeException if entry in current context does not match current entry
|
||||
*/
|
||||
public void exit() throws ErrorEntryFreeException {
|
||||
exit(1, OBJECTS0);
|
||||
}
|
||||
|
||||
public void exit(int count) throws ErrorEntryFreeException {
|
||||
exit(count, OBJECTS0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to {@link #exit()}. Support try-with-resources since JDK 1.7.
|
||||
*
|
||||
* @since 1.5.0
|
||||
*/
|
||||
@Override
|
||||
public void close() {
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Exit this entry. This method should invoke if and only if once at the end of the resource protection.
|
||||
*
|
||||
* @param count tokens to release.
|
||||
* @param args extra parameters
|
||||
* @throws ErrorEntryFreeException, if {@link Context#getCurEntry()} is not this entry.
|
||||
*/
|
||||
public abstract void exit(int count, Object... args) throws ErrorEntryFreeException;
|
||||
|
||||
/**
|
||||
* Exit this entry.
|
||||
*
|
||||
* @param count tokens to release.
|
||||
* @param args extra parameters
|
||||
* @return next available entry after exit, that is the parent entry.
|
||||
* @throws ErrorEntryFreeException, if {@link Context#getCurEntry()} is not this entry.
|
||||
*/
|
||||
protected abstract Entry trueExit(int count, Object... args) throws ErrorEntryFreeException;
|
||||
|
||||
/**
|
||||
* Get related {@link Node} of the parent {@link Entry}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public abstract Node getLastNode();
|
||||
|
||||
public long getCreateTimestamp() {
|
||||
return createTimestamp;
|
||||
}
|
||||
|
||||
public long getCompleteTimestamp() {
|
||||
return completeTimestamp;
|
||||
}
|
||||
|
||||
public Entry setCompleteTimestamp(long completeTimestamp) {
|
||||
this.completeTimestamp = completeTimestamp;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Node getCurNode() {
|
||||
return curNode;
|
||||
}
|
||||
|
||||
public void setCurNode(Node node) {
|
||||
this.curNode = node;
|
||||
}
|
||||
|
||||
public BlockException getBlockError() {
|
||||
return blockError;
|
||||
}
|
||||
|
||||
public Entry setBlockError(BlockException blockError) {
|
||||
this.blockError = blockError;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Throwable getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public void setError(Throwable error) {
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get origin {@link Node} of the this {@link Entry}.
|
||||
*
|
||||
* @return origin {@link Node} of the this {@link Entry}, may be null if no origin specified by
|
||||
* {@link ContextUtil#enter(String name, String origin)}.
|
||||
*/
|
||||
public Node getOriginNode() {
|
||||
return originNode;
|
||||
}
|
||||
|
||||
public void setOriginNode(Node originNode) {
|
||||
this.originNode = originNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@code CompletableFuture} since JDK 8, it guarantees specified handler
|
||||
* is invoked when this entry terminated (exited), no matter it's blocked or permitted.
|
||||
* Use it when you did some STATEFUL operations on entries.
|
||||
*
|
||||
* @param handler handler function on the invocation terminates
|
||||
* @since 1.8.0
|
||||
*/
|
||||
public abstract void whenTerminate(BiConsumer<Context, Entry> handler);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel;
|
||||
|
||||
/**
|
||||
* An enum marks resource invocation direction.
|
||||
*
|
||||
* @author jialiang.linjl
|
||||
* @author Yanming Zhou
|
||||
*/
|
||||
public enum EntryType {
|
||||
/**
|
||||
* Inbound traffic
|
||||
*/
|
||||
IN,
|
||||
/**
|
||||
* Outbound traffic
|
||||
*/
|
||||
OUT;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel;
|
||||
|
||||
import com.alibaba.csp.sentinel.init.InitExecutor;
|
||||
|
||||
/**
|
||||
* Sentinel Env. This class will trigger all initialization for Sentinel.
|
||||
*
|
||||
* <p>
|
||||
* NOTE: to prevent deadlocks, other classes' static code block or static field should
|
||||
* NEVER refer to this class.
|
||||
* </p>
|
||||
*
|
||||
* @author jialiang.linjl
|
||||
*/
|
||||
public class Env {
|
||||
|
||||
public static final Sph sph = new CtSph();
|
||||
|
||||
static {
|
||||
// If init fails, the process will exit.
|
||||
InitExecutor.doInit();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel;
|
||||
|
||||
/**
|
||||
* Represents order mismatch of resource entry and resource exit (pair mismatch).
|
||||
*
|
||||
* @author qinan.qn
|
||||
*/
|
||||
public class ErrorEntryFreeException extends RuntimeException {
|
||||
|
||||
public ErrorEntryFreeException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.alibaba.csp.sentinel;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public final class ResourceTypeConstants {
|
||||
|
||||
public static final int COMMON = 0;
|
||||
public static final int COMMON_WEB = 1;
|
||||
public static final int COMMON_RPC = 2;
|
||||
public static final int COMMON_API_GATEWAY = 3;
|
||||
public static final int COMMON_DB_SQL = 4;
|
||||
|
||||
private ResourceTypeConstants() {}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
import com.alibaba.csp.sentinel.slots.system.SystemRule;
|
||||
|
||||
/**
|
||||
* The basic interface for recording statistics and performing rule checking for resources.
|
||||
*
|
||||
* @author qinan.qn
|
||||
* @author jialiang.linjl
|
||||
* @author leyou
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public interface Sph extends SphResourceTypeSupport {
|
||||
|
||||
/**
|
||||
* Record statistics and perform rule checking for the given resource.
|
||||
*
|
||||
* @param name the unique name of the protected resource
|
||||
* @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data).
|
||||
* @throws BlockException if the block criteria is met
|
||||
*/
|
||||
Entry entry(String name) throws BlockException;
|
||||
|
||||
/**
|
||||
* Record statistics and perform rule checking for the given method.
|
||||
*
|
||||
* @param method the protected method
|
||||
* @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data).
|
||||
* @throws BlockException if the block criteria is met
|
||||
*/
|
||||
Entry entry(Method method) throws BlockException;
|
||||
|
||||
/**
|
||||
* Record statistics and perform rule checking for the given method.
|
||||
*
|
||||
* @param method the protected method
|
||||
* @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens)
|
||||
* @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data).
|
||||
* @throws BlockException if the block criteria is met
|
||||
*/
|
||||
Entry entry(Method method, int batchCount) throws BlockException;
|
||||
|
||||
/**
|
||||
* Record statistics and perform rule checking for the given resource.
|
||||
*
|
||||
* @param name the unique string for the resource
|
||||
* @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens)
|
||||
* @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data).
|
||||
* @throws BlockException if the block criteria is met
|
||||
*/
|
||||
Entry entry(String name, int batchCount) throws BlockException;
|
||||
|
||||
/**
|
||||
* Record statistics and perform rule checking for the given method.
|
||||
*
|
||||
* @param method the protected method
|
||||
* @param trafficType the traffic type (inbound, outbound or internal). This is used
|
||||
* to mark whether it can be blocked when the system is unstable,
|
||||
* only inbound traffic could be blocked by {@link SystemRule}
|
||||
* @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data).
|
||||
* @throws BlockException if the block criteria is met
|
||||
*/
|
||||
Entry entry(Method method, EntryType trafficType) throws BlockException;
|
||||
|
||||
/**
|
||||
* Record statistics and perform rule checking for the given resource.
|
||||
*
|
||||
* @param name the unique name for the protected resource
|
||||
* @param trafficType the traffic type (inbound, outbound or internal). This is used
|
||||
* to mark whether it can be blocked when the system is unstable,
|
||||
* only inbound traffic could be blocked by {@link SystemRule}
|
||||
* @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data).
|
||||
* @throws BlockException if the block criteria is met
|
||||
*/
|
||||
Entry entry(String name, EntryType trafficType) throws BlockException;
|
||||
|
||||
/**
|
||||
* Record statistics and perform rule checking for the given method.
|
||||
*
|
||||
* @param method the protected method
|
||||
* @param trafficType the traffic type (inbound, outbound or internal). This is used
|
||||
* to mark whether it can be blocked when the system is unstable,
|
||||
* only inbound traffic could be blocked by {@link SystemRule}
|
||||
* @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens)
|
||||
* @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data).
|
||||
* @throws BlockException if the block criteria is met
|
||||
*/
|
||||
Entry entry(Method method, EntryType trafficType, int batchCount) throws BlockException;
|
||||
|
||||
/**
|
||||
* Record statistics and perform rule checking for the given resource.
|
||||
*
|
||||
* @param name the unique name for the protected resource
|
||||
* @param trafficType the traffic type (inbound, outbound or internal). This is used
|
||||
* to mark whether it can be blocked when the system is unstable,
|
||||
* only inbound traffic could be blocked by {@link SystemRule}
|
||||
* @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens)
|
||||
* @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data).
|
||||
* @throws BlockException if the block criteria is met
|
||||
*/
|
||||
Entry entry(String name, EntryType trafficType, int batchCount) throws BlockException;
|
||||
|
||||
/**
|
||||
* Record statistics and perform rule checking for the given resource.
|
||||
*
|
||||
* @param method the protected method
|
||||
* @param trafficType the traffic type (inbound, outbound or internal). This is used
|
||||
* to mark whether it can be blocked when the system is unstable,
|
||||
* only inbound traffic could be blocked by {@link SystemRule}
|
||||
* @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens)
|
||||
* @param args parameters of the method for flow control or customized slots
|
||||
* @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data).
|
||||
* @throws BlockException if the block criteria is met
|
||||
*/
|
||||
Entry entry(Method method, EntryType trafficType, int batchCount, Object... args) throws BlockException;
|
||||
|
||||
/**
|
||||
* Record statistics and perform rule checking for the given resource.
|
||||
*
|
||||
* @param name the unique name for the protected resource
|
||||
* @param trafficType the traffic type (inbound, outbound or internal). This is used
|
||||
* to mark whether it can be blocked when the system is unstable,
|
||||
* only inbound traffic could be blocked by {@link SystemRule}
|
||||
* @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens)
|
||||
* @param args args for parameter flow control or customized slots
|
||||
* @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data)
|
||||
* @throws BlockException if the block criteria is met
|
||||
*/
|
||||
Entry entry(String name, EntryType trafficType, int batchCount, Object... args) throws BlockException;
|
||||
|
||||
/**
|
||||
* Create a protected asynchronous resource.
|
||||
*
|
||||
* @param name the unique name for the protected resource
|
||||
* @param trafficType the traffic type (inbound, outbound or internal). This is used
|
||||
* to mark whether it can be blocked when the system is unstable,
|
||||
* only inbound traffic could be blocked by {@link SystemRule}
|
||||
* @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens)
|
||||
* @param args args for parameter flow control or customized slots
|
||||
* @return created asynchronous entry
|
||||
* @throws BlockException if the block criteria is met
|
||||
* @since 0.2.0
|
||||
*/
|
||||
AsyncEntry asyncEntry(String name, EntryType trafficType, int batchCount, Object... args) throws BlockException;
|
||||
|
||||
/**
|
||||
* Create a protected resource with priority.
|
||||
*
|
||||
* @param name the unique name for the protected resource
|
||||
* @param trafficType the traffic type (inbound, outbound or internal). This is used
|
||||
* to mark whether it can be blocked when the system is unstable,
|
||||
* only inbound traffic could be blocked by {@link SystemRule}
|
||||
* @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens)
|
||||
* @param prioritized whether the entry is prioritized
|
||||
* @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data)
|
||||
* @throws BlockException if the block criteria is met
|
||||
* @since 1.4.0
|
||||
*/
|
||||
Entry entryWithPriority(String name, EntryType trafficType, int batchCount, boolean prioritized)
|
||||
throws BlockException;
|
||||
|
||||
/**
|
||||
* Create a protected resource with priority.
|
||||
*
|
||||
* @param name the unique name for the protected resource
|
||||
* @param trafficType the traffic type (inbound, outbound or internal). This is used
|
||||
* to mark whether it can be blocked when the system is unstable,
|
||||
* only inbound traffic could be blocked by {@link SystemRule}
|
||||
* @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens)
|
||||
* @param prioritized whether the entry is prioritized
|
||||
* @param args args for parameter flow control or customized slots
|
||||
* @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data)
|
||||
* @throws BlockException if the block criteria is met
|
||||
* @since 1.5.0
|
||||
*/
|
||||
Entry entryWithPriority(String name, EntryType trafficType, int batchCount, boolean prioritized, Object... args)
|
||||
throws BlockException;
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
import com.alibaba.csp.sentinel.context.ContextUtil;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
import com.alibaba.csp.sentinel.slots.block.Rule;
|
||||
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
|
||||
import com.alibaba.csp.sentinel.slots.system.SystemRule;
|
||||
import com.alibaba.csp.sentinel.slots.system.SystemRuleManager;
|
||||
|
||||
/**
|
||||
* Conceptually, physical or logical resource that need protection should be
|
||||
* surrounded by an entry. The requests to this resource will be blocked if any
|
||||
* criteria is met, eg. when any {@link Rule}'s threshold is exceeded. Once blocked,
|
||||
* {@link SphO}#entry() will return false.
|
||||
*
|
||||
* <p>
|
||||
* To configure the criteria, we can use <code>XXXRuleManager.loadRules()</code> to add rules. eg.
|
||||
* {@link FlowRuleManager#loadRules(List)}, {@link DegradeRuleManager#loadRules(List)},
|
||||
* {@link SystemRuleManager#loadRules(List)}.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Following code is an example. {@code "abc"} represent a unique name for the
|
||||
* protected resource:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* public void foo() {
|
||||
* if (SphO.entry("abc")) {
|
||||
* try {
|
||||
* // business logic
|
||||
* } finally {
|
||||
* SphO.exit(); // must exit()
|
||||
* }
|
||||
* } else {
|
||||
* // failed to enter the protected resource.
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* Make sure {@code SphO.entry()} and {@link SphO#exit()} be paired in the same thread,
|
||||
* otherwise {@link ErrorEntryFreeException} will be thrown.
|
||||
*
|
||||
* @author jialiang.linjl
|
||||
* @author leyou
|
||||
* @author Eric Zhao
|
||||
* @see SphU
|
||||
*/
|
||||
public class SphO {
|
||||
|
||||
private static final Object[] OBJECTS0 = new Object[0];
|
||||
|
||||
/**
|
||||
* Record statistics and perform rule checking for the given resource.
|
||||
*
|
||||
* @param name the unique name of the protected resource
|
||||
* @return true if no rule's threshold is exceeded, otherwise return false.
|
||||
*/
|
||||
public static boolean entry(String name) {
|
||||
return entry(name, EntryType.OUT, 1, OBJECTS0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checking all {@link Rule}s about the protected method.
|
||||
*
|
||||
* @param method the protected method
|
||||
* @return true if no rule's threshold is exceeded, otherwise return false.
|
||||
*/
|
||||
public static boolean entry(Method method) {
|
||||
return entry(method, EntryType.OUT, 1, OBJECTS0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checking all {@link Rule}s about the protected method.
|
||||
*
|
||||
* @param method the protected method
|
||||
* @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens)
|
||||
* @return true if no rule's threshold is exceeded, otherwise return false.
|
||||
*/
|
||||
public static boolean entry(Method method, int batchCount) {
|
||||
return entry(method, EntryType.OUT, batchCount, OBJECTS0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record statistics and perform rule checking for the given resource.
|
||||
*
|
||||
* @param name the unique string for the resource
|
||||
* @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens)
|
||||
* @return true if no rule's threshold is exceeded, otherwise return false.
|
||||
*/
|
||||
public static boolean entry(String name, int batchCount) {
|
||||
return entry(name, EntryType.OUT, batchCount, OBJECTS0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checking all {@link Rule}s about the protected method.
|
||||
*
|
||||
* @param method the protected method
|
||||
* @param type the resource is an inbound or an outbound method. This is used
|
||||
* to mark whether it can be blocked when the system is unstable,
|
||||
* only inbound traffic could be blocked by {@link SystemRule}
|
||||
* @return true if no rule's threshold is exceeded, otherwise return false.
|
||||
*/
|
||||
public static boolean entry(Method method, EntryType type) {
|
||||
return entry(method, type, 1, OBJECTS0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record statistics and perform rule checking for the given resource.
|
||||
*
|
||||
* @param name the unique name for the protected resource
|
||||
* @param type the resource is an inbound or an outbound method. This is used
|
||||
* to mark whether it can be blocked when the system is unstable,
|
||||
* only inbound traffic could be blocked by {@link SystemRule}
|
||||
* @return true if no rule's threshold is exceeded, otherwise return false.
|
||||
*/
|
||||
public static boolean entry(String name, EntryType type) {
|
||||
return entry(name, type, 1, OBJECTS0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checking all {@link Rule}s about the protected method.
|
||||
*
|
||||
* @param method the protected method
|
||||
* @param type the resource is an inbound or an outbound method. This is used
|
||||
* to mark whether it can be blocked when the system is unstable,
|
||||
* only inbound traffic could be blocked by {@link SystemRule}
|
||||
* @param count the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens)
|
||||
* @return true if no rule's threshold is exceeded, otherwise return false.
|
||||
*/
|
||||
public static boolean entry(Method method, EntryType type, int count) {
|
||||
return entry(method, type, count, OBJECTS0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record statistics and perform rule checking for the given resource.
|
||||
*
|
||||
* @param name the unique name for the protected resource
|
||||
* @param type the resource is an inbound or an outbound method. This is used
|
||||
* to mark whether it can be blocked when the system is unstable,
|
||||
* only inbound traffic could be blocked by {@link SystemRule}
|
||||
* @param count the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens)
|
||||
* @return true if no rule's threshold is exceeded, otherwise return false.
|
||||
*/
|
||||
public static boolean entry(String name, EntryType type, int count) {
|
||||
return entry(name, type, count, OBJECTS0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record statistics and perform rule checking for the given resource.
|
||||
*
|
||||
* @param name the unique name for the protected resource
|
||||
* @param trafficType the traffic type (inbound, outbound or internal). This is used
|
||||
* to mark whether it can be blocked when the system is unstable,
|
||||
* only inbound traffic could be blocked by {@link SystemRule}
|
||||
* @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens)
|
||||
* @param args args for parameter flow control or customized slots
|
||||
* @return true if no rule's threshold is exceeded, otherwise return false.
|
||||
*/
|
||||
public static boolean entry(String name, EntryType trafficType, int batchCount, Object... args) {
|
||||
try {
|
||||
Env.sph.entry(name, trafficType, batchCount, args);
|
||||
} catch (BlockException e) {
|
||||
return false;
|
||||
} catch (Throwable e) {
|
||||
RecordLog.warn("SphO fatal error", e);
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record statistics and perform rule checking for the given method resource.
|
||||
*
|
||||
* @param method the protected method
|
||||
* @param trafficType the traffic type (inbound, outbound or internal). This is used
|
||||
* to mark whether it can be blocked when the system is unstable,
|
||||
* only inbound traffic could be blocked by {@link SystemRule}
|
||||
* @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens)
|
||||
* @param args args for parameter flow control or customized slots
|
||||
* @return true if no rule's threshold is exceeded, otherwise return false.
|
||||
*/
|
||||
public static boolean entry(Method method, EntryType trafficType, int batchCount, Object... args) {
|
||||
try {
|
||||
Env.sph.entry(method, trafficType, batchCount, args);
|
||||
} catch (BlockException e) {
|
||||
return false;
|
||||
} catch (Throwable e) {
|
||||
RecordLog.warn("SphO fatal error", e);
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void exit(int count, Object... args) {
|
||||
ContextUtil.getContext().getCurEntry().exit(count, args);
|
||||
}
|
||||
|
||||
public static void exit(int count) {
|
||||
ContextUtil.getContext().getCurEntry().exit(count, OBJECTS0);
|
||||
}
|
||||
|
||||
public static void exit() {
|
||||
exit(1, OBJECTS0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.alibaba.csp.sentinel;
|
||||
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
import com.alibaba.csp.sentinel.slots.system.SystemRule;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public interface SphResourceTypeSupport {
|
||||
|
||||
/**
|
||||
* Record statistics and perform rule checking for the given resource with provided classification.
|
||||
*
|
||||
* @param name the unique name of the protected resource
|
||||
* @param resourceType the classification of the resource
|
||||
* @param trafficType the traffic type (inbound, outbound or internal). This is used
|
||||
* to mark whether it can be blocked when the system is unstable,
|
||||
* only inbound traffic could be blocked by {@link SystemRule}
|
||||
* @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens)
|
||||
* @param args args for parameter flow control or customized slots
|
||||
* @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data)
|
||||
* @throws BlockException if the block criteria is met
|
||||
*/
|
||||
Entry entryWithType(String name, int resourceType, EntryType trafficType, int batchCount, Object[] args)
|
||||
throws BlockException;
|
||||
|
||||
/**
|
||||
* Record statistics and perform rule checking for the given resource with the provided classification.
|
||||
*
|
||||
* @param name the unique name of the protected resource
|
||||
* @param resourceType classification of the resource (e.g. Web or RPC)
|
||||
* @param trafficType the traffic type (inbound, outbound or internal). This is used
|
||||
* to mark whether it can be blocked when the system is unstable,
|
||||
* only inbound traffic could be blocked by {@link SystemRule}
|
||||
* @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens)
|
||||
* @param prioritized whether the entry is prioritized
|
||||
* @param args args for parameter flow control or customized slots
|
||||
* @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data)
|
||||
* @throws BlockException if the block criteria is met
|
||||
*/
|
||||
Entry entryWithType(String name, int resourceType, EntryType trafficType, int batchCount, boolean prioritized,
|
||||
Object[] args) throws BlockException;
|
||||
|
||||
/**
|
||||
* Record statistics and perform rule checking for the given resource that indicates an async invocation.
|
||||
*
|
||||
* @param name the unique name for the protected resource
|
||||
* @param resourceType classification of the resource (e.g. Web or RPC)
|
||||
* @param trafficType the traffic type (inbound, outbound or internal). This is used
|
||||
* to mark whether it can be blocked when the system is unstable,
|
||||
* only inbound traffic could be blocked by {@link SystemRule}
|
||||
* @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens)
|
||||
* @param prioritized whether the entry is prioritized
|
||||
* @param args args for parameter flow control or customized slots
|
||||
* @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data)
|
||||
* @throws BlockException if the block criteria is met
|
||||
*/
|
||||
AsyncEntry asyncEntryWithType(String name, int resourceType, EntryType trafficType, int batchCount,
|
||||
boolean prioritized,
|
||||
Object[] args) throws BlockException;
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
import com.alibaba.csp.sentinel.slots.block.Rule;
|
||||
import com.alibaba.csp.sentinel.slots.system.SystemRule;
|
||||
|
||||
/**
|
||||
* <p>The fundamental Sentinel API for recording statistics and performing rule checking for resources.</p>
|
||||
* <p>
|
||||
* Conceptually, physical or logical resource that need protection should be
|
||||
* surrounded by an entry. The requests to this resource will be blocked if any
|
||||
* criteria is met, eg. when any {@link Rule}'s threshold is exceeded. Once blocked,
|
||||
* a {@link BlockException} will be thrown.
|
||||
* </p>
|
||||
* <p>
|
||||
* To configure the criteria, we can use <code>XxxRuleManager.loadRules()</code> to load rules.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Following code is an example, {@code "abc"} represent a unique name for the
|
||||
* protected resource:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* public void foo() {
|
||||
* Entry entry = null;
|
||||
* try {
|
||||
* entry = SphU.entry("abc");
|
||||
* // resource that need protection
|
||||
* } catch (BlockException blockException) {
|
||||
* // when goes there, it is blocked
|
||||
* // add blocked handle logic here
|
||||
* } catch (Throwable bizException) {
|
||||
* // business exception
|
||||
* Tracer.trace(bizException);
|
||||
* } finally {
|
||||
* // ensure finally be executed
|
||||
* if (entry != null){
|
||||
* entry.exit();
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* Make sure {@code SphU.entry()} and {@link Entry#exit()} be paired in the same thread,
|
||||
* otherwise {@link ErrorEntryFreeException} will be thrown.
|
||||
* </p>
|
||||
*
|
||||
* @author jialiang.linjl
|
||||
* @author Eric Zhao
|
||||
* @see SphO
|
||||
*/
|
||||
public class SphU {
|
||||
|
||||
private static final Object[] OBJECTS0 = new Object[0];
|
||||
|
||||
private SphU() {}
|
||||
|
||||
/**
|
||||
* Record statistics and perform rule checking for the given resource.
|
||||
*
|
||||
* @param name the unique name of the protected resource
|
||||
* @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data)
|
||||
* @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules)
|
||||
*/
|
||||
public static Entry entry(String name) throws BlockException {
|
||||
return Env.sph.entry(name, EntryType.OUT, 1, OBJECTS0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checking all {@link Rule}s about the protected method.
|
||||
*
|
||||
* @param method the protected method
|
||||
* @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data)
|
||||
* @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules)
|
||||
*/
|
||||
public static Entry entry(Method method) throws BlockException {
|
||||
return Env.sph.entry(method, EntryType.OUT, 1, OBJECTS0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checking all {@link Rule}s about the protected method.
|
||||
*
|
||||
* @param method the protected method
|
||||
* @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens)
|
||||
* @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data)
|
||||
* @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules)
|
||||
*/
|
||||
public static Entry entry(Method method, int batchCount) throws BlockException {
|
||||
return Env.sph.entry(method, EntryType.OUT, batchCount, OBJECTS0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record statistics and perform rule checking for the given resource.
|
||||
*
|
||||
* @param name the unique string for the resource
|
||||
* @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens)
|
||||
* @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data)
|
||||
* @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules)
|
||||
*/
|
||||
public static Entry entry(String name, int batchCount) throws BlockException {
|
||||
return Env.sph.entry(name, EntryType.OUT, batchCount, OBJECTS0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checking all {@link Rule}s about the protected method.
|
||||
*
|
||||
* @param method the protected method
|
||||
* @param trafficType the traffic type (inbound, outbound or internal). This is used
|
||||
* to mark whether it can be blocked when the system is unstable,
|
||||
* only inbound traffic could be blocked by {@link SystemRule}
|
||||
* @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules)
|
||||
*/
|
||||
public static Entry entry(Method method, EntryType trafficType) throws BlockException {
|
||||
return Env.sph.entry(method, trafficType, 1, OBJECTS0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record statistics and perform rule checking for the given resource.
|
||||
*
|
||||
* @param name the unique name for the protected resource
|
||||
* @param trafficType the traffic type (inbound, outbound or internal). This is used
|
||||
* to mark whether it can be blocked when the system is unstable,
|
||||
* only inbound traffic could be blocked by {@link SystemRule}
|
||||
* @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules)
|
||||
*/
|
||||
public static Entry entry(String name, EntryType trafficType) throws BlockException {
|
||||
return Env.sph.entry(name, trafficType, 1, OBJECTS0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checking all {@link Rule}s about the protected method.
|
||||
*
|
||||
* @param method the protected method
|
||||
* @param trafficType the traffic type (inbound, outbound or internal). This is used
|
||||
* to mark whether it can be blocked when the system is unstable,
|
||||
* only inbound traffic could be blocked by {@link SystemRule}
|
||||
* @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens)
|
||||
* @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules)
|
||||
*/
|
||||
public static Entry entry(Method method, EntryType trafficType, int batchCount) throws BlockException {
|
||||
return Env.sph.entry(method, trafficType, batchCount, OBJECTS0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record statistics and perform rule checking for the given resource.
|
||||
*
|
||||
* @param name the unique name for the protected resource
|
||||
* @param trafficType the traffic type (inbound, outbound or internal). This is used
|
||||
* to mark whether it can be blocked when the system is unstable,
|
||||
* only inbound traffic could be blocked by {@link SystemRule}
|
||||
* @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens)
|
||||
* @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data)
|
||||
* @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules)
|
||||
*/
|
||||
public static Entry entry(String name, EntryType trafficType, int batchCount) throws BlockException {
|
||||
return Env.sph.entry(name, trafficType, batchCount, OBJECTS0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checking all {@link Rule}s about the protected method.
|
||||
*
|
||||
* @param method the protected method
|
||||
* @param trafficType the traffic type (inbound, outbound or internal). This is used
|
||||
* to mark whether it can be blocked when the system is unstable,
|
||||
* only inbound traffic could be blocked by {@link SystemRule}
|
||||
* @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens)
|
||||
* @param args args for parameter flow control or customized slots
|
||||
* @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data)
|
||||
* @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules)
|
||||
*/
|
||||
public static Entry entry(Method method, EntryType trafficType, int batchCount, Object... args)
|
||||
throws BlockException {
|
||||
return Env.sph.entry(method, trafficType, batchCount, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record statistics and perform rule checking for the given resource.
|
||||
*
|
||||
* @param name the unique name for the protected resource
|
||||
* @param trafficType the traffic type (inbound, outbound or internal). This is used
|
||||
* to mark whether it can be blocked when the system is unstable,
|
||||
* only inbound traffic could be blocked by {@link SystemRule}
|
||||
* @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens)
|
||||
* @param args args for parameter flow control
|
||||
* @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules)
|
||||
*/
|
||||
public static Entry entry(String name, EntryType trafficType, int batchCount, Object... args)
|
||||
throws BlockException {
|
||||
return Env.sph.entry(name, trafficType, batchCount, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record statistics and check all rules of the resource that indicates an async invocation.
|
||||
*
|
||||
* @param name the unique name of the protected resource
|
||||
* @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules)
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public static AsyncEntry asyncEntry(String name) throws BlockException {
|
||||
return Env.sph.asyncEntry(name, EntryType.OUT, 1, OBJECTS0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record statistics and check all rules of the resource that indicates an async invocation.
|
||||
*
|
||||
* @param name the unique name for the protected resource
|
||||
* @param trafficType the traffic type (inbound, outbound or internal). This is used
|
||||
* to mark whether it can be blocked when the system is unstable,
|
||||
* only inbound traffic could be blocked by {@link SystemRule}
|
||||
* @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data)
|
||||
* @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules)
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public static AsyncEntry asyncEntry(String name, EntryType trafficType) throws BlockException {
|
||||
return Env.sph.asyncEntry(name, trafficType, 1, OBJECTS0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record statistics and check all rules of the resource that indicates an async invocation.
|
||||
*
|
||||
* @param name the unique name for the protected resource
|
||||
* @param trafficType the traffic type (inbound, outbound or internal). This is used
|
||||
* to mark whether it can be blocked when the system is unstable,
|
||||
* only inbound traffic could be blocked by {@link SystemRule}
|
||||
* @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens)
|
||||
* @param args args for parameter flow control
|
||||
* @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data)
|
||||
* @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules)
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public static AsyncEntry asyncEntry(String name, EntryType trafficType, int batchCount, Object... args)
|
||||
throws BlockException {
|
||||
return Env.sph.asyncEntry(name, trafficType, batchCount, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record statistics and perform rule checking for the given resource. The entry is prioritized.
|
||||
*
|
||||
* @param name the unique name for the protected resource
|
||||
* @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules)
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public static Entry entryWithPriority(String name) throws BlockException {
|
||||
return Env.sph.entryWithPriority(name, EntryType.OUT, 1, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record statistics and perform rule checking for the given resource. The entry is prioritized.
|
||||
*
|
||||
* @param name the unique name for the protected resource
|
||||
* @param trafficType the traffic type (inbound, outbound or internal). This is used
|
||||
* to mark whether it can be blocked when the system is unstable,
|
||||
* only inbound traffic could be blocked by {@link SystemRule}
|
||||
* @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data)
|
||||
* @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules)
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public static Entry entryWithPriority(String name, EntryType trafficType) throws BlockException {
|
||||
return Env.sph.entryWithPriority(name, trafficType, 1, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record statistics and perform rule checking for the given resource.
|
||||
*
|
||||
* @param name the unique name for the protected resource
|
||||
* @param resourceType classification of the resource (e.g. Web or RPC)
|
||||
* @param trafficType the traffic type (inbound, outbound or internal). This is used
|
||||
* to mark whether it can be blocked when the system is unstable,
|
||||
* only inbound traffic could be blocked by {@link SystemRule}
|
||||
* @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data)
|
||||
* @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules)
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public static Entry entry(String name, int resourceType, EntryType trafficType) throws BlockException {
|
||||
return Env.sph.entryWithType(name, resourceType, trafficType, 1, OBJECTS0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record statistics and perform rule checking for the given resource.
|
||||
*
|
||||
* @param name the unique name for the protected resource
|
||||
* @param trafficType the traffic type (inbound, outbound or internal). This is used
|
||||
* to mark whether it can be blocked when the system is unstable,
|
||||
* only inbound traffic could be blocked by {@link SystemRule}
|
||||
* @param resourceType classification of the resource (e.g. Web or RPC)
|
||||
* @param args args for parameter flow control or customized slots
|
||||
* @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data)
|
||||
* @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules)
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public static Entry entry(String name, int resourceType, EntryType trafficType, Object[] args)
|
||||
throws BlockException {
|
||||
return Env.sph.entryWithType(name, resourceType, trafficType, 1, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record statistics and perform rule checking for the given resource that indicates an async invocation.
|
||||
*
|
||||
* @param name the unique name for the protected resource
|
||||
* @param trafficType the traffic type (inbound, outbound or internal). This is used
|
||||
* to mark whether it can be blocked when the system is unstable,
|
||||
* only inbound traffic could be blocked by {@link SystemRule}
|
||||
* @param resourceType classification of the resource (e.g. Web or RPC)
|
||||
* @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data)
|
||||
* @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules)
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public static AsyncEntry asyncEntry(String name, int resourceType, EntryType trafficType)
|
||||
throws BlockException {
|
||||
return Env.sph.asyncEntryWithType(name, resourceType, trafficType, 1, false, OBJECTS0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record statistics and perform rule checking for the given resource that indicates an async invocation.
|
||||
*
|
||||
* @param name the unique name for the protected resource
|
||||
* @param trafficType the traffic type (inbound, outbound or internal). This is used
|
||||
* to mark whether it can be blocked when the system is unstable,
|
||||
* only inbound traffic could be blocked by {@link SystemRule}
|
||||
* @param resourceType classification of the resource (e.g. Web or RPC)
|
||||
* @param args args for parameter flow control or customized slots
|
||||
* @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data)
|
||||
* @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules)
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public static AsyncEntry asyncEntry(String name, int resourceType, EntryType trafficType, Object[] args)
|
||||
throws BlockException {
|
||||
return Env.sph.asyncEntryWithType(name, resourceType, trafficType, 1, false, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record statistics and perform rule checking for the given resource that indicates an async invocation.
|
||||
*
|
||||
* @param name the unique name for the protected resource
|
||||
* @param trafficType the traffic type (inbound, outbound or internal). This is used
|
||||
* to mark whether it can be blocked when the system is unstable,
|
||||
* only inbound traffic could be blocked by {@link SystemRule}
|
||||
* @param resourceType classification of the resource (e.g. Web or RPC)
|
||||
* @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens)
|
||||
* @param args args for parameter flow control or customized slots
|
||||
* @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data)
|
||||
* @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules)
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public static AsyncEntry asyncEntry(String name, int resourceType, EntryType trafficType, int batchCount,
|
||||
Object[] args) throws BlockException {
|
||||
return Env.sph.asyncEntryWithType(name, resourceType, trafficType, batchCount, false, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel;
|
||||
|
||||
import com.alibaba.csp.sentinel.context.Context;
|
||||
import com.alibaba.csp.sentinel.context.ContextUtil;
|
||||
import com.alibaba.csp.sentinel.context.NullContext;
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||
import com.alibaba.csp.sentinel.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* This class is used to record other exceptions except block exception.
|
||||
*
|
||||
* @author jialiang.linjl
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public class Tracer {
|
||||
|
||||
protected static Class<? extends Throwable>[] traceClasses;
|
||||
protected static Class<? extends Throwable>[] ignoreClasses;
|
||||
|
||||
protected static Predicate<Throwable> exceptionPredicate;
|
||||
|
||||
protected Tracer() {}
|
||||
|
||||
/**
|
||||
* Trace provided {@link Throwable} to the resource entry in current context.
|
||||
*
|
||||
* @param e exception to record
|
||||
*/
|
||||
public static void trace(Throwable e) {
|
||||
traceContext(e, ContextUtil.getContext());
|
||||
}
|
||||
|
||||
/**
|
||||
* Trace provided {@link Throwable} to current entry in current context.
|
||||
*
|
||||
* @param e exception to record
|
||||
* @param count exception count to add
|
||||
*/
|
||||
@Deprecated
|
||||
public static void trace(Throwable e, int count) {
|
||||
traceContext(e, count, ContextUtil.getContext());
|
||||
}
|
||||
|
||||
/**
|
||||
* Trace provided {@link Throwable} to current entry of given entrance context.
|
||||
*
|
||||
* @param e exception to record
|
||||
* @param context target entrance context
|
||||
* @since 1.8.0
|
||||
*/
|
||||
public static void traceContext(Throwable e, Context context) {
|
||||
if (!shouldTrace(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (context == null || context instanceof NullContext) {
|
||||
return;
|
||||
}
|
||||
traceEntryInternal(e, context.getCurEntry());
|
||||
}
|
||||
|
||||
/**
|
||||
* Trace provided {@link Throwable} and add exception count to current entry in provided context.
|
||||
*
|
||||
* @param e exception to record
|
||||
* @param count exception count to add
|
||||
* @since 1.4.2
|
||||
*/
|
||||
@Deprecated
|
||||
public static void traceContext(Throwable e, int count, Context context) {
|
||||
if (!shouldTrace(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (context == null || context instanceof NullContext) {
|
||||
return;
|
||||
}
|
||||
traceEntryInternal(e, context.getCurEntry());
|
||||
}
|
||||
|
||||
/**
|
||||
* Trace provided {@link Throwable} to the given resource entry.
|
||||
*
|
||||
* @param e exception to record
|
||||
* @since 1.4.2
|
||||
*/
|
||||
public static void traceEntry(Throwable e, Entry entry) {
|
||||
if (!shouldTrace(e)) {
|
||||
return;
|
||||
}
|
||||
traceEntryInternal(e, entry);
|
||||
}
|
||||
|
||||
private static void traceEntryInternal(/*@NeedToTrace*/ Throwable e, Entry entry) {
|
||||
if (entry == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
entry.setError(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set exception to trace. If not set, all Exception except for {@link BlockException} will be traced.
|
||||
* <p>
|
||||
* Note that if both {@link #setExceptionsToIgnore(Class[])} and this method is set,
|
||||
* the ExceptionsToIgnore will be of higher precedence.
|
||||
* </p>
|
||||
*
|
||||
* @param traceClasses the list of exception classes to trace.
|
||||
* @since 1.6.1
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static void setExceptionsToTrace(Class<? extends Throwable>... traceClasses) {
|
||||
checkNotNull(traceClasses);
|
||||
Tracer.traceClasses = traceClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get exception classes to trace.
|
||||
*
|
||||
* @return an array of exception classes to trace.
|
||||
* @since 1.6.1
|
||||
*/
|
||||
public static Class<? extends Throwable>[] getExceptionsToTrace() {
|
||||
return traceClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set exceptions to ignore. if not set, all Exception except for {@link BlockException} will be traced.
|
||||
* <p>
|
||||
* Note that if both {@link #setExceptionsToTrace(Class[])} and this method is set,
|
||||
* the ExceptionsToIgnore will be of higher precedence.
|
||||
* </p>
|
||||
*
|
||||
* @param ignoreClasses the list of exception classes to ignore.
|
||||
* @since 1.6.1
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static void setExceptionsToIgnore(Class<? extends Throwable>... ignoreClasses) {
|
||||
checkNotNull(ignoreClasses);
|
||||
Tracer.ignoreClasses = ignoreClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get exception classes to ignore.
|
||||
*
|
||||
* @return an array of exception classes to ignore.
|
||||
* @since 1.6.1
|
||||
*/
|
||||
public static Class<? extends Throwable>[] getExceptionsToIgnore() {
|
||||
return ignoreClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get exception predicate
|
||||
* @return the exception predicate.
|
||||
*/
|
||||
public static Predicate<? extends Throwable> getExceptionPredicate() {
|
||||
return exceptionPredicate;
|
||||
}
|
||||
|
||||
/**
|
||||
* set an exception predicate which indicates the exception should be traced(return true) or ignored(return false)
|
||||
* except for {@link BlockException}
|
||||
* @param exceptionPredicate the exception predicate
|
||||
*/
|
||||
public static void setExceptionPredicate(Predicate<Throwable> exceptionPredicate) {
|
||||
AssertUtil.notNull(exceptionPredicate, "exception predicate must not be null");
|
||||
Tracer.exceptionPredicate = exceptionPredicate;
|
||||
}
|
||||
|
||||
private static void checkNotNull(Class<? extends Throwable>[] classes) {
|
||||
AssertUtil.notNull(classes, "trace or ignore classes must not be null");
|
||||
for (Class<? extends Throwable> clazz : classes) {
|
||||
AssertUtil.notNull(clazz, "trace or ignore classes must not be null");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the throwable should be traced.
|
||||
*
|
||||
* @param t the throwable to check.
|
||||
* @return true if the throwable should be traced, else return false.
|
||||
*/
|
||||
protected static boolean shouldTrace(Throwable t) {
|
||||
if (t == null || t instanceof BlockException) {
|
||||
return false;
|
||||
}
|
||||
if (exceptionPredicate != null) {
|
||||
return exceptionPredicate.test(t);
|
||||
}
|
||||
|
||||
if (ignoreClasses != null) {
|
||||
for (Class<? extends Throwable> clazz : ignoreClasses) {
|
||||
if (clazz != null && clazz.isAssignableFrom(t.getClass())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (traceClasses != null) {
|
||||
for (Class<? extends Throwable> clazz : traceClasses) {
|
||||
if (clazz != null && clazz.isAssignableFrom(t.getClass())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 1999-2020 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.annotation;
|
||||
|
||||
import com.alibaba.csp.sentinel.EntryType;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* The annotation indicates a definition of Sentinel resource.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @author zhaoyuguang
|
||||
* @since 0.1.1
|
||||
*/
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface SentinelResource {
|
||||
|
||||
/**
|
||||
* @return name of the Sentinel resource
|
||||
*/
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* @return the entry type (inbound or outbound), outbound by default
|
||||
*/
|
||||
EntryType entryType() default EntryType.OUT;
|
||||
|
||||
/**
|
||||
* @return the classification (type) of the resource
|
||||
* @since 1.7.0
|
||||
*/
|
||||
int resourceType() default 0;
|
||||
|
||||
/**
|
||||
* @return name of the block exception function, empty by default
|
||||
*/
|
||||
String blockHandler() default "";
|
||||
|
||||
/**
|
||||
* The {@code blockHandler} is located in the same class with the original method by default.
|
||||
* However, if some methods share the same signature and intend to set the same block handler,
|
||||
* then users can set the class where the block handler exists. Note that the block handler method
|
||||
* must be static.
|
||||
*
|
||||
* @return the class where the block handler exists, should not provide more than one classes
|
||||
*/
|
||||
Class<?>[] blockHandlerClass() default {};
|
||||
|
||||
/**
|
||||
* @return name of the fallback function, empty by default
|
||||
*/
|
||||
String fallback() default "";
|
||||
|
||||
/**
|
||||
* The {@code defaultFallback} is used as the default universal fallback method.
|
||||
* It should not accept any parameters, and the return type should be compatible
|
||||
* with the original method.
|
||||
*
|
||||
* @return name of the default fallback method, empty by default
|
||||
* @since 1.6.0
|
||||
*/
|
||||
String defaultFallback() default "";
|
||||
|
||||
/**
|
||||
* The {@code fallback} is located in the same class with the original method by default.
|
||||
* However, if some methods share the same signature and intend to set the same fallback,
|
||||
* then users can set the class where the fallback function exists. Note that the shared fallback method
|
||||
* must be static.
|
||||
*
|
||||
* @return the class where the fallback method is located (only single class)
|
||||
* @since 1.6.0
|
||||
*/
|
||||
Class<?>[] fallbackClass() default {};
|
||||
|
||||
/**
|
||||
* @return the list of exception classes to trace, {@link Throwable} by default
|
||||
* @since 1.5.1
|
||||
*/
|
||||
Class<? extends Throwable>[] exceptionsToTrace() default {Throwable.class};
|
||||
|
||||
/**
|
||||
* Indicates the exceptions to be ignored. Note that {@code exceptionsToTrace} should
|
||||
* not appear with {@code exceptionsToIgnore} at the same time, or {@code exceptionsToIgnore}
|
||||
* will be of higher precedence.
|
||||
*
|
||||
* @return the list of exception classes to ignore, empty by default
|
||||
* @since 1.6.0
|
||||
*/
|
||||
Class<? extends Throwable>[] exceptionsToIgnore() default {};
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.cluster;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.client.ClusterTokenClient;
|
||||
import com.alibaba.csp.sentinel.cluster.client.TokenClientProvider;
|
||||
import com.alibaba.csp.sentinel.cluster.server.EmbeddedClusterTokenServer;
|
||||
import com.alibaba.csp.sentinel.cluster.server.EmbeddedClusterTokenServerProvider;
|
||||
import com.alibaba.csp.sentinel.init.InitExecutor;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.property.DynamicSentinelProperty;
|
||||
import com.alibaba.csp.sentinel.property.PropertyListener;
|
||||
import com.alibaba.csp.sentinel.property.SentinelProperty;
|
||||
import com.alibaba.csp.sentinel.util.TimeUtil;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Global state manager for Sentinel cluster.
|
||||
* This enables switching between cluster token client and server mode.
|
||||
* </p>
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public final class ClusterStateManager {
|
||||
|
||||
public static final int CLUSTER_CLIENT = 0;
|
||||
public static final int CLUSTER_SERVER = 1;
|
||||
public static final int CLUSTER_NOT_STARTED = -1;
|
||||
|
||||
private static volatile int mode = CLUSTER_NOT_STARTED;
|
||||
private static volatile long lastModified = -1;
|
||||
|
||||
private static volatile SentinelProperty<Integer> stateProperty = new DynamicSentinelProperty<Integer>();
|
||||
private static final PropertyListener<Integer> PROPERTY_LISTENER = new ClusterStatePropertyListener();
|
||||
|
||||
static {
|
||||
InitExecutor.doInit();
|
||||
stateProperty.addListener(PROPERTY_LISTENER);
|
||||
}
|
||||
|
||||
public static void registerProperty(SentinelProperty<Integer> property) {
|
||||
synchronized (PROPERTY_LISTENER) {
|
||||
RecordLog.info("[ClusterStateManager] Registering new property to cluster state manager");
|
||||
stateProperty.removeListener(PROPERTY_LISTENER);
|
||||
property.addListener(PROPERTY_LISTENER);
|
||||
stateProperty = property;
|
||||
}
|
||||
}
|
||||
|
||||
public static int getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public static boolean isClient() {
|
||||
return mode == CLUSTER_CLIENT;
|
||||
}
|
||||
|
||||
public static boolean isServer() {
|
||||
return mode == CLUSTER_SERVER;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Set current mode to client mode. If Sentinel currently works in server mode,
|
||||
* it will be turned off. Then the cluster client will be started.
|
||||
* </p>
|
||||
*/
|
||||
public static boolean setToClient() {
|
||||
if (mode == CLUSTER_CLIENT) {
|
||||
return true;
|
||||
}
|
||||
mode = CLUSTER_CLIENT;
|
||||
sleepIfNeeded();
|
||||
lastModified = TimeUtil.currentTimeMillis();
|
||||
return startClient();
|
||||
}
|
||||
|
||||
private static boolean startClient() {
|
||||
try {
|
||||
EmbeddedClusterTokenServer server = EmbeddedClusterTokenServerProvider.getServer();
|
||||
if (server != null) {
|
||||
server.stop();
|
||||
}
|
||||
ClusterTokenClient tokenClient = TokenClientProvider.getClient();
|
||||
if (tokenClient != null) {
|
||||
tokenClient.start();
|
||||
RecordLog.info("[ClusterStateManager] Changing cluster mode to client");
|
||||
return true;
|
||||
} else {
|
||||
RecordLog.warn("[ClusterStateManager] Cannot change to client (no client SPI found)");
|
||||
return false;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
RecordLog.warn("[ClusterStateManager] Error when changing cluster mode to client", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean stopClient() {
|
||||
try {
|
||||
ClusterTokenClient tokenClient = TokenClientProvider.getClient();
|
||||
if (tokenClient != null) {
|
||||
tokenClient.stop();
|
||||
RecordLog.info("[ClusterStateManager] Stopping the cluster token client");
|
||||
return true;
|
||||
} else {
|
||||
RecordLog.warn("[ClusterStateManager] Cannot stop cluster token client (no server SPI found)");
|
||||
return false;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
RecordLog.warn("[ClusterStateManager] Error when stopping cluster token client", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Set current mode to server mode. If Sentinel currently works in client mode,
|
||||
* it will be turned off. Then the cluster server will be started.
|
||||
* </p>
|
||||
*/
|
||||
public static boolean setToServer() {
|
||||
if (mode == CLUSTER_SERVER) {
|
||||
return true;
|
||||
}
|
||||
mode = CLUSTER_SERVER;
|
||||
sleepIfNeeded();
|
||||
lastModified = TimeUtil.currentTimeMillis();
|
||||
return startServer();
|
||||
}
|
||||
|
||||
private static boolean startServer() {
|
||||
try {
|
||||
ClusterTokenClient tokenClient = TokenClientProvider.getClient();
|
||||
if (tokenClient != null) {
|
||||
tokenClient.stop();
|
||||
}
|
||||
EmbeddedClusterTokenServer server = EmbeddedClusterTokenServerProvider.getServer();
|
||||
if (server != null) {
|
||||
server.start();
|
||||
RecordLog.info("[ClusterStateManager] Changing cluster mode to server");
|
||||
return true;
|
||||
} else {
|
||||
RecordLog.warn("[ClusterStateManager] Cannot change to server (no server SPI found)");
|
||||
return false;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
RecordLog.warn("[ClusterStateManager] Error when changing cluster mode to server", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean stopServer() {
|
||||
try {
|
||||
EmbeddedClusterTokenServer server = EmbeddedClusterTokenServerProvider.getServer();
|
||||
if (server != null) {
|
||||
server.stop();
|
||||
RecordLog.info("[ClusterStateManager] Stopping the cluster server");
|
||||
return true;
|
||||
} else {
|
||||
RecordLog.warn("[ClusterStateManager] Cannot stop server (no server SPI found)");
|
||||
return false;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
RecordLog.warn("[ClusterStateManager] Error when stopping server", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The interval between two change operations should be greater than {@code MIN_INTERVAL} (by default 10s).
|
||||
* Or we need to wait for a while.
|
||||
*/
|
||||
private static void sleepIfNeeded() {
|
||||
if (lastModified <= 0) {
|
||||
return;
|
||||
}
|
||||
long now = TimeUtil.currentTimeMillis();
|
||||
long durationPast = now - lastModified;
|
||||
long estimated = durationPast - MIN_INTERVAL;
|
||||
if (estimated < 0) {
|
||||
try {
|
||||
Thread.sleep(-estimated);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static long getLastModified() {
|
||||
return lastModified;
|
||||
}
|
||||
|
||||
private static class ClusterStatePropertyListener implements PropertyListener<Integer> {
|
||||
@Override
|
||||
public synchronized void configLoad(Integer value) {
|
||||
applyStateInternal(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void configUpdate(Integer value) {
|
||||
applyStateInternal(value);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean applyStateInternal(Integer state) {
|
||||
if (state == null || state < CLUSTER_NOT_STARTED) {
|
||||
return false;
|
||||
}
|
||||
if (state == mode) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
switch (state) {
|
||||
case CLUSTER_CLIENT:
|
||||
return setToClient();
|
||||
case CLUSTER_SERVER:
|
||||
return setToServer();
|
||||
case CLUSTER_NOT_STARTED:
|
||||
setStop();
|
||||
return true;
|
||||
default:
|
||||
RecordLog.warn("[ClusterStateManager] Ignoring unknown cluster state: " + state);
|
||||
return false;
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
RecordLog.warn("[ClusterStateManager] Fatal error when applying state: " + state, t);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void setStop() {
|
||||
if (mode == CLUSTER_NOT_STARTED) {
|
||||
return;
|
||||
}
|
||||
RecordLog.info("[ClusterStateManager] Changing cluster mode to not-started");
|
||||
mode = CLUSTER_NOT_STARTED;
|
||||
|
||||
sleepIfNeeded();
|
||||
lastModified = TimeUtil.currentTimeMillis();
|
||||
|
||||
stopClient();
|
||||
stopServer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply given state to cluster mode.
|
||||
*
|
||||
* @param state valid state to apply
|
||||
*/
|
||||
public static void applyState(Integer state) {
|
||||
stateProperty.updateValue(state);
|
||||
}
|
||||
|
||||
public static void markToServer() {
|
||||
mode = CLUSTER_SERVER;
|
||||
}
|
||||
|
||||
private static final int MIN_INTERVAL = 5 * 1000;
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.cluster;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Result entity of acquiring cluster flow token.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class TokenResult {
|
||||
|
||||
private Integer status;
|
||||
|
||||
private int remaining;
|
||||
private int waitInMs;
|
||||
|
||||
private long tokenId;
|
||||
|
||||
private Map<String, String> attachments;
|
||||
|
||||
public TokenResult() {
|
||||
}
|
||||
|
||||
public TokenResult(Integer status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public long getTokenId() {
|
||||
return tokenId;
|
||||
}
|
||||
|
||||
public void setTokenId(long tokenId) {
|
||||
this.tokenId = tokenId;
|
||||
}
|
||||
|
||||
public Integer getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public TokenResult setStatus(Integer status) {
|
||||
this.status = status;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getRemaining() {
|
||||
return remaining;
|
||||
}
|
||||
|
||||
public TokenResult setRemaining(int remaining) {
|
||||
this.remaining = remaining;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getWaitInMs() {
|
||||
return waitInMs;
|
||||
}
|
||||
|
||||
public TokenResult setWaitInMs(int waitInMs) {
|
||||
this.waitInMs = waitInMs;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, String> getAttachments() {
|
||||
return attachments;
|
||||
}
|
||||
|
||||
public TokenResult setAttachments(Map<String, String> attachments) {
|
||||
this.attachments = attachments;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TokenResult{" +
|
||||
"status=" + status +
|
||||
", remaining=" + remaining +
|
||||
", waitInMs=" + waitInMs +
|
||||
", attachments=" + attachments +
|
||||
", tokenId=" + tokenId +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.cluster;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public final class TokenResultStatus {
|
||||
|
||||
/**
|
||||
* Bad client request.
|
||||
*/
|
||||
public static final int BAD_REQUEST = -4;
|
||||
/**
|
||||
* Too many request in server.
|
||||
*/
|
||||
public static final int TOO_MANY_REQUEST = -2;
|
||||
/**
|
||||
* Server or client unexpected failure (due to transport or serialization failure).
|
||||
*/
|
||||
public static final int FAIL = -1;
|
||||
|
||||
/**
|
||||
* Token acquired.
|
||||
*/
|
||||
public static final int OK = 0;
|
||||
|
||||
/**
|
||||
* Token acquire failed (blocked).
|
||||
*/
|
||||
public static final int BLOCKED = 1;
|
||||
/**
|
||||
* Should wait for next buckets.
|
||||
*/
|
||||
public static final int SHOULD_WAIT = 2;
|
||||
/**
|
||||
* Token acquire failed (no rule exists).
|
||||
*/
|
||||
public static final int NO_RULE_EXISTS = 3;
|
||||
/**
|
||||
* Token acquire failed (reference resource is not available).
|
||||
*/
|
||||
public static final int NO_REF_RULE_EXISTS = 4;
|
||||
/**
|
||||
* Token acquire failed (strategy not available).
|
||||
*/
|
||||
public static final int NOT_AVAILABLE = 5;
|
||||
/**
|
||||
* Token is successfully released.
|
||||
*/
|
||||
public static final int RELEASE_OK = 6;
|
||||
/**
|
||||
* Token already is released before the request arrives.
|
||||
*/
|
||||
public static final int ALREADY_RELEASE=7;
|
||||
|
||||
private TokenResultStatus() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.cluster;
|
||||
|
||||
/**
|
||||
* A simple descriptor for Sentinel token server.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class TokenServerDescriptor {
|
||||
|
||||
private final String host;
|
||||
private final int port;
|
||||
|
||||
private String type = "default";
|
||||
|
||||
public TokenServerDescriptor(String host, int port) {
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public TokenServerDescriptor setType(String type) {
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TokenServerDescriptor{" +
|
||||
"host='" + host + '\'' +
|
||||
", port=" + port +
|
||||
", type='" + type + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.cluster;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Service interface of flow control.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public interface TokenService {
|
||||
|
||||
/**
|
||||
* Request tokens from remote token server.
|
||||
*
|
||||
* @param ruleId the unique rule ID
|
||||
* @param acquireCount token count to acquire
|
||||
* @param prioritized whether the request is prioritized
|
||||
* @return result of the token request
|
||||
*/
|
||||
TokenResult requestToken(Long ruleId, int acquireCount, boolean prioritized);
|
||||
|
||||
/**
|
||||
* Request tokens for a specific parameter from remote token server.
|
||||
*
|
||||
* @param ruleId the unique rule ID
|
||||
* @param acquireCount token count to acquire
|
||||
* @param params parameter list
|
||||
* @return result of the token request
|
||||
*/
|
||||
TokenResult requestParamToken(Long ruleId, int acquireCount, Collection<Object> params);
|
||||
|
||||
/**
|
||||
* Request acquire concurrent tokens from remote token server.
|
||||
*
|
||||
* @param clientAddress the address of the request belong.
|
||||
* @param ruleId ruleId the unique rule ID
|
||||
* @param acquireCount token count to acquire
|
||||
* @return result of the token request
|
||||
*/
|
||||
TokenResult requestConcurrentToken(String clientAddress,Long ruleId,int acquireCount);
|
||||
/**
|
||||
* Request release concurrent tokens from remote token server asynchronously.
|
||||
*
|
||||
* @param tokenId the unique token ID
|
||||
*/
|
||||
void releaseConcurrentToken(Long tokenId);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.cluster.client;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.TokenServerDescriptor;
|
||||
import com.alibaba.csp.sentinel.cluster.TokenService;
|
||||
|
||||
/**
|
||||
* Token client interface for distributed flow control.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public interface ClusterTokenClient extends TokenService {
|
||||
|
||||
/**
|
||||
* Get descriptor of current token server.
|
||||
*
|
||||
* @return current token server if connected, otherwise null
|
||||
*/
|
||||
TokenServerDescriptor currentServer();
|
||||
|
||||
/**
|
||||
* Start the token client.
|
||||
*
|
||||
* @throws Exception some error occurs
|
||||
*/
|
||||
void start() throws Exception;
|
||||
|
||||
/**
|
||||
* Stop the token client.
|
||||
*
|
||||
* @throws Exception some error occurs
|
||||
*/
|
||||
void stop() throws Exception;
|
||||
|
||||
/**
|
||||
* Get state of the cluster token client.
|
||||
*
|
||||
* @return state of the cluster token client
|
||||
*/
|
||||
int getState();
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.cluster.client;
|
||||
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.spi.SpiLoader;
|
||||
|
||||
/**
|
||||
* Provider for a universal {@link ClusterTokenClient} instance.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public final class TokenClientProvider {
|
||||
|
||||
private static ClusterTokenClient client = null;
|
||||
|
||||
static {
|
||||
// Not strictly thread-safe, but it's OK since it will be resolved only once.
|
||||
resolveTokenClientInstance();
|
||||
}
|
||||
|
||||
public static ClusterTokenClient getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
private static void resolveTokenClientInstance() {
|
||||
ClusterTokenClient resolvedClient = SpiLoader.of(ClusterTokenClient.class).loadFirstInstance();
|
||||
if (resolvedClient == null) {
|
||||
RecordLog.info(
|
||||
"[TokenClientProvider] No existing cluster token client, cluster client mode will not be activated");
|
||||
} else {
|
||||
client = resolvedClient;
|
||||
RecordLog.info("[TokenClientProvider] Cluster token client resolved: {}",
|
||||
client.getClass().getCanonicalName());
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isClientSpiAvailable() {
|
||||
return getClient() != null;
|
||||
}
|
||||
|
||||
private TokenClientProvider() {}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.cluster.log;
|
||||
|
||||
import com.alibaba.csp.sentinel.eagleeye.EagleEye;
|
||||
import com.alibaba.csp.sentinel.eagleeye.StatLogger;
|
||||
import com.alibaba.csp.sentinel.log.LogBase;
|
||||
|
||||
/**
|
||||
* @author jialiang.linjl
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public final class ClusterClientStatLogUtil {
|
||||
|
||||
private static final String FILE_NAME = "sentinel-cluster-client.log";
|
||||
|
||||
private static StatLogger statLogger;
|
||||
|
||||
static {
|
||||
String path = LogBase.getLogBaseDir() + FILE_NAME;
|
||||
|
||||
statLogger = EagleEye.statLoggerBuilder("sentinel-cluster-client-record")
|
||||
.intervalSeconds(1)
|
||||
.entryDelimiter('|')
|
||||
.keyDelimiter(',')
|
||||
.valueDelimiter(',')
|
||||
.maxEntryCount(5000)
|
||||
.configLogFilePath(path)
|
||||
.maxFileSizeMB(300)
|
||||
.maxBackupIndex(3)
|
||||
.buildSingleton();
|
||||
}
|
||||
|
||||
public static void log(String msg) {
|
||||
statLogger.stat(msg).count();
|
||||
}
|
||||
|
||||
public static void log(String msg, int count) {
|
||||
statLogger.stat(msg).count(count);
|
||||
}
|
||||
|
||||
private ClusterClientStatLogUtil() {}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.cluster.log;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import com.alibaba.csp.sentinel.eagleeye.EagleEye;
|
||||
import com.alibaba.csp.sentinel.eagleeye.StatLogger;
|
||||
import com.alibaba.csp.sentinel.log.LogBase;
|
||||
|
||||
/**
|
||||
* @author jialiang.linjl
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public final class ClusterStatLogUtil {
|
||||
|
||||
private static final String FILE_NAME = "sentinel-cluster.log";
|
||||
|
||||
private static StatLogger statLogger;
|
||||
|
||||
static {
|
||||
String path = LogBase.getLogBaseDir() + FILE_NAME;
|
||||
|
||||
statLogger = EagleEye.statLoggerBuilder("sentinel-cluster-record")
|
||||
.intervalSeconds(1)
|
||||
.entryDelimiter('|')
|
||||
.keyDelimiter(',')
|
||||
.valueDelimiter(',')
|
||||
.maxEntryCount(5000)
|
||||
.configLogFilePath(path)
|
||||
.maxFileSizeMB(300)
|
||||
.maxBackupIndex(3)
|
||||
.buildSingleton();
|
||||
}
|
||||
|
||||
public static void log(String msg) {
|
||||
statLogger.stat(msg).count();
|
||||
}
|
||||
|
||||
public static void log(String msg, int count) {
|
||||
statLogger.stat(msg).count(count);
|
||||
}
|
||||
|
||||
private ClusterStatLogUtil() {}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.cluster.server;
|
||||
|
||||
/**
|
||||
* Token server interface for distributed flow control.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public interface ClusterTokenServer {
|
||||
|
||||
/**
|
||||
* Start the Sentinel cluster server.
|
||||
*
|
||||
* @throws Exception if any error occurs
|
||||
*/
|
||||
void start() throws Exception;
|
||||
|
||||
/**
|
||||
* Stop the Sentinel cluster server.
|
||||
*
|
||||
* @throws Exception if any error occurs
|
||||
*/
|
||||
void stop() throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.cluster.server;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.TokenService;
|
||||
|
||||
/**
|
||||
* Embedded token server interface that can work in embedded mode.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public interface EmbeddedClusterTokenServer extends ClusterTokenServer, TokenService {
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.cluster.server;
|
||||
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.spi.SpiLoader;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public final class EmbeddedClusterTokenServerProvider {
|
||||
|
||||
private static EmbeddedClusterTokenServer server = null;
|
||||
|
||||
static {
|
||||
resolveInstance();
|
||||
}
|
||||
|
||||
private static void resolveInstance() {
|
||||
EmbeddedClusterTokenServer s = SpiLoader.of(EmbeddedClusterTokenServer.class).loadFirstInstance();
|
||||
if (s == null) {
|
||||
RecordLog.warn("[EmbeddedClusterTokenServerProvider] No existing cluster token server, cluster server mode will not be activated");
|
||||
} else {
|
||||
server = s;
|
||||
RecordLog.info("[EmbeddedClusterTokenServerProvider] Cluster token server resolved: {}",
|
||||
server.getClass().getCanonicalName());
|
||||
}
|
||||
}
|
||||
|
||||
public static EmbeddedClusterTokenServer getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
public static boolean isServerSpiAvailable() {
|
||||
return getServer() != null;
|
||||
}
|
||||
|
||||
private EmbeddedClusterTokenServerProvider() {}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.concurrent;
|
||||
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Wrapped thread factory for better use.
|
||||
*/
|
||||
public class NamedThreadFactory implements ThreadFactory {
|
||||
|
||||
private final ThreadGroup group;
|
||||
private final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||
|
||||
private final String namePrefix;
|
||||
private final boolean daemon;
|
||||
|
||||
public NamedThreadFactory(String namePrefix, boolean daemon) {
|
||||
this.daemon = daemon;
|
||||
SecurityManager s = System.getSecurityManager();
|
||||
group = (s != null) ? s.getThreadGroup() :
|
||||
Thread.currentThread().getThreadGroup();
|
||||
this.namePrefix = namePrefix;
|
||||
}
|
||||
|
||||
public NamedThreadFactory(String namePrefix) {
|
||||
this(namePrefix, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
Thread t = new Thread(group, r, namePrefix + "-thread-" + threadNumber.getAndIncrement(), 0);
|
||||
t.setDaemon(daemon);
|
||||
return t;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,345 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.config;
|
||||
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* The universal local configuration center of Sentinel. The config is retrieved from command line arguments
|
||||
* and customized properties file by default.
|
||||
*
|
||||
* @author leyou
|
||||
* @author Eric Zhao
|
||||
* @author Lin Liang
|
||||
*/
|
||||
public final class SentinelConfig {
|
||||
|
||||
/**
|
||||
* The default application type.
|
||||
*
|
||||
* @since 1.6.0
|
||||
*/
|
||||
public static final int APP_TYPE_COMMON = 0;
|
||||
|
||||
/**
|
||||
* Parameter value for using context classloader.
|
||||
*/
|
||||
private static final String CLASSLOADER_CONTEXT = "context";
|
||||
|
||||
private static final Map<String, String> props = new ConcurrentHashMap<>();
|
||||
|
||||
private static int appType = APP_TYPE_COMMON;
|
||||
private static String appName = "";
|
||||
|
||||
public static final String PROJECT_NAME_PROP_KEY = "project.name";
|
||||
public static final String APP_NAME_PROP_KEY = "csp.sentinel.app.name";
|
||||
public static final String APP_TYPE_PROP_KEY = "csp.sentinel.app.type";
|
||||
public static final String CHARSET = "csp.sentinel.charset";
|
||||
public static final String SINGLE_METRIC_FILE_SIZE = "csp.sentinel.metric.file.single.size";
|
||||
public static final String TOTAL_METRIC_FILE_COUNT = "csp.sentinel.metric.file.total.count";
|
||||
public static final String COLD_FACTOR = "csp.sentinel.flow.cold.factor";
|
||||
public static final String STATISTIC_MAX_RT = "csp.sentinel.statistic.max.rt";
|
||||
public static final String SPI_CLASSLOADER = "csp.sentinel.spi.classloader";
|
||||
public static final String METRIC_FLUSH_INTERVAL = "csp.sentinel.metric.flush.interval";
|
||||
|
||||
public static final String DEFAULT_CHARSET = "UTF-8";
|
||||
public static final long DEFAULT_SINGLE_METRIC_FILE_SIZE = 1024 * 1024 * 50;
|
||||
public static final int DEFAULT_TOTAL_METRIC_FILE_COUNT = 6;
|
||||
public static final int DEFAULT_COLD_FACTOR = 3;
|
||||
public static final int DEFAULT_STATISTIC_MAX_RT = 5000;
|
||||
public static final long DEFAULT_METRIC_FLUSH_INTERVAL = 1L;
|
||||
|
||||
static {
|
||||
try {
|
||||
initialize();
|
||||
loadProps();
|
||||
resolveAppName();
|
||||
resolveAppType();
|
||||
RecordLog.info("[SentinelConfig] Application type resolved: {}", appType);
|
||||
} catch (Throwable ex) {
|
||||
RecordLog.warn("[SentinelConfig] Failed to initialize", ex);
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static void resolveAppType() {
|
||||
try {
|
||||
String type = getConfig(APP_TYPE_PROP_KEY);
|
||||
if (type == null) {
|
||||
appType = APP_TYPE_COMMON;
|
||||
return;
|
||||
}
|
||||
appType = Integer.parseInt(type);
|
||||
if (appType < 0) {
|
||||
appType = APP_TYPE_COMMON;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
appType = APP_TYPE_COMMON;
|
||||
}
|
||||
}
|
||||
|
||||
private static void initialize() {
|
||||
// Init default properties.
|
||||
setConfig(CHARSET, DEFAULT_CHARSET);
|
||||
setConfig(SINGLE_METRIC_FILE_SIZE, String.valueOf(DEFAULT_SINGLE_METRIC_FILE_SIZE));
|
||||
setConfig(TOTAL_METRIC_FILE_COUNT, String.valueOf(DEFAULT_TOTAL_METRIC_FILE_COUNT));
|
||||
setConfig(COLD_FACTOR, String.valueOf(DEFAULT_COLD_FACTOR));
|
||||
setConfig(STATISTIC_MAX_RT, String.valueOf(DEFAULT_STATISTIC_MAX_RT));
|
||||
setConfig(METRIC_FLUSH_INTERVAL, String.valueOf(DEFAULT_METRIC_FLUSH_INTERVAL));
|
||||
}
|
||||
|
||||
private static void loadProps() {
|
||||
Properties properties = SentinelConfigLoader.getProperties();
|
||||
for (Object key : properties.keySet()) {
|
||||
setConfig((String) key, (String) properties.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get config value of the specific key.
|
||||
*
|
||||
* @param key config key
|
||||
* @return the config value.
|
||||
*/
|
||||
public static String getConfig(String key) {
|
||||
AssertUtil.notNull(key, "key cannot be null");
|
||||
return props.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get config value of the specific key.
|
||||
*
|
||||
* @param key config key
|
||||
* @param envVariableKey Get the value of the environment variable with the given key
|
||||
* @return the config value.
|
||||
*/
|
||||
public static String getConfig(String key, boolean envVariableKey) {
|
||||
AssertUtil.notNull(key, "key cannot be null");
|
||||
if (envVariableKey) {
|
||||
String value = System.getenv(key);
|
||||
if (StringUtil.isNotEmpty(value)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return getConfig(key);
|
||||
}
|
||||
|
||||
public static void setConfig(String key, String value) {
|
||||
AssertUtil.notNull(key, "key cannot be null");
|
||||
AssertUtil.notNull(value, "value cannot be null");
|
||||
props.put(key, value);
|
||||
}
|
||||
|
||||
public static String removeConfig(String key) {
|
||||
AssertUtil.notNull(key, "key cannot be null");
|
||||
return props.remove(key);
|
||||
}
|
||||
|
||||
public static void setConfigIfAbsent(String key, String value) {
|
||||
AssertUtil.notNull(key, "key cannot be null");
|
||||
AssertUtil.notNull(value, "value cannot be null");
|
||||
String v = props.get(key);
|
||||
if (v == null) {
|
||||
props.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getAppName() {
|
||||
return appName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get application type.
|
||||
*
|
||||
* @return application type, common (0) by default
|
||||
* @since 1.6.0
|
||||
*/
|
||||
public static int getAppType() {
|
||||
return appType;
|
||||
}
|
||||
|
||||
public static String charset() {
|
||||
return props.get(CHARSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the metric log flush interval in second
|
||||
* @return the metric log flush interval in second
|
||||
* @since 1.8.1
|
||||
*/
|
||||
public static long metricLogFlushIntervalSec() {
|
||||
String flushIntervalStr = SentinelConfig.getConfig(METRIC_FLUSH_INTERVAL);
|
||||
if (flushIntervalStr == null) {
|
||||
return DEFAULT_METRIC_FLUSH_INTERVAL;
|
||||
}
|
||||
try {
|
||||
return Long.parseLong(flushIntervalStr);
|
||||
} catch (Throwable throwable) {
|
||||
RecordLog.warn("[SentinelConfig] Parse the metricLogFlushInterval fail, use default value: "
|
||||
+ DEFAULT_METRIC_FLUSH_INTERVAL, throwable);
|
||||
return DEFAULT_METRIC_FLUSH_INTERVAL;
|
||||
}
|
||||
}
|
||||
|
||||
public static long singleMetricFileSize() {
|
||||
try {
|
||||
return Long.parseLong(props.get(SINGLE_METRIC_FILE_SIZE));
|
||||
} catch (Throwable throwable) {
|
||||
RecordLog.warn("[SentinelConfig] Parse singleMetricFileSize fail, use default value: "
|
||||
+ DEFAULT_SINGLE_METRIC_FILE_SIZE, throwable);
|
||||
return DEFAULT_SINGLE_METRIC_FILE_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
public static int totalMetricFileCount() {
|
||||
try {
|
||||
return Integer.parseInt(props.get(TOTAL_METRIC_FILE_COUNT));
|
||||
} catch (Throwable throwable) {
|
||||
RecordLog.warn("[SentinelConfig] Parse totalMetricFileCount fail, use default value: "
|
||||
+ DEFAULT_TOTAL_METRIC_FILE_COUNT, throwable);
|
||||
return DEFAULT_TOTAL_METRIC_FILE_COUNT;
|
||||
}
|
||||
}
|
||||
|
||||
public static int coldFactor() {
|
||||
try {
|
||||
int coldFactor = Integer.parseInt(props.get(COLD_FACTOR));
|
||||
// check the cold factor larger than 1
|
||||
if (coldFactor <= 1) {
|
||||
coldFactor = DEFAULT_COLD_FACTOR;
|
||||
RecordLog.warn("cold factor=" + coldFactor + ", should be larger than 1, use default value: "
|
||||
+ DEFAULT_COLD_FACTOR);
|
||||
}
|
||||
return coldFactor;
|
||||
} catch (Throwable throwable) {
|
||||
RecordLog.warn("[SentinelConfig] Parse coldFactor fail, use default value: "
|
||||
+ DEFAULT_COLD_FACTOR, throwable);
|
||||
return DEFAULT_COLD_FACTOR;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Get the max RT value that Sentinel could accept for system BBR strategy.</p>
|
||||
*
|
||||
* @return the max allowed RT value
|
||||
* @since 1.4.1
|
||||
*/
|
||||
public static int statisticMaxRt() {
|
||||
String v = props.get(STATISTIC_MAX_RT);
|
||||
try {
|
||||
if (StringUtil.isEmpty(v)) {
|
||||
return DEFAULT_STATISTIC_MAX_RT;
|
||||
}
|
||||
return Integer.parseInt(v);
|
||||
} catch (Throwable throwable) {
|
||||
RecordLog.warn("[SentinelConfig] Invalid statisticMaxRt value: {}, using the default value instead: "
|
||||
+ DEFAULT_STATISTIC_MAX_RT, v, throwable);
|
||||
SentinelConfig.setConfig(STATISTIC_MAX_RT, String.valueOf(DEFAULT_STATISTIC_MAX_RT));
|
||||
return DEFAULT_STATISTIC_MAX_RT;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for resolving project name. The order is elaborated below:
|
||||
*
|
||||
* <ol>
|
||||
* <li>Resolve the value from {@code CSP_SENTINEL_APP_NAME} system environment;</li>
|
||||
* <li>Resolve the value from {@code csp.sentinel.app.name} system property;</li>
|
||||
* <li>Resolve the value from {@code project.name} system property (for compatibility);</li>
|
||||
* <li>Resolve the value from {@code sun.java.command} system property, then remove path, arguments and ".jar" or ".JAR"
|
||||
* suffix, use the result as app name. Note that whitespace in file name or path is not allowed, or a
|
||||
* wrong app name may be gotten, For example:
|
||||
* <p>
|
||||
* <code>
|
||||
* "test.Main" -> test.Main<br/>
|
||||
* "/target/test.Main" -> test.Main<br/>
|
||||
* "/target/test.Main args1" -> test.Main<br/>
|
||||
* "Main.jar" -> Main<br/>
|
||||
* "/target/Main.JAR args1" -> Main<br/>
|
||||
* "Mai n.jar" -> Mai // whitespace in file name is not allowed<br/>
|
||||
* </code>
|
||||
* </p>
|
||||
* </li>
|
||||
* </ol>
|
||||
*/
|
||||
private static void resolveAppName() {
|
||||
// Priority: system env -> csp.sentinel.app.name -> project.name -> main class (or jar) name
|
||||
String envKey = toEnvKey(APP_NAME_PROP_KEY);
|
||||
String n = System.getenv(envKey);
|
||||
if (!StringUtil.isBlank(n)) {
|
||||
appName = n;
|
||||
RecordLog.info("App name resolved from system env {}: {}", envKey, appName);
|
||||
return;
|
||||
}
|
||||
n = props.get(APP_NAME_PROP_KEY);
|
||||
if (!StringUtil.isBlank(n)) {
|
||||
appName = n;
|
||||
RecordLog.info("App name resolved from property {}: {}", APP_NAME_PROP_KEY, appName);
|
||||
return;
|
||||
}
|
||||
n = props.get(PROJECT_NAME_PROP_KEY);
|
||||
if (!StringUtil.isBlank(n)) {
|
||||
appName = n;
|
||||
RecordLog.info("App name resolved from property {}: {}", PROJECT_NAME_PROP_KEY, appName);
|
||||
return;
|
||||
}
|
||||
// Parse sun.java.command property by default.
|
||||
String command = System.getProperty("sun.java.command");
|
||||
if (StringUtil.isBlank(command)) {
|
||||
RecordLog.warn("Cannot resolve default appName from property sun.java.command");
|
||||
return;
|
||||
}
|
||||
command = command.split("\\s")[0];
|
||||
String separator = File.separator;
|
||||
if (command.contains(separator)) {
|
||||
String[] strs;
|
||||
if ("\\".equals(separator)) {
|
||||
// Handle separator in Windows.
|
||||
strs = command.split("\\\\");
|
||||
} else {
|
||||
strs = command.split(separator);
|
||||
}
|
||||
command = strs[strs.length - 1];
|
||||
}
|
||||
if (command.toLowerCase().endsWith(".jar")) {
|
||||
command = command.substring(0, command.length() - 4);
|
||||
}
|
||||
appName = command;
|
||||
RecordLog.info("App name resolved from default: {}", appName);
|
||||
}
|
||||
|
||||
private static String toEnvKey(/*@NotBlank*/ String propKey) {
|
||||
return propKey.toUpperCase().replace('.', '_');
|
||||
}
|
||||
/**
|
||||
* Whether use context classloader via config parameter
|
||||
*
|
||||
* @return Whether use context classloader
|
||||
*/
|
||||
public static boolean shouldUseContextClassloader() {
|
||||
String classloaderConf = SentinelConfig.getConfig(SentinelConfig.SPI_CLASSLOADER);
|
||||
return CLASSLOADER_CONTEXT.equalsIgnoreCase(classloaderConf);
|
||||
}
|
||||
|
||||
private SentinelConfig() {}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.config;
|
||||
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.util.AppNameUtil;
|
||||
import com.alibaba.csp.sentinel.util.ConfigUtil;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
import static com.alibaba.csp.sentinel.util.ConfigUtil.addSeparator;
|
||||
|
||||
/**
|
||||
* <p>The loader that responsible for loading Sentinel common configurations.</p>
|
||||
*
|
||||
* @author lianglin
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public final class SentinelConfigLoader {
|
||||
|
||||
public static final String SENTINEL_CONFIG_ENV_KEY = "CSP_SENTINEL_CONFIG_FILE";
|
||||
public static final String SENTINEL_CONFIG_PROPERTY_KEY = "csp.sentinel.config.file";
|
||||
|
||||
private static final String DEFAULT_SENTINEL_CONFIG_FILE = "classpath:sentinel.properties";
|
||||
|
||||
private static Properties properties = new Properties();
|
||||
|
||||
static {
|
||||
try {
|
||||
load();
|
||||
} catch (Throwable t) {
|
||||
RecordLog.warn("[SentinelConfigLoader] Failed to initialize configuration items", t);
|
||||
}
|
||||
}
|
||||
|
||||
private static void load() {
|
||||
// Order: system property -> system env -> default file (classpath:sentinel.properties) -> legacy path
|
||||
String fileName = System.getProperty(SENTINEL_CONFIG_PROPERTY_KEY);
|
||||
if (StringUtil.isBlank(fileName)) {
|
||||
fileName = System.getenv(SENTINEL_CONFIG_ENV_KEY);
|
||||
if (StringUtil.isBlank(fileName)) {
|
||||
fileName = DEFAULT_SENTINEL_CONFIG_FILE;
|
||||
}
|
||||
}
|
||||
|
||||
Properties p = ConfigUtil.loadProperties(fileName);
|
||||
if (p != null && !p.isEmpty()) {
|
||||
RecordLog.info("[SentinelConfigLoader] Loading Sentinel config from {}", fileName);
|
||||
properties.putAll(p);
|
||||
}
|
||||
|
||||
for (Map.Entry<Object, Object> entry : new CopyOnWriteArraySet<>(System.getProperties().entrySet())) {
|
||||
String configKey = entry.getKey().toString();
|
||||
String newConfigValue = entry.getValue().toString();
|
||||
String oldConfigValue = properties.getProperty(configKey);
|
||||
properties.put(configKey, newConfigValue);
|
||||
if (oldConfigValue != null) {
|
||||
RecordLog.info("[SentinelConfigLoader] JVM parameter overrides {}: {} -> {}",
|
||||
configKey, oldConfigValue, newConfigValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static Properties getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.context;
|
||||
|
||||
import com.alibaba.csp.sentinel.Entry;
|
||||
import com.alibaba.csp.sentinel.SphO;
|
||||
import com.alibaba.csp.sentinel.SphU;
|
||||
import com.alibaba.csp.sentinel.node.DefaultNode;
|
||||
import com.alibaba.csp.sentinel.node.EntranceNode;
|
||||
import com.alibaba.csp.sentinel.node.Node;
|
||||
import com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot;
|
||||
|
||||
/**
|
||||
* This class holds metadata of current invocation:<br/>
|
||||
*
|
||||
* <ul>
|
||||
* <li>the {@link EntranceNode}: the root of the current invocation
|
||||
* tree.</li>
|
||||
* <li>the current {@link Entry}: the current invocation point.</li>
|
||||
* <li>the current {@link Node}: the statistics related to the
|
||||
* {@link Entry}.</li>
|
||||
* <li>the origin: The origin is useful when we want to control different
|
||||
* invoker/consumer separately. Usually the origin could be the Service Consumer's app name
|
||||
* or origin IP. </li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Each {@link SphU}#entry() or {@link SphO}#entry() should be in a {@link Context},
|
||||
* if we don't invoke {@link ContextUtil}#enter() explicitly, DEFAULT context will be used.
|
||||
* </p>
|
||||
* <p>
|
||||
* A invocation tree will be created if we invoke {@link SphU}#entry() multi times in
|
||||
* the same context.
|
||||
* </p>
|
||||
* <p>
|
||||
* Same resource in different context will count separately, see {@link NodeSelectorSlot}.
|
||||
* </p>
|
||||
*
|
||||
* @author jialiang.linjl
|
||||
* @author leyou(lihao)
|
||||
* @author Eric Zhao
|
||||
* @see ContextUtil
|
||||
* @see NodeSelectorSlot
|
||||
*/
|
||||
public class Context {
|
||||
|
||||
/**
|
||||
* Context name.
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* The entrance node of current invocation tree.
|
||||
*/
|
||||
private DefaultNode entranceNode;
|
||||
|
||||
/**
|
||||
* Current processing entry.
|
||||
*/
|
||||
private Entry curEntry;
|
||||
|
||||
/**
|
||||
* The origin of this context (usually indicate different invokers, e.g. service consumer name or origin IP).
|
||||
*/
|
||||
private String origin = "";
|
||||
|
||||
private final boolean async;
|
||||
|
||||
/**
|
||||
* Create a new async context.
|
||||
*
|
||||
* @param entranceNode entrance node of the context
|
||||
* @param name context name
|
||||
* @return the new created context
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public static Context newAsyncContext(DefaultNode entranceNode, String name) {
|
||||
return new Context(name, entranceNode, true);
|
||||
}
|
||||
|
||||
public Context(DefaultNode entranceNode, String name) {
|
||||
this(name, entranceNode, false);
|
||||
}
|
||||
|
||||
public Context(String name, DefaultNode entranceNode, boolean async) {
|
||||
this.name = name;
|
||||
this.entranceNode = entranceNode;
|
||||
this.async = async;
|
||||
}
|
||||
|
||||
public boolean isAsync() {
|
||||
return async;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Node getCurNode() {
|
||||
return curEntry == null ? null : curEntry.getCurNode();
|
||||
}
|
||||
|
||||
public Context setCurNode(Node node) {
|
||||
this.curEntry.setCurNode(node);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Entry getCurEntry() {
|
||||
return curEntry;
|
||||
}
|
||||
|
||||
public Context setCurEntry(Entry curEntry) {
|
||||
this.curEntry = curEntry;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getOrigin() {
|
||||
return origin;
|
||||
}
|
||||
|
||||
public Context setOrigin(String origin) {
|
||||
this.origin = origin;
|
||||
return this;
|
||||
}
|
||||
|
||||
public double getOriginTotalQps() {
|
||||
return getOriginNode() == null ? 0 : getOriginNode().totalQps();
|
||||
}
|
||||
|
||||
public double getOriginBlockQps() {
|
||||
return getOriginNode() == null ? 0 : getOriginNode().blockQps();
|
||||
}
|
||||
|
||||
public double getOriginPassReqQps() {
|
||||
return getOriginNode() == null ? 0 : getOriginNode().successQps();
|
||||
}
|
||||
|
||||
public double getOriginPassQps() {
|
||||
return getOriginNode() == null ? 0 : getOriginNode().passQps();
|
||||
}
|
||||
|
||||
public long getOriginTotalRequest() {
|
||||
return getOriginNode() == null ? 0 : getOriginNode().totalRequest();
|
||||
}
|
||||
|
||||
public long getOriginBlockRequest() {
|
||||
return getOriginNode() == null ? 0 : getOriginNode().blockRequest();
|
||||
}
|
||||
|
||||
public double getOriginAvgRt() {
|
||||
return getOriginNode() == null ? 0 : getOriginNode().avgRt();
|
||||
}
|
||||
|
||||
public int getOriginCurThreadNum() {
|
||||
return getOriginNode() == null ? 0 : getOriginNode().curThreadNum();
|
||||
}
|
||||
|
||||
public DefaultNode getEntranceNode() {
|
||||
return entranceNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent {@link Node} of the current.
|
||||
*
|
||||
* @return the parent node of the current.
|
||||
*/
|
||||
public Node getLastNode() {
|
||||
if (curEntry != null && curEntry.getLastNode() != null) {
|
||||
return curEntry.getLastNode();
|
||||
} else {
|
||||
return entranceNode;
|
||||
}
|
||||
}
|
||||
|
||||
public Node getOriginNode() {
|
||||
return curEntry == null ? null : curEntry.getOriginNode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Context{" +
|
||||
"name='" + name + '\'' +
|
||||
", entranceNode=" + entranceNode +
|
||||
", curEntry=" + curEntry +
|
||||
", origin='" + origin + '\'' +
|
||||
", async=" + async +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.context;
|
||||
|
||||
/**
|
||||
* @author qinan.qn
|
||||
*/
|
||||
public class ContextNameDefineException extends RuntimeException {
|
||||
|
||||
public ContextNameDefineException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.context;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import com.alibaba.csp.sentinel.Constants;
|
||||
import com.alibaba.csp.sentinel.EntryType;
|
||||
import com.alibaba.csp.sentinel.SphO;
|
||||
import com.alibaba.csp.sentinel.SphU;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.node.DefaultNode;
|
||||
import com.alibaba.csp.sentinel.node.EntranceNode;
|
||||
import com.alibaba.csp.sentinel.node.Node;
|
||||
import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper;
|
||||
import com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot;
|
||||
|
||||
/**
|
||||
* Utility class to get or create {@link Context} in current thread.
|
||||
*
|
||||
* <p>
|
||||
* Each {@link SphU}#entry() or {@link SphO}#entry() should be in a {@link Context}.
|
||||
* If we don't invoke {@link ContextUtil}#enter() explicitly, DEFAULT context will be used.
|
||||
* </p>
|
||||
*
|
||||
* @author jialiang.linjl
|
||||
* @author leyou(lihao)
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public class ContextUtil {
|
||||
|
||||
/**
|
||||
* Store the context in ThreadLocal for easy access.
|
||||
*/
|
||||
private static ThreadLocal<Context> contextHolder = new ThreadLocal<>();
|
||||
|
||||
/**
|
||||
* Holds all {@link EntranceNode}. Each {@link EntranceNode} is associated with a distinct context name.
|
||||
*/
|
||||
private static volatile Map<String, DefaultNode> contextNameNodeMap = new HashMap<>();
|
||||
|
||||
private static final ReentrantLock LOCK = new ReentrantLock();
|
||||
private static final Context NULL_CONTEXT = new NullContext();
|
||||
|
||||
static {
|
||||
// Cache the entrance node for default context.
|
||||
initDefaultContext();
|
||||
}
|
||||
|
||||
private static void initDefaultContext() {
|
||||
String defaultContextName = Constants.CONTEXT_DEFAULT_NAME;
|
||||
EntranceNode node = new EntranceNode(new StringResourceWrapper(defaultContextName, EntryType.IN), null);
|
||||
Constants.ROOT.addChild(node);
|
||||
contextNameNodeMap.put(defaultContextName, node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Not thread-safe, only for test.
|
||||
*/
|
||||
static void resetContextMap() {
|
||||
if (contextNameNodeMap != null) {
|
||||
RecordLog.warn("Context map cleared and reset to initial state");
|
||||
contextNameNodeMap.clear();
|
||||
initDefaultContext();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Enter the invocation context, which marks as the entrance of an invocation chain.
|
||||
* The context is wrapped with {@code ThreadLocal}, meaning that each thread has it's own {@link Context}.
|
||||
* New context will be created if current thread doesn't have one.
|
||||
* </p>
|
||||
* <p>
|
||||
* A context will be bound with an {@link EntranceNode}, which represents the entrance statistic node
|
||||
* of the invocation chain. New {@link EntranceNode} will be created if
|
||||
* current context does't have one. Note that same context name will share
|
||||
* same {@link EntranceNode} globally.
|
||||
* </p>
|
||||
* <p>
|
||||
* The origin node will be created in {@link com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot}.
|
||||
* Note that each distinct {@code origin} of different resources will lead to creating different new
|
||||
* {@link Node}, meaning that total amount of created origin statistic nodes will be:<br/>
|
||||
* {@code distinct resource name amount * distinct origin count}.<br/>
|
||||
* So when there are too many origins, memory footprint should be carefully considered.
|
||||
* </p>
|
||||
* <p>
|
||||
* Same resource in different context will count separately, see {@link NodeSelectorSlot}.
|
||||
* </p>
|
||||
*
|
||||
* @param name the context name
|
||||
* @param origin the origin of this invocation, usually the origin could be the Service
|
||||
* Consumer's app name. The origin is useful when we want to control different
|
||||
* invoker/consumer separately.
|
||||
* @return The invocation context of the current thread
|
||||
*/
|
||||
public static Context enter(String name, String origin) {
|
||||
if (Constants.CONTEXT_DEFAULT_NAME.equals(name)) {
|
||||
throw new ContextNameDefineException(
|
||||
"The " + Constants.CONTEXT_DEFAULT_NAME + " can't be permit to defined!");
|
||||
}
|
||||
return trueEnter(name, origin);
|
||||
}
|
||||
|
||||
protected static Context trueEnter(String name, String origin) {
|
||||
Context context = contextHolder.get();
|
||||
if (context == null) {
|
||||
Map<String, DefaultNode> localCacheNameMap = contextNameNodeMap;
|
||||
DefaultNode node = localCacheNameMap.get(name);
|
||||
if (node == null) {
|
||||
if (localCacheNameMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {
|
||||
setNullContext();
|
||||
return NULL_CONTEXT;
|
||||
} else {
|
||||
LOCK.lock();
|
||||
try {
|
||||
node = contextNameNodeMap.get(name);
|
||||
if (node == null) {
|
||||
if (contextNameNodeMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {
|
||||
setNullContext();
|
||||
return NULL_CONTEXT;
|
||||
} else {
|
||||
node = new EntranceNode(new StringResourceWrapper(name, EntryType.IN), null);
|
||||
// Add entrance node.
|
||||
Constants.ROOT.addChild(node);
|
||||
|
||||
Map<String, DefaultNode> newMap = new HashMap<>(contextNameNodeMap.size() + 1);
|
||||
newMap.putAll(contextNameNodeMap);
|
||||
newMap.put(name, node);
|
||||
contextNameNodeMap = newMap;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
LOCK.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
context = new Context(node, name);
|
||||
context.setOrigin(origin);
|
||||
contextHolder.set(context);
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
private static boolean shouldWarn = true;
|
||||
|
||||
private static void setNullContext() {
|
||||
contextHolder.set(NULL_CONTEXT);
|
||||
// Don't need to be thread-safe.
|
||||
if (shouldWarn) {
|
||||
RecordLog.warn("[SentinelStatusChecker] WARN: Amount of context exceeds the threshold "
|
||||
+ Constants.MAX_CONTEXT_NAME_SIZE + ". Entries in new contexts will NOT take effect!");
|
||||
shouldWarn = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Enter the invocation context, which marks as the entrance of an invocation chain.
|
||||
* The context is wrapped with {@code ThreadLocal}, meaning that each thread has it's own {@link Context}.
|
||||
* New context will be created if current thread doesn't have one.
|
||||
* </p>
|
||||
* <p>
|
||||
* A context will be bound with an {@link EntranceNode}, which represents the entrance statistic node
|
||||
* of the invocation chain. New {@link EntranceNode} will be created if
|
||||
* current context does't have one. Note that same context name will share
|
||||
* same {@link EntranceNode} globally.
|
||||
* </p>
|
||||
* <p>
|
||||
* Same resource in different context will count separately, see {@link NodeSelectorSlot}.
|
||||
* </p>
|
||||
*
|
||||
* @param name the context name
|
||||
* @return The invocation context of the current thread
|
||||
*/
|
||||
public static Context enter(String name) {
|
||||
return enter(name, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Exit context of current thread, that is removing {@link Context} in the
|
||||
* ThreadLocal.
|
||||
*/
|
||||
public static void exit() {
|
||||
Context context = contextHolder.get();
|
||||
if (context != null && context.getCurEntry() == null) {
|
||||
contextHolder.set(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current size of context entrance node map.
|
||||
*
|
||||
* @return current size of context entrance node map
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public static int contextSize() {
|
||||
return contextNameNodeMap.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if provided context is a default auto-created context.
|
||||
*
|
||||
* @param context context to check
|
||||
* @return true if it is a default context, otherwise false
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public static boolean isDefaultContext(Context context) {
|
||||
if (context == null) {
|
||||
return false;
|
||||
}
|
||||
return Constants.CONTEXT_DEFAULT_NAME.equals(context.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get {@link Context} of current thread.
|
||||
*
|
||||
* @return context of current thread. Null value will be return if current
|
||||
* thread does't have context.
|
||||
*/
|
||||
public static Context getContext() {
|
||||
return contextHolder.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Replace current context with the provided context.
|
||||
* This is mainly designed for context switching (e.g. in asynchronous invocation).
|
||||
* </p>
|
||||
* <p>
|
||||
* Note: When switching context manually, remember to restore the original context.
|
||||
* For common scenarios, you can use {@link #runOnContext(Context, Runnable)}.
|
||||
* </p>
|
||||
*
|
||||
* @param newContext new context to set
|
||||
* @return old context
|
||||
* @since 0.2.0
|
||||
*/
|
||||
static Context replaceContext(Context newContext) {
|
||||
Context backupContext = contextHolder.get();
|
||||
if (newContext == null) {
|
||||
contextHolder.remove();
|
||||
} else {
|
||||
contextHolder.set(newContext);
|
||||
}
|
||||
return backupContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the code within provided context.
|
||||
* This is mainly designed for context switching (e.g. in asynchronous invocation).
|
||||
*
|
||||
* @param context the context
|
||||
* @param f lambda to run within the context
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public static void runOnContext(Context context, Runnable f) {
|
||||
Context curContext = replaceContext(context);
|
||||
try {
|
||||
f.run();
|
||||
} finally {
|
||||
replaceContext(curContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.context;
|
||||
|
||||
import com.alibaba.csp.sentinel.Constants;
|
||||
|
||||
/**
|
||||
* If total {@link Context} exceed {@link Constants#MAX_CONTEXT_NAME_SIZE}, a
|
||||
* {@link NullContext} will get when invoke {@link ContextUtil}.enter(), means
|
||||
* no rules checking will do.
|
||||
*
|
||||
* @author qinan.qn
|
||||
*/
|
||||
public class NullContext extends Context {
|
||||
|
||||
public NullContext() {
|
||||
super(null, "null_context_internal");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.eagleeye;
|
||||
|
||||
class BaseLoggerBuilder<T extends BaseLoggerBuilder<T>> {
|
||||
|
||||
protected final String loggerName;
|
||||
|
||||
protected String filePath = null;
|
||||
|
||||
protected long maxFileSize = 1024;
|
||||
|
||||
protected char entryDelimiter = '|';
|
||||
|
||||
protected int maxBackupIndex = 3;
|
||||
|
||||
BaseLoggerBuilder(String loggerName) {
|
||||
this.loggerName = loggerName;
|
||||
}
|
||||
|
||||
public T logFilePath(String logFilePath) {
|
||||
return configLogFilePath(logFilePath, EagleEye.EAGLEEYE_LOG_DIR);
|
||||
}
|
||||
|
||||
public T appFilePath(String appFilePath) {
|
||||
return configLogFilePath(appFilePath, EagleEye.APP_LOG_DIR);
|
||||
}
|
||||
|
||||
public T baseLogFilePath(String baseLogFilePath) {
|
||||
return configLogFilePath(baseLogFilePath, EagleEye.BASE_LOG_DIR);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private T configLogFilePath(String filePathToConfig, String basePath) {
|
||||
EagleEyeCoreUtils.checkNotNullEmpty(filePathToConfig, "filePath");
|
||||
if (filePathToConfig.charAt(0) != '/') {
|
||||
filePathToConfig = basePath + filePathToConfig;
|
||||
}
|
||||
this.filePath = filePathToConfig;
|
||||
return (T)this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public T configLogFilePath(String filePath) {
|
||||
EagleEyeCoreUtils.checkNotNullEmpty(filePath, "filePath");
|
||||
this.filePath = filePath;
|
||||
return (T)this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public T maxFileSizeMB(long maxFileSizeMB) {
|
||||
if (maxFileSize < 10) {
|
||||
throw new IllegalArgumentException("Invalid maxFileSizeMB");
|
||||
}
|
||||
this.maxFileSize = maxFileSizeMB * 1024 * 1024;
|
||||
return (T)this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public T maxBackupIndex(int maxBackupIndex) {
|
||||
if (maxBackupIndex < 1) {
|
||||
throw new IllegalArgumentException("");
|
||||
}
|
||||
this.maxBackupIndex = maxBackupIndex;
|
||||
return (T)this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public T entryDelimiter(char entryDelimiter) {
|
||||
this.entryDelimiter = entryDelimiter;
|
||||
return (T)this;
|
||||
}
|
||||
|
||||
String getLoggerName() {
|
||||
return loggerName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.eagleeye;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public final class EagleEye {
|
||||
|
||||
public static final String CLASS_LOCATION = getEagleEyeLocation();
|
||||
|
||||
static final String USER_HOME = locateUserHome();
|
||||
|
||||
static final String BASE_LOG_DIR = locateBaseLogPath();
|
||||
|
||||
static final String EAGLEEYE_LOG_DIR = locateEagleEyeLogPath();
|
||||
|
||||
static final String APP_LOG_DIR = locateAppLogPath();
|
||||
|
||||
static final Charset DEFAULT_CHARSET = getDefaultOutputCharset();
|
||||
|
||||
static final String EAGLEEYE_SELF_LOG_FILE = EagleEye.EAGLEEYE_LOG_DIR + "eagleeye-self.log";
|
||||
|
||||
// 200MB
|
||||
static final long MAX_SELF_LOG_FILE_SIZE = 200 * 1024 * 1024;
|
||||
|
||||
static EagleEyeAppender selfAppender = createSelfLogger();
|
||||
|
||||
static private TokenBucket exceptionBucket = new TokenBucket(10, TimeUnit.SECONDS.toMillis(10));
|
||||
|
||||
static String getEagleEyeLocation() {
|
||||
try {
|
||||
URL resource = EagleEye.class.getProtectionDomain().getCodeSource().getLocation();
|
||||
if (resource != null) {
|
||||
return resource.toString();
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
// ignore
|
||||
}
|
||||
return "unknown location";
|
||||
}
|
||||
|
||||
static Charset getDefaultOutputCharset() {
|
||||
Charset cs;
|
||||
String charsetName = EagleEyeCoreUtils.getSystemProperty("EAGLEEYE.CHARSET");
|
||||
if (EagleEyeCoreUtils.isNotBlank(charsetName)) {
|
||||
charsetName = charsetName.trim();
|
||||
try {
|
||||
cs = Charset.forName(charsetName);
|
||||
if (cs != null) {
|
||||
return cs;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// quietly
|
||||
}
|
||||
}
|
||||
try {
|
||||
cs = Charset.forName("GB18030");
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
cs = Charset.forName("GBK");
|
||||
} catch (Exception e2) {
|
||||
cs = Charset.forName("UTF-8");
|
||||
}
|
||||
}
|
||||
return cs;
|
||||
}
|
||||
|
||||
private static String locateUserHome() {
|
||||
String userHome = EagleEyeCoreUtils.getSystemProperty("user.home");
|
||||
if (EagleEyeCoreUtils.isNotBlank(userHome)) {
|
||||
if (!userHome.endsWith(File.separator)) {
|
||||
userHome += File.separator;
|
||||
}
|
||||
} else {
|
||||
userHome = "/tmp/";
|
||||
}
|
||||
return userHome;
|
||||
}
|
||||
|
||||
private static String locateBaseLogPath() {
|
||||
String tmpPath = EagleEyeCoreUtils.getSystemProperty("JM.LOG.PATH");
|
||||
if (EagleEyeCoreUtils.isNotBlank(tmpPath)) {
|
||||
if (!tmpPath.endsWith(File.separator)) {
|
||||
tmpPath += File.separator;
|
||||
}
|
||||
} else {
|
||||
tmpPath = USER_HOME + "logs" + File.separator;
|
||||
}
|
||||
return tmpPath;
|
||||
}
|
||||
|
||||
private static String locateEagleEyeLogPath() {
|
||||
String tmpPath = EagleEyeCoreUtils.getSystemProperty("EAGLEEYE.LOG.PATH");
|
||||
if (EagleEyeCoreUtils.isNotBlank(tmpPath)) {
|
||||
if (!tmpPath.endsWith(File.separator)) {
|
||||
tmpPath += File.separator;
|
||||
}
|
||||
} else {
|
||||
tmpPath = BASE_LOG_DIR + "eagleeye" + File.separator;
|
||||
}
|
||||
return tmpPath;
|
||||
}
|
||||
|
||||
private static String locateAppLogPath() {
|
||||
String appName = EagleEyeCoreUtils.getSystemProperty("project.name");
|
||||
if (EagleEyeCoreUtils.isNotBlank(appName)) {
|
||||
return USER_HOME + appName + File.separator + "logs" + File.separator;
|
||||
} else {
|
||||
return EAGLEEYE_LOG_DIR;
|
||||
}
|
||||
}
|
||||
|
||||
static private final EagleEyeAppender createSelfLogger() {
|
||||
EagleEyeRollingFileAppender selfAppender = new EagleEyeRollingFileAppender(EAGLEEYE_SELF_LOG_FILE,
|
||||
EagleEyeCoreUtils.getSystemPropertyForLong("EAGLEEYE.LOG.SELF.FILESIZE", MAX_SELF_LOG_FILE_SIZE),
|
||||
false);
|
||||
return new SyncAppender(selfAppender);
|
||||
}
|
||||
|
||||
static {
|
||||
initEagleEye();
|
||||
}
|
||||
|
||||
private static void initEagleEye() {
|
||||
try {
|
||||
selfLog("[INFO] EagleEye started (" + CLASS_LOCATION + ")" + ", classloader="
|
||||
+ EagleEye.class.getClassLoader());
|
||||
} catch (Throwable e) {
|
||||
selfLog("[INFO] EagleEye started (" + CLASS_LOCATION + ")");
|
||||
}
|
||||
|
||||
try {
|
||||
EagleEyeLogDaemon.start();
|
||||
} catch (Throwable e) {
|
||||
selfLog("[ERROR] fail to start EagleEyeLogDaemon", e);
|
||||
}
|
||||
try {
|
||||
StatLogController.start();
|
||||
} catch (Throwable e) {
|
||||
selfLog("[ERROR] fail to start StatLogController", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void shutdown() {
|
||||
selfLog("[WARN] EagleEye is shutting down (" + CLASS_LOCATION + ")");
|
||||
|
||||
EagleEye.flush();
|
||||
|
||||
try {
|
||||
StatLogController.stop();
|
||||
EagleEye.selfLog("[INFO] StatLogController stopped");
|
||||
} catch (Throwable e) {
|
||||
selfLog("[ERROR] fail to stop StatLogController", e);
|
||||
}
|
||||
|
||||
try {
|
||||
EagleEyeLogDaemon.stop();
|
||||
EagleEye.selfLog("[INFO] EagleEyeLogDaemon stopped");
|
||||
} catch (Throwable e) {
|
||||
selfLog("[ERROR] fail to stop EagleEyeLogDaemon", e);
|
||||
}
|
||||
|
||||
EagleEye.selfLog("[WARN] EagleEye shutdown successfully (" + CLASS_LOCATION + ")");
|
||||
try {
|
||||
selfAppender.close();
|
||||
} catch (Throwable e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
private EagleEye() {
|
||||
}
|
||||
|
||||
static public StatLogger statLogger(String loggerName) {
|
||||
return statLoggerBuilder(loggerName).buildSingleton();
|
||||
}
|
||||
|
||||
static public StatLoggerBuilder statLoggerBuilder(String loggerName) {
|
||||
return new StatLoggerBuilder(loggerName);
|
||||
}
|
||||
|
||||
static void setEagelEyeSelfAppender(EagleEyeAppender appender) {
|
||||
selfAppender = appender;
|
||||
}
|
||||
|
||||
public static void selfLog(String log) {
|
||||
try {
|
||||
String timestamp = EagleEyeCoreUtils.formatTime(System.currentTimeMillis());
|
||||
String line = "[" + timestamp + "] " + log + EagleEyeCoreUtils.NEWLINE;
|
||||
selfAppender.append(line);
|
||||
} catch (Throwable t) {
|
||||
}
|
||||
}
|
||||
|
||||
public static void selfLog(String log, Throwable e) {
|
||||
long now = System.currentTimeMillis();
|
||||
if (exceptionBucket.accept(now)) {
|
||||
try {
|
||||
String timestamp = EagleEyeCoreUtils.formatTime(now);
|
||||
StringWriter sw = new StringWriter(4096);
|
||||
PrintWriter pw = new PrintWriter(sw, false);
|
||||
pw.append('[').append(timestamp).append("] ").append(log).append(EagleEyeCoreUtils.NEWLINE);
|
||||
e.printStackTrace(pw);
|
||||
pw.println();
|
||||
pw.flush();
|
||||
selfAppender.append(sw.toString());
|
||||
} catch (Throwable t) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static public void flush() {
|
||||
EagleEyeLogDaemon.flushAndWait();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.eagleeye;
|
||||
|
||||
public abstract class EagleEyeAppender {
|
||||
|
||||
public abstract void append(String log);
|
||||
|
||||
public void flush() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
public void rollOver() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
public void close() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
public String getOutputLocation() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.eagleeye;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
final class EagleEyeCoreUtils {
|
||||
|
||||
public static final String EMPTY_STRING = "";
|
||||
public static final String NEWLINE = "\r\n";
|
||||
|
||||
public static final String[] EMPTY_STRING_ARRAY = new String[0];
|
||||
|
||||
public static boolean isBlank(String str) {
|
||||
int strLen;
|
||||
if (str == null || (strLen = str.length()) == 0) {
|
||||
return true;
|
||||
}
|
||||
for (int i = 0; i < strLen; i++) {
|
||||
if ((!Character.isWhitespace(str.charAt(i)))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static String checkNotNullEmpty(String value, String name) throws IllegalArgumentException {
|
||||
if (isBlank(value)) {
|
||||
throw new IllegalArgumentException(name + " is null or empty");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public static <T> T checkNotNull(T value, String name) throws IllegalArgumentException {
|
||||
if (value == null) {
|
||||
throw new IllegalArgumentException(name + " is null");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public static <T> T defaultIfNull(T value, T defaultValue) {
|
||||
return (value == null) ? defaultValue : value;
|
||||
}
|
||||
|
||||
public static boolean isNotBlank(String str) {
|
||||
return !isBlank(str);
|
||||
}
|
||||
|
||||
public static boolean isNotEmpty(String str) {
|
||||
return str != null && str.length() > 0;
|
||||
}
|
||||
|
||||
public static String trim(String str) {
|
||||
return str == null ? null : str.trim();
|
||||
}
|
||||
|
||||
public static String[] split(String str, char separatorChar) {
|
||||
return splitWorker(str, separatorChar, false);
|
||||
}
|
||||
|
||||
private static String[] splitWorker(String str, char separatorChar, boolean preserveAllTokens) {
|
||||
if (str == null) {
|
||||
return null;
|
||||
}
|
||||
int len = str.length();
|
||||
if (len == 0) {
|
||||
return EMPTY_STRING_ARRAY;
|
||||
}
|
||||
List<String> list = new ArrayList<String>();
|
||||
int i = 0, start = 0;
|
||||
boolean match = false;
|
||||
boolean lastMatch = false;
|
||||
while (i < len) {
|
||||
if (str.charAt(i) == separatorChar) {
|
||||
if (match || preserveAllTokens) {
|
||||
list.add(str.substring(start, i));
|
||||
match = false;
|
||||
lastMatch = true;
|
||||
}
|
||||
start = ++i;
|
||||
continue;
|
||||
}
|
||||
lastMatch = false;
|
||||
match = true;
|
||||
i++;
|
||||
}
|
||||
if (match || (preserveAllTokens && lastMatch)) {
|
||||
list.add(str.substring(start, i));
|
||||
}
|
||||
return list.toArray(new String[list.size()]);
|
||||
}
|
||||
|
||||
public static StringBuilder appendWithBlankCheck(String str, String defaultValue, StringBuilder appender) {
|
||||
if (isNotBlank(str)) {
|
||||
appender.append(str);
|
||||
} else {
|
||||
appender.append(defaultValue);
|
||||
}
|
||||
return appender;
|
||||
}
|
||||
|
||||
public static StringBuilder appendWithNullCheck(Object obj, String defaultValue, StringBuilder appender) {
|
||||
if (obj != null) {
|
||||
appender.append(obj.toString());
|
||||
} else {
|
||||
appender.append(defaultValue);
|
||||
}
|
||||
return appender;
|
||||
}
|
||||
|
||||
public static StringBuilder appendLog(String str, StringBuilder appender, char delimiter) {
|
||||
if (str != null) {
|
||||
int len = str.length();
|
||||
appender.ensureCapacity(appender.length() + len);
|
||||
for (int i = 0; i < len; i++) {
|
||||
char c = str.charAt(i);
|
||||
if (c == '\n' || c == '\r' || c == delimiter) {
|
||||
c = ' ';
|
||||
}
|
||||
appender.append(c);
|
||||
}
|
||||
}
|
||||
return appender;
|
||||
}
|
||||
|
||||
private static final ThreadLocal<FastDateFormat> dateFmt = new ThreadLocal<FastDateFormat>() {
|
||||
@Override
|
||||
protected FastDateFormat initialValue() {
|
||||
return new FastDateFormat();
|
||||
}
|
||||
};
|
||||
|
||||
public static String formatTime(long timestamp) {
|
||||
return dateFmt.get().format(timestamp);
|
||||
}
|
||||
|
||||
public static String getSystemProperty(String key) {
|
||||
try {
|
||||
return System.getProperty(key);
|
||||
} catch (Throwable t) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static long getSystemPropertyForLong(String key, long defaultValue) {
|
||||
try {
|
||||
return Long.parseLong(System.getProperty(key));
|
||||
} catch (Throwable t) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isHexNumeric(char ch) {
|
||||
return (ch >= 'a' && ch <= 'f') || (ch >= '0' && ch <= '9');
|
||||
}
|
||||
|
||||
public static boolean isNumeric(char ch) {
|
||||
return ch >= '0' && ch <= '9';
|
||||
}
|
||||
|
||||
public static void shutdownThreadPool(ExecutorService pool, long awaitTimeMillis) {
|
||||
try {
|
||||
pool.shutdown();
|
||||
|
||||
boolean done = false;
|
||||
if (awaitTimeMillis > 0) {
|
||||
try {
|
||||
done = pool.awaitTermination(awaitTimeMillis, TimeUnit.MILLISECONDS);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!done) {
|
||||
pool.shutdownNow();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// quietly
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.eagleeye;
|
||||
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
class EagleEyeLogDaemon implements Runnable {
|
||||
|
||||
private static final long LOG_CHECK_INTERVAL = TimeUnit.SECONDS.toMillis(20);
|
||||
|
||||
private static AtomicBoolean running = new AtomicBoolean(false);
|
||||
|
||||
private static Thread worker = null;
|
||||
|
||||
private static final CopyOnWriteArrayList<EagleEyeAppender> watchedAppenders
|
||||
= new CopyOnWriteArrayList<EagleEyeAppender>();
|
||||
|
||||
static EagleEyeAppender watch(EagleEyeAppender appender) {
|
||||
watchedAppenders.addIfAbsent(appender);
|
||||
return appender;
|
||||
}
|
||||
|
||||
static boolean unwatch(EagleEyeAppender appender) {
|
||||
return watchedAppenders.remove(appender);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (running.get()) {
|
||||
|
||||
cleanupFiles();
|
||||
|
||||
try {
|
||||
Thread.sleep(LOG_CHECK_INTERVAL);
|
||||
} catch (InterruptedException e) {
|
||||
|
||||
}
|
||||
|
||||
flushAndReload();
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanupFiles() {
|
||||
for (EagleEyeAppender watchedAppender : watchedAppenders) {
|
||||
try {
|
||||
watchedAppender.cleanup();
|
||||
} catch (Exception e) {
|
||||
EagleEye.selfLog("[ERROR] fail to cleanup: " + watchedAppender, e);
|
||||
}
|
||||
}
|
||||
try {
|
||||
EagleEye.selfAppender.cleanup();
|
||||
} catch (Exception e) {
|
||||
// quietly
|
||||
}
|
||||
}
|
||||
|
||||
private void flushAndReload() {
|
||||
for (EagleEyeAppender watchedAppender : watchedAppenders) {
|
||||
try {
|
||||
watchedAppender.reload();
|
||||
} catch (Exception e) {
|
||||
EagleEye.selfLog("[ERROR] fail to reload: " + watchedAppender, e);
|
||||
}
|
||||
}
|
||||
try {
|
||||
EagleEye.selfAppender.reload();
|
||||
} catch (Exception e) {
|
||||
// quietly
|
||||
}
|
||||
}
|
||||
|
||||
static void start() {
|
||||
if (running.compareAndSet(false, true)) {
|
||||
Thread worker = new Thread(new EagleEyeLogDaemon());
|
||||
worker.setDaemon(true);
|
||||
worker.setName("EagleEye-LogDaemon-Thread");
|
||||
worker.start();
|
||||
EagleEyeLogDaemon.worker = worker;
|
||||
}
|
||||
}
|
||||
|
||||
static void stop() {
|
||||
if (running.compareAndSet(true, false)) {
|
||||
|
||||
closeAppenders();
|
||||
|
||||
final Thread worker = EagleEyeLogDaemon.worker;
|
||||
if (worker != null) {
|
||||
try {
|
||||
worker.interrupt();
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
try {
|
||||
worker.join(1000);
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void closeAppenders() {
|
||||
for (EagleEyeAppender watchedAppender : watchedAppenders) {
|
||||
try {
|
||||
watchedAppender.close();
|
||||
} catch (Exception e) {
|
||||
EagleEye.selfLog("[ERROR] fail to close: " + watchedAppender, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void flushAndWait() {
|
||||
for (EagleEyeAppender watchedAppender : watchedAppenders) {
|
||||
try {
|
||||
watchedAppender.flush();
|
||||
} catch (Exception e) {
|
||||
EagleEye.selfLog("[ERROR] fail to flush: " + watchedAppender, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private EagleEyeLogDaemon() {}
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.eagleeye;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.channels.FileLock;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
class EagleEyeRollingFileAppender extends EagleEyeAppender {
|
||||
|
||||
private static final long LOG_FLUSH_INTERVAL = TimeUnit.SECONDS.toMillis(1);
|
||||
|
||||
private static final int DEFAULT_BUFFER_SIZE = 4 * 1024; // 4KB
|
||||
|
||||
private final int maxBackupIndex = 3;
|
||||
|
||||
private final long maxFileSize;
|
||||
|
||||
private final int bufferSize = DEFAULT_BUFFER_SIZE;
|
||||
|
||||
private final String filePath;
|
||||
|
||||
private final AtomicBoolean isRolling = new AtomicBoolean(false);
|
||||
|
||||
private BufferedOutputStream bos = null;
|
||||
|
||||
private long nextFlushTime = 0L;
|
||||
|
||||
private long lastRollOverTime = 0L;
|
||||
|
||||
private long outputByteSize = 0L;
|
||||
|
||||
private final boolean selfLogEnabled;
|
||||
|
||||
private boolean multiProcessDetected = false;
|
||||
|
||||
private static final String DELETE_FILE_SUFFIX = ".deleted";
|
||||
|
||||
public EagleEyeRollingFileAppender(String filePath, long maxFileSize) {
|
||||
this(filePath, maxFileSize, true);
|
||||
}
|
||||
|
||||
public EagleEyeRollingFileAppender(String filePath, long maxFileSize, boolean selfLogEnabled) {
|
||||
this.filePath = filePath;
|
||||
this.maxFileSize = maxFileSize;
|
||||
this.selfLogEnabled = selfLogEnabled;
|
||||
setFile();
|
||||
}
|
||||
|
||||
private void setFile() {
|
||||
try {
|
||||
File logFile = new File(filePath);
|
||||
if (!logFile.exists()) {
|
||||
File parentFile = logFile.getParentFile();
|
||||
if (!parentFile.exists() && !parentFile.mkdirs()) {
|
||||
doSelfLog("[ERROR] Fail to mkdirs: " + parentFile.getAbsolutePath());
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (!logFile.createNewFile()) {
|
||||
doSelfLog("[ERROR] Fail to create file, it exists: " + logFile.getAbsolutePath());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
doSelfLog(
|
||||
"[ERROR] Fail to create file: " + logFile.getAbsolutePath() + ", error=" + e.getMessage());
|
||||
}
|
||||
}
|
||||
if (!logFile.isFile() || !logFile.canWrite()) {
|
||||
doSelfLog("[ERROR] Invalid file, exists=" + logFile.exists() + ", isFile=" + logFile.isFile()
|
||||
+ ", canWrite=" + logFile.canWrite() + ", path=" + logFile.getAbsolutePath());
|
||||
return;
|
||||
}
|
||||
FileOutputStream ostream = new FileOutputStream(logFile, true);
|
||||
// true
|
||||
// O_APPEND
|
||||
this.bos = new BufferedOutputStream(ostream, bufferSize);
|
||||
this.lastRollOverTime = System.currentTimeMillis();
|
||||
this.outputByteSize = logFile.length();
|
||||
} catch (Throwable e) {
|
||||
doSelfLog("[ERROR] Fail to create file to write: " + filePath + ", error=" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void append(String log) {
|
||||
BufferedOutputStream bos = this.bos;
|
||||
if (bos != null) {
|
||||
try {
|
||||
waitUntilRollFinish();
|
||||
|
||||
byte[] bytes = log.getBytes(EagleEye.DEFAULT_CHARSET);
|
||||
int len = bytes.length;
|
||||
if (len > DEFAULT_BUFFER_SIZE && this.multiProcessDetected) {
|
||||
len = DEFAULT_BUFFER_SIZE;
|
||||
bytes[len - 1] = '\n';
|
||||
}
|
||||
bos.write(bytes, 0, len);
|
||||
outputByteSize += len;
|
||||
|
||||
if (outputByteSize >= maxFileSize) {
|
||||
rollOver();
|
||||
} else {
|
||||
if (System.currentTimeMillis() >= nextFlushTime) {
|
||||
flush();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
doSelfLog("[ERROR] fail to write log to file " + filePath + ", error=" + e.getMessage());
|
||||
close();
|
||||
setFile();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
final BufferedOutputStream bos = this.bos;
|
||||
if (bos != null) {
|
||||
try {
|
||||
bos.flush();
|
||||
nextFlushTime = System.currentTimeMillis() + LOG_FLUSH_INTERVAL;
|
||||
} catch (Exception e) {
|
||||
doSelfLog("[WARN] Fail to flush OutputStream: " + filePath + ", " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollOver() {
|
||||
final String lockFilePath = filePath + ".lock";
|
||||
final File lockFile = new File(lockFilePath);
|
||||
|
||||
RandomAccessFile raf = null;
|
||||
FileLock fileLock = null;
|
||||
|
||||
if (!isRolling.compareAndSet(false, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
raf = new RandomAccessFile(lockFile, "rw");
|
||||
fileLock = raf.getChannel().tryLock();
|
||||
|
||||
if (fileLock != null) {
|
||||
File target;
|
||||
File file;
|
||||
final int maxBackupIndex = this.maxBackupIndex;
|
||||
|
||||
reload();
|
||||
if (outputByteSize >= maxFileSize) {
|
||||
file = new File(filePath + '.' + maxBackupIndex);
|
||||
if (file.exists()) {
|
||||
target = new File(filePath + '.' + maxBackupIndex + DELETE_FILE_SUFFIX);
|
||||
if (!file.renameTo(target) && !file.delete()) {
|
||||
doSelfLog("[ERROR] Fail to delete or rename file: " + file.getAbsolutePath() + " to "
|
||||
+ target.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = maxBackupIndex - 1; i >= 1; i--) {
|
||||
file = new File(filePath + '.' + i);
|
||||
if (file.exists()) {
|
||||
target = new File(filePath + '.' + (i + 1));
|
||||
if (!file.renameTo(target) && !file.delete()) {
|
||||
doSelfLog("[ERROR] Fail to delete or rename file: " + file.getAbsolutePath() + " to "
|
||||
+ target.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
target = new File(filePath + "." + 1);
|
||||
|
||||
close();
|
||||
|
||||
file = new File(filePath);
|
||||
if (file.renameTo(target)) {
|
||||
doSelfLog("[INFO] File rolled to " + target.getAbsolutePath() + ", "
|
||||
+ TimeUnit.MILLISECONDS.toMinutes(System.currentTimeMillis() - lastRollOverTime)
|
||||
+ " minutes since last roll");
|
||||
} else {
|
||||
doSelfLog("[WARN] Fail to rename file: " + file.getAbsolutePath() + " to "
|
||||
+ target.getAbsolutePath());
|
||||
}
|
||||
|
||||
setFile();
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
doSelfLog("[ERROR] Fail rollover file: " + filePath + ", error=" + e.getMessage());
|
||||
} finally {
|
||||
isRolling.set(false);
|
||||
|
||||
if (fileLock != null) {
|
||||
try {
|
||||
fileLock.release();
|
||||
} catch (IOException e) {
|
||||
doSelfLog("[ERROR] Fail to release file lock: " + lockFilePath + ", error=" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (raf != null) {
|
||||
try {
|
||||
raf.close();
|
||||
} catch (IOException e) {
|
||||
doSelfLog("[WARN] Fail to close file lock: " + lockFilePath + ", error=" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (fileLock != null) {
|
||||
if (!lockFile.delete() && lockFile.exists()) {
|
||||
doSelfLog("[WARN] Fail to delete file lock: " + lockFilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
BufferedOutputStream bos = this.bos;
|
||||
if (bos != null) {
|
||||
try {
|
||||
bos.close();
|
||||
} catch (IOException e) {
|
||||
doSelfLog("[WARN] Fail to close OutputStream: " + e.getMessage());
|
||||
}
|
||||
this.bos = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reload() {
|
||||
flush();
|
||||
File logFile = new File(filePath);
|
||||
long fileSize = logFile.length();
|
||||
boolean fileNotExists = fileSize <= 0 && !logFile.exists();
|
||||
|
||||
if (this.bos == null || fileSize < outputByteSize || fileNotExists) {
|
||||
doSelfLog("[INFO] Log file rolled over by outside: " + filePath + ", force reload");
|
||||
close();
|
||||
setFile();
|
||||
} else if (fileSize > outputByteSize) {
|
||||
this.outputByteSize = fileSize;
|
||||
if (!this.multiProcessDetected) {
|
||||
this.multiProcessDetected = true;
|
||||
if (selfLogEnabled) {
|
||||
doSelfLog("[WARN] Multi-process file write detected: " + filePath);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
try {
|
||||
File logFile = new File(filePath);
|
||||
File parentDir = logFile.getParentFile();
|
||||
if (parentDir != null && parentDir.isDirectory()) {
|
||||
final String baseFileName = logFile.getName();
|
||||
File[] filesToDelete = parentDir.listFiles(new FilenameFilter() {
|
||||
@Override
|
||||
public boolean accept(File dir, String name) {
|
||||
if (name != null && name.startsWith(baseFileName) && name.endsWith(DELETE_FILE_SUFFIX)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (filesToDelete != null && filesToDelete.length > 0) {
|
||||
for (File f : filesToDelete) {
|
||||
boolean success = f.delete() || !f.exists();
|
||||
if (success) {
|
||||
doSelfLog("[INFO] Deleted log file: " + f.getAbsolutePath());
|
||||
} else if (f.exists()) {
|
||||
doSelfLog("[ERROR] Fail to delete log file: " + f.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
doSelfLog("[ERROR] Fail to cleanup log file, error=" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
void waitUntilRollFinish() {
|
||||
while (isRolling.get()) {
|
||||
try {
|
||||
Thread.sleep(1L);
|
||||
} catch (Exception e) {
|
||||
// quietly
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void doSelfLog(String log) {
|
||||
if (selfLogEnabled) {
|
||||
EagleEye.selfLog(log);
|
||||
} else {
|
||||
System.out.println("[EagleEye]" + log);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOutputLocation() {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EagleEyeRollingFileAppender [filePath=" + filePath + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.eagleeye;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
class FastDateFormat {
|
||||
|
||||
private final SimpleDateFormat fmt = createSimpleDateFormat();
|
||||
|
||||
private char[] buffer = new char[23];
|
||||
|
||||
private long lastSecond = -1;
|
||||
private long lastMillis = -1;
|
||||
|
||||
public String format(long timestamp) {
|
||||
formatToBuffer(timestamp);
|
||||
return new String(buffer, 0, 23);
|
||||
}
|
||||
|
||||
public String format(Date date) {
|
||||
return format(date.getTime());
|
||||
}
|
||||
|
||||
public void formatAndAppendTo(long timestamp, StringBuilder appender) {
|
||||
formatToBuffer(timestamp);
|
||||
appender.append(buffer, 0, 23);
|
||||
}
|
||||
|
||||
private void formatToBuffer(long timestamp) {
|
||||
if (timestamp == lastMillis) {
|
||||
return;
|
||||
}
|
||||
long diff = timestamp - lastSecond;
|
||||
if (diff >= 0 && diff < 1000) {
|
||||
int ms = (int)(timestamp % 1000);
|
||||
buffer[22] = (char)(ms % 10 + '0');
|
||||
ms /= 10;
|
||||
buffer[21] = (char)(ms % 10 + '0');
|
||||
buffer[20] = (char)(ms / 10 + '0');
|
||||
lastMillis = timestamp;
|
||||
} else {
|
||||
String result = fmt.format(new Date(timestamp));
|
||||
result.getChars(0, result.length(), buffer, 0);
|
||||
lastSecond = timestamp / 1000 * 1000;
|
||||
lastMillis = timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
String formatWithoutMs(long timestamp) {
|
||||
long diff = timestamp - lastSecond;
|
||||
if (diff < 0 || diff >= 1000) {
|
||||
String result = fmt.format(new Date(timestamp));
|
||||
result.getChars(0, result.length(), buffer, 0);
|
||||
lastSecond = timestamp / 1000 * 1000;
|
||||
lastMillis = timestamp;
|
||||
}
|
||||
return new String(buffer, 0, 19);
|
||||
}
|
||||
|
||||
private SimpleDateFormat createSimpleDateFormat() {
|
||||
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
|
||||
fmt.setTimeZone(TimeZone.getDefault());
|
||||
return fmt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.eagleeye;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public final class StatEntry {
|
||||
|
||||
private final StatLogger statLogger;
|
||||
|
||||
private final String[] keys;
|
||||
private transient int hash;
|
||||
|
||||
public StatEntry(StatLogger statLogger, String key) {
|
||||
this.statLogger = statLogger;
|
||||
this.keys = new String[] {key};
|
||||
}
|
||||
|
||||
public StatEntry(StatLogger statLogger, String key1, String key2) {
|
||||
this.statLogger = statLogger;
|
||||
this.keys = new String[] {key1, key2};
|
||||
}
|
||||
|
||||
public StatEntry(StatLogger statLogger, String key1, String key2, String key3) {
|
||||
this.statLogger = statLogger;
|
||||
this.keys = new String[] {key1, key2, key3};
|
||||
}
|
||||
|
||||
public StatEntry(StatLogger statLogger, String key1, String key2, String key3, String key4) {
|
||||
this.statLogger = statLogger;
|
||||
this.keys = new String[] {key1, key2, key3, key4};
|
||||
}
|
||||
|
||||
public StatEntry(StatLogger statLogger, String key1, String key2, String key3, String key4, String key5) {
|
||||
this.statLogger = statLogger;
|
||||
this.keys = new String[] {key1, key2, key3, key4, key5};
|
||||
}
|
||||
|
||||
public StatEntry(StatLogger statLogger, String key1, String key2, String key3, String key4, String key5,
|
||||
String key6) {
|
||||
this.statLogger = statLogger;
|
||||
this.keys = new String[] {key1, key2, key3, key4, key5, key6};
|
||||
}
|
||||
|
||||
public StatEntry(StatLogger statLogger, String key1, String key2, String key3, String key4, String key5,
|
||||
String key6, String key7) {
|
||||
this.statLogger = statLogger;
|
||||
this.keys = new String[] {key1, key2, key3, key4, key5, key6, key7};
|
||||
}
|
||||
|
||||
public StatEntry(StatLogger statLogger, String key1, String key2, String key3, String key4, String key5,
|
||||
String key6, String key7, String key8) {
|
||||
this.statLogger = statLogger;
|
||||
this.keys = new String[] {key1, key2, key3, key4, key5, key6, key7, key8};
|
||||
}
|
||||
|
||||
public StatEntry(StatLogger statLogger, String key1, String... moreKeys) {
|
||||
String[] keys = new String[1 + moreKeys.length];
|
||||
keys[0] = key1;
|
||||
for (int i = 0; i < moreKeys.length; ++i) {
|
||||
keys[i + 1] = moreKeys[i];
|
||||
}
|
||||
this.statLogger = statLogger;
|
||||
this.keys = keys;
|
||||
}
|
||||
|
||||
public StatEntry(StatLogger statLogger, List<String> keys) {
|
||||
if (keys == null || keys.isEmpty()) {
|
||||
throw new IllegalArgumentException("keys empty or null: " + keys);
|
||||
}
|
||||
this.statLogger = statLogger;
|
||||
this.keys = keys.toArray(new String[keys.size()]);
|
||||
}
|
||||
|
||||
public StatEntry(StatLogger statLogger, String[] keys) {
|
||||
if (keys == null || keys.length == 0) {
|
||||
throw new IllegalArgumentException("keys empty or null");
|
||||
}
|
||||
this.statLogger = statLogger;
|
||||
this.keys = Arrays.copyOf(keys, keys.length);
|
||||
}
|
||||
|
||||
public String[] getKeys() {
|
||||
return keys;
|
||||
}
|
||||
|
||||
void appendTo(StringBuilder appender, char delimiter) {
|
||||
final int len = keys.length;
|
||||
if (len > 0) {
|
||||
appender.append(keys[0]);
|
||||
for (int i = 1; i < len; ++i) {
|
||||
appender.append(delimiter).append(keys[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder(64);
|
||||
sb.append("StatKeys [");
|
||||
appendTo(sb, ',');
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
if (hash == 0) {
|
||||
int result = 1;
|
||||
result = 31 * result + Arrays.hashCode(keys);
|
||||
hash = result;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
StatEntry other = (StatEntry)obj;
|
||||
if (hash != 0 && other.hash != 0 && hash != other.hash) {
|
||||
return false;
|
||||
}
|
||||
if (!Arrays.equals(keys, other.keys)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
StatEntryFunc getFunc(final StatEntryFuncFactory factory) {
|
||||
return this.statLogger.getRollingData().getStatEntryFunc(this, factory);
|
||||
}
|
||||
|
||||
public void count() {
|
||||
count(1);
|
||||
}
|
||||
|
||||
public void count(long count) {
|
||||
getFunc(StatEntryFuncFactory.COUNT_SUM).count(count);
|
||||
}
|
||||
|
||||
public void countAndSum(long valueToSum) {
|
||||
countAndSum(1, valueToSum);
|
||||
}
|
||||
|
||||
public void countAndSum(long count, long valueToSum) {
|
||||
getFunc(StatEntryFuncFactory.COUNT_SUM).countAndSum(count, valueToSum);
|
||||
}
|
||||
|
||||
public void minMax(long candidate) {
|
||||
minMax(candidate, null);
|
||||
}
|
||||
|
||||
public void minMax(long candidate, String ref) {
|
||||
getFunc(StatEntryFuncFactory.MIN_MAX).minMax(candidate, ref);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.eagleeye;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.atomic.LongAdder;
|
||||
|
||||
interface StatEntryFunc {
|
||||
|
||||
void appendTo(StringBuilder appender, char delimiter);
|
||||
|
||||
int getStatType();
|
||||
|
||||
Object[] getValues();
|
||||
|
||||
void count(long count);
|
||||
|
||||
void countAndSum(long count, long value);
|
||||
|
||||
void arrayAdd(long... values);
|
||||
|
||||
void arraySet(long... values);
|
||||
|
||||
void minMax(long candidate, String ref);
|
||||
|
||||
void batchAdd(long... values);
|
||||
|
||||
void strArray(String... values);
|
||||
}
|
||||
|
||||
enum StatEntryFuncFactory {
|
||||
COUNT_SUM {
|
||||
@Override
|
||||
StatEntryFunc create() {
|
||||
return new StatEntryFuncCountAndSum();
|
||||
}
|
||||
},
|
||||
MIN_MAX {
|
||||
@Override
|
||||
StatEntryFunc create() {
|
||||
return new StatEntryFuncMinMax();
|
||||
}
|
||||
};
|
||||
|
||||
abstract StatEntryFunc create();
|
||||
}
|
||||
|
||||
class StatEntryFuncCountAndSum implements StatEntryFunc {
|
||||
|
||||
private LongAdder count = new LongAdder();
|
||||
private LongAdder value = new LongAdder();
|
||||
|
||||
@Override
|
||||
public void appendTo(StringBuilder appender, char delimiter) {
|
||||
appender.append(count.sum()).append(delimiter).append(value.sum());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] getValues() {
|
||||
return new Object[] {count.sum(), value.sum()};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStatType() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void count(long count) {
|
||||
this.count.add(count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void countAndSum(long count, long value) {
|
||||
this.count.add(count);
|
||||
this.value.add(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void arrayAdd(long... values) {
|
||||
throw new IllegalStateException("arrayAdd() is unavailable if countAndSum() has been called");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void arraySet(long... values) {
|
||||
throw new IllegalStateException("arraySet() is unavailable if countAndSum() has been called");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void minMax(long candidate, String ref) {
|
||||
throw new IllegalStateException("minMax() is unavailable if countAndSum() has been called");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void batchAdd(long... values) {
|
||||
throw new IllegalStateException("batchAdd() is unavailable if countAndSum() has been called");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void strArray(String... values) {
|
||||
throw new IllegalStateException("strArray() is unavailable if countAndSum() has been called");
|
||||
}
|
||||
}
|
||||
|
||||
class StatEntryFuncMinMax implements StatEntryFunc {
|
||||
|
||||
private AtomicReference<ValueRef> max = new AtomicReference<ValueRef>(new ValueRef(Long.MIN_VALUE, null));
|
||||
private AtomicReference<ValueRef> min = new AtomicReference<ValueRef>(new ValueRef(Long.MAX_VALUE, null));
|
||||
|
||||
@Override
|
||||
public void appendTo(StringBuilder appender, char delimiter) {
|
||||
ValueRef lmax = max.get();
|
||||
ValueRef lmin = min.get();
|
||||
|
||||
appender.append(lmax.value).append(delimiter);
|
||||
if (lmax.ref != null) {
|
||||
appender.append(lmax.ref);
|
||||
}
|
||||
appender.append(delimiter);
|
||||
|
||||
appender.append(lmin.value).append(delimiter);
|
||||
if (lmin.ref != null) {
|
||||
appender.append(lmin.ref);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] getValues() {
|
||||
ValueRef lmax = max.get();
|
||||
ValueRef lmin = min.get();
|
||||
return new Object[] {lmax.value, lmax.ref, lmin.value, lmin.ref};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStatType() {
|
||||
return 4;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void count(long count) {
|
||||
throw new IllegalStateException("count() is unavailable if minMax() has been called");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void countAndSum(long count, long value) {
|
||||
throw new IllegalStateException("countAndSum() is unavailable if minMax() has been called");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void arrayAdd(long... values) {
|
||||
throw new IllegalStateException("arrayAdd() is unavailable if minMax() has been called");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void arraySet(long... values) {
|
||||
throw new IllegalStateException("arraySet() is unavailable if minMax() has been called");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void batchAdd(long... values) {
|
||||
throw new IllegalStateException("batchAdd() is unavailable if minMax() has been called");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void minMax(long candidate, String ref) {
|
||||
ValueRef lmax = max.get();
|
||||
if (lmax.value <= candidate) {
|
||||
final ValueRef cmax = new ValueRef(candidate, ref);
|
||||
while (!max.compareAndSet(lmax, cmax) && (lmax = max.get()).value <= candidate) { ; }
|
||||
}
|
||||
ValueRef lmin = min.get();
|
||||
if (lmin.value >= candidate) {
|
||||
final ValueRef cmin = new ValueRef(candidate, ref);
|
||||
while (!min.compareAndSet(lmin, cmin) && (lmin = min.get()).value >= candidate) { ; }
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void strArray(String... values) {
|
||||
throw new IllegalStateException("strArray() is unavailable if minMax() has been called");
|
||||
}
|
||||
|
||||
private static final class ValueRef {
|
||||
final long value;
|
||||
final String ref;
|
||||
|
||||
ValueRef(long value, String ref) {
|
||||
this.value = value;
|
||||
this.ref = ref;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.eagleeye;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory;
|
||||
|
||||
class StatLogController {
|
||||
|
||||
private static final Map<String, StatLogger> statLoggers = new ConcurrentHashMap<String, StatLogger>();
|
||||
|
||||
private static final int STAT_ENTRY_COOL_DOWN_MILLIS = 200;
|
||||
|
||||
private static final ScheduledThreadPoolExecutor rollerThreadPool =
|
||||
new ScheduledThreadPoolExecutor(1, new NamedThreadFactory(
|
||||
"EagleEye-StatLogController-roller", true));
|
||||
|
||||
private static final ScheduledThreadPoolExecutor writerThreadPool =
|
||||
new ScheduledThreadPoolExecutor(1, new NamedThreadFactory(
|
||||
"EagleEye-StatLogController-writer", true));
|
||||
|
||||
private static AtomicBoolean running = new AtomicBoolean(false);
|
||||
|
||||
static StatLogger createLoggerIfNotExists(StatLoggerBuilder builder) {
|
||||
String loggerName = builder.getLoggerName();
|
||||
StatLogger statLogger = statLoggers.get(loggerName);
|
||||
if (statLogger == null) {
|
||||
synchronized (StatLogController.class) {
|
||||
if ((statLogger = statLoggers.get(loggerName)) == null) {
|
||||
statLogger = builder.create();
|
||||
statLoggers.put(loggerName, statLogger);
|
||||
|
||||
writerThreadPool.setMaximumPoolSize(Math.max(1, statLoggers.size()));
|
||||
|
||||
scheduleNextRollingTask(statLogger);
|
||||
EagleEye.selfLog("[INFO] created statLogger[" + statLogger.getLoggerName() +
|
||||
"]: " + statLogger.getAppender());
|
||||
}
|
||||
}
|
||||
}
|
||||
return statLogger;
|
||||
}
|
||||
|
||||
static Map<String, StatLogger> getAllStatLoggers() {
|
||||
return Collections.unmodifiableMap(statLoggers);
|
||||
}
|
||||
|
||||
private static void scheduleNextRollingTask(StatLogger statLogger) {
|
||||
if (!running.get()) {
|
||||
EagleEye.selfLog("[INFO] stopped rolling statLogger[" + statLogger.getLoggerName() + "]");
|
||||
return;
|
||||
}
|
||||
|
||||
StatLogRollingTask rollingTask = new StatLogRollingTask(statLogger);
|
||||
|
||||
long rollingTimeMillis = statLogger.getRollingData().getRollingTimeMillis();
|
||||
long delayMillis = rollingTimeMillis - System.currentTimeMillis();
|
||||
if (delayMillis > 5) {
|
||||
rollerThreadPool.schedule(rollingTask, delayMillis, TimeUnit.MILLISECONDS);
|
||||
} else if (-delayMillis > statLogger.getIntervalMillis()) {
|
||||
EagleEye.selfLog("[WARN] unusual delay of statLogger[" + statLogger.getLoggerName() +
|
||||
"], delay=" + (-delayMillis) + "ms, submit now");
|
||||
rollerThreadPool.submit(rollingTask);
|
||||
} else {
|
||||
rollerThreadPool.submit(rollingTask);
|
||||
}
|
||||
}
|
||||
|
||||
static void scheduleWriteTask(StatRollingData statRollingData) {
|
||||
if (statRollingData != null) {
|
||||
try {
|
||||
StatLogWriteTask task = new StatLogWriteTask(statRollingData);
|
||||
writerThreadPool.schedule(task, STAT_ENTRY_COOL_DOWN_MILLIS, TimeUnit.MILLISECONDS);
|
||||
} catch (Throwable t) {
|
||||
EagleEye.selfLog("[ERROR] fail to roll statLogger[" +
|
||||
statRollingData.getStatLogger().getLoggerName() + "]", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class StatLogRollingTask implements Runnable {
|
||||
|
||||
final StatLogger statLogger;
|
||||
|
||||
StatLogRollingTask(StatLogger statLogger) {
|
||||
this.statLogger = statLogger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
scheduleWriteTask(statLogger.rolling());
|
||||
scheduleNextRollingTask(statLogger);
|
||||
}
|
||||
}
|
||||
|
||||
private static class StatLogWriteTask implements Runnable {
|
||||
|
||||
final StatRollingData statRollingData;
|
||||
|
||||
StatLogWriteTask(StatRollingData statRollingData) {
|
||||
this.statRollingData = statRollingData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
final StatRollingData data = statRollingData;
|
||||
final StatLogger logger = data.getStatLogger();
|
||||
try {
|
||||
final FastDateFormat fmt = new FastDateFormat();
|
||||
final StringBuilder buffer = new StringBuilder(256);
|
||||
final String timeStr = fmt.formatWithoutMs(data.getTimeSlot());
|
||||
|
||||
final EagleEyeAppender appender = logger.getAppender();
|
||||
final Set<Entry<StatEntry, StatEntryFunc>> entrySet = data.getStatEntrySet();
|
||||
final char entryDelimiter = logger.getEntryDelimiter();
|
||||
final char keyDelimiter = logger.getKeyDelimiter();
|
||||
final char valueDelimiter = logger.getValueDelimiter();
|
||||
|
||||
for (Entry<StatEntry, StatEntryFunc> entry : entrySet) {
|
||||
buffer.delete(0, buffer.length());
|
||||
StatEntryFunc func = entry.getValue();
|
||||
// time|statType|keys|values
|
||||
buffer.append(timeStr).append(entryDelimiter);
|
||||
buffer.append(func.getStatType()).append(entryDelimiter);
|
||||
entry.getKey().appendTo(buffer, keyDelimiter);
|
||||
buffer.append(entryDelimiter);
|
||||
func.appendTo(buffer, valueDelimiter);
|
||||
buffer.append(EagleEyeCoreUtils.NEWLINE);
|
||||
appender.append(buffer.toString());
|
||||
}
|
||||
|
||||
appender.flush();
|
||||
} catch (Throwable t) {
|
||||
EagleEye.selfLog("[WARN] fail to write statLogger[" +
|
||||
logger.getLoggerName() + "]", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void start() {
|
||||
if (running.compareAndSet(false, true)) {
|
||||
rollerThreadPool.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
|
||||
writerThreadPool.setExecuteExistingDelayedTasksAfterShutdownPolicy(true);
|
||||
}
|
||||
}
|
||||
|
||||
static void stop() {
|
||||
if (running.compareAndSet(true, false)) {
|
||||
EagleEyeCoreUtils.shutdownThreadPool(rollerThreadPool, 0);
|
||||
EagleEye.selfLog("[INFO] StatLoggerController: roller ThreadPool shutdown successfully");
|
||||
|
||||
for (StatLogger statLogger : statLoggers.values()) {
|
||||
new StatLogRollingTask(statLogger).run();
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(STAT_ENTRY_COOL_DOWN_MILLIS);
|
||||
} catch (InterruptedException e) {
|
||||
// quietly
|
||||
}
|
||||
|
||||
EagleEyeCoreUtils.shutdownThreadPool(writerThreadPool, 2000);
|
||||
EagleEye.selfLog("[INFO] StatLoggerController: writer ThreadPool shutdown successfully");
|
||||
}
|
||||
}
|
||||
|
||||
private StatLogController() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.eagleeye;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* @author jifeng
|
||||
*/
|
||||
public final class StatLogger {
|
||||
|
||||
private final String loggerName;
|
||||
|
||||
private final EagleEyeAppender appender;
|
||||
|
||||
private final AtomicReference<StatRollingData> ref;
|
||||
|
||||
private final long intervalMillis;
|
||||
|
||||
private final int maxEntryCount;
|
||||
|
||||
private final char entryDelimiter;
|
||||
private final char keyDelimiter;
|
||||
private final char valueDelimiter;
|
||||
|
||||
StatLogger(String loggerName, EagleEyeAppender appender, long intervalMillis, int maxEntryCount,
|
||||
char entryDelimiter, char keyDelimiter, char valueDelimiter) {
|
||||
this.loggerName = loggerName;
|
||||
this.appender = appender;
|
||||
this.intervalMillis = intervalMillis;
|
||||
this.maxEntryCount = maxEntryCount;
|
||||
this.entryDelimiter = entryDelimiter;
|
||||
this.keyDelimiter = keyDelimiter;
|
||||
this.valueDelimiter = valueDelimiter;
|
||||
this.ref = new AtomicReference<StatRollingData>();
|
||||
rolling();
|
||||
}
|
||||
|
||||
public String getLoggerName() {
|
||||
return loggerName;
|
||||
}
|
||||
|
||||
EagleEyeAppender getAppender() {
|
||||
return appender;
|
||||
}
|
||||
|
||||
StatRollingData getRollingData() {
|
||||
return ref.get();
|
||||
}
|
||||
|
||||
long getIntervalMillis() {
|
||||
return intervalMillis;
|
||||
}
|
||||
|
||||
int getMaxEntryCount() {
|
||||
return maxEntryCount;
|
||||
}
|
||||
|
||||
char getEntryDelimiter() {
|
||||
return entryDelimiter;
|
||||
}
|
||||
|
||||
char getKeyDelimiter() {
|
||||
return keyDelimiter;
|
||||
}
|
||||
|
||||
char getValueDelimiter() {
|
||||
return valueDelimiter;
|
||||
}
|
||||
|
||||
StatRollingData rolling() {
|
||||
do {
|
||||
long now = System.currentTimeMillis();
|
||||
long timeSlot = now - now % intervalMillis;
|
||||
|
||||
StatRollingData prevData = ref.get();
|
||||
long rollingTimeMillis = timeSlot + intervalMillis;
|
||||
int initialCapacity = prevData != null ? prevData.getStatCount() : 16;
|
||||
StatRollingData nextData = new StatRollingData(
|
||||
this, initialCapacity, timeSlot, rollingTimeMillis);
|
||||
if (ref.compareAndSet(prevData, nextData)) {
|
||||
return prevData;
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
|
||||
public StatEntry stat(String key) {
|
||||
return new StatEntry(this, key);
|
||||
}
|
||||
|
||||
public StatEntry stat(String key1, String key2) {
|
||||
return new StatEntry(this, key1, key2);
|
||||
}
|
||||
|
||||
public StatEntry stat(String key1, String key2, String key3) {
|
||||
return new StatEntry(this, key1, key2, key3);
|
||||
}
|
||||
|
||||
public StatEntry stat(String key1, String key2, String key3, String key4) {
|
||||
return new StatEntry(this, key1, key2, key3, key4);
|
||||
}
|
||||
|
||||
public StatEntry stat(String key1, String key2, String key3, String key4, String key5) {
|
||||
return new StatEntry(this, key1, key2, key3, key4, key5);
|
||||
}
|
||||
|
||||
public StatEntry stat(String key1, String key2, String key3, String key4, String key5, String key6) {
|
||||
return new StatEntry(this, key1, key2, key3, key4, key5, key6);
|
||||
}
|
||||
|
||||
public StatEntry stat(String key1, String key2, String key3, String key4, String key5, String key6, String key7) {
|
||||
return new StatEntry(this, key1, key2, key3, key4, key5, key6, key7);
|
||||
}
|
||||
|
||||
public StatEntry stat(String key1, String key2, String key3, String key4, String key5, String key6, String key7,
|
||||
String key8) {
|
||||
return new StatEntry(this, key1, key2, key3, key4, key5, key6, key7, key8);
|
||||
}
|
||||
|
||||
public StatEntry stat(String key1, String... moreKeys) {
|
||||
return new StatEntry(this, key1, moreKeys);
|
||||
}
|
||||
|
||||
public StatEntry stat(List<String> keys) {
|
||||
return new StatEntry(this, keys);
|
||||
}
|
||||
|
||||
public StatEntry stat(String[] keys) {
|
||||
return new StatEntry(this, keys);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.eagleeye;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author jifeng
|
||||
*/
|
||||
public final class StatLoggerBuilder extends BaseLoggerBuilder<StatLoggerBuilder> {
|
||||
|
||||
private int intervalSeconds = 60;
|
||||
|
||||
private int maxEntryCount = 20000;
|
||||
|
||||
private char keyDelimiter = ',';
|
||||
|
||||
private char valueDelimiter = ',';
|
||||
|
||||
private EagleEyeAppender appender = null;
|
||||
|
||||
StatLoggerBuilder(String loggerName) {
|
||||
super(loggerName);
|
||||
}
|
||||
|
||||
public StatLoggerBuilder intervalSeconds(int intervalSeconds) {
|
||||
validateInterval(intervalSeconds);
|
||||
this.intervalSeconds = intervalSeconds;
|
||||
return this;
|
||||
}
|
||||
|
||||
public StatLoggerBuilder maxEntryCount(int maxEntryCount) {
|
||||
if (maxEntryCount < 1) {
|
||||
throw new IllegalArgumentException("Max entry count should be at least 1: " + maxEntryCount);
|
||||
}
|
||||
this.maxEntryCount = maxEntryCount;
|
||||
return this;
|
||||
}
|
||||
|
||||
public StatLoggerBuilder keyDelimiter(char keyDelimiter) {
|
||||
this.keyDelimiter = keyDelimiter;
|
||||
return this;
|
||||
}
|
||||
|
||||
public StatLoggerBuilder valueDelimiter(char valueDelimiter) {
|
||||
this.valueDelimiter = valueDelimiter;
|
||||
return this;
|
||||
}
|
||||
|
||||
StatLoggerBuilder appender(EagleEyeAppender appender) {
|
||||
this.appender = appender;
|
||||
return this;
|
||||
}
|
||||
|
||||
StatLogger create() {
|
||||
long intervalMillis = TimeUnit.SECONDS.toMillis(this.intervalSeconds);
|
||||
|
||||
String filePath;
|
||||
if (this.filePath == null) {
|
||||
filePath = EagleEye.EAGLEEYE_LOG_DIR + "stat-" + loggerName + ".log";
|
||||
} else if (this.filePath.endsWith("/") || this.filePath.endsWith("\\")) {
|
||||
filePath = this.filePath + "stat-" + loggerName + ".log";
|
||||
} else {
|
||||
filePath = this.filePath;
|
||||
}
|
||||
|
||||
EagleEyeAppender appender = this.appender;
|
||||
if (appender == null) {
|
||||
EagleEyeRollingFileAppender rfAppender = new EagleEyeRollingFileAppender(filePath, maxFileSize);
|
||||
appender = new SyncAppender(rfAppender);
|
||||
}
|
||||
|
||||
EagleEyeLogDaemon.watch(appender);
|
||||
return new StatLogger(loggerName, appender, intervalMillis, maxEntryCount,
|
||||
entryDelimiter, keyDelimiter, valueDelimiter);
|
||||
}
|
||||
|
||||
public StatLogger buildSingleton() {
|
||||
return StatLogController.createLoggerIfNotExists(this);
|
||||
}
|
||||
|
||||
static void validateInterval(final long intervalSeconds) throws IllegalArgumentException {
|
||||
if (intervalSeconds < 1) {
|
||||
throw new IllegalArgumentException("Interval cannot be less than 1" + intervalSeconds);
|
||||
} else if (intervalSeconds < 60) {
|
||||
if (60 % intervalSeconds != 0) {
|
||||
throw new IllegalArgumentException("Invalid second interval (cannot divide by 60): " + intervalSeconds);
|
||||
}
|
||||
} else if (intervalSeconds <= 5 * 60) {
|
||||
if (intervalSeconds % 60 != 0) {
|
||||
throw new IllegalArgumentException("Invalid second interval (cannot divide by 60): " + intervalSeconds);
|
||||
}
|
||||
if (60 % intervalSeconds != 0) {
|
||||
throw new IllegalArgumentException("Invalid second interval (cannot divide by 60): " + intervalSeconds);
|
||||
}
|
||||
} else if (intervalSeconds > 5 * 60) {
|
||||
throw new IllegalArgumentException("Interval should be less than 5 min: " + intervalSeconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.eagleeye;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* @author jifeng
|
||||
*/
|
||||
final class StatRollingData {
|
||||
|
||||
private final StatLogger statLogger;
|
||||
|
||||
private final long timeSlot;
|
||||
|
||||
private final long rollingTimeMillis;
|
||||
|
||||
private final ReentrantLock writeLock;
|
||||
|
||||
private final Map<StatEntry, StatEntryFunc> statMap;
|
||||
|
||||
StatRollingData(StatLogger statLogger, int initialCapacity, long timeSlot, long rollingTimeMillis) {
|
||||
this(statLogger, timeSlot, rollingTimeMillis,
|
||||
new ConcurrentHashMap<StatEntry, StatEntryFunc>(
|
||||
Math.min(initialCapacity, statLogger.getMaxEntryCount())));
|
||||
}
|
||||
|
||||
private StatRollingData(StatLogger statLogger, long timeSlot, long rollingTimeMillis,
|
||||
Map<StatEntry, StatEntryFunc> statMap) {
|
||||
this.statLogger = statLogger;
|
||||
this.timeSlot = timeSlot;
|
||||
this.rollingTimeMillis = rollingTimeMillis;
|
||||
this.writeLock = new ReentrantLock();
|
||||
this.statMap = statMap;
|
||||
}
|
||||
|
||||
StatEntryFunc getStatEntryFunc(
|
||||
final StatEntry statEntry, final StatEntryFuncFactory factory) {
|
||||
StatEntryFunc func = statMap.get(statEntry);
|
||||
if (func == null) {
|
||||
StatRollingData clone = null;
|
||||
writeLock.lock();
|
||||
try {
|
||||
int entryCount = statMap.size();
|
||||
if (entryCount < statLogger.getMaxEntryCount()) {
|
||||
func = statMap.get(statEntry);
|
||||
if (func == null) {
|
||||
func = factory.create();
|
||||
statMap.put(statEntry, func);
|
||||
}
|
||||
} else {
|
||||
Map<StatEntry, StatEntryFunc> cloneStatMap =
|
||||
new HashMap<StatEntry, StatEntryFunc>(statMap);
|
||||
statMap.clear();
|
||||
|
||||
func = factory.create();
|
||||
statMap.put(statEntry, func);
|
||||
clone = new StatRollingData(statLogger, timeSlot, rollingTimeMillis, cloneStatMap);
|
||||
}
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
}
|
||||
|
||||
if (clone != null) {
|
||||
StatLogController.scheduleWriteTask(clone);
|
||||
}
|
||||
}
|
||||
return func;
|
||||
}
|
||||
|
||||
StatLogger getStatLogger() {
|
||||
return statLogger;
|
||||
}
|
||||
|
||||
long getRollingTimeMillis() {
|
||||
return rollingTimeMillis;
|
||||
}
|
||||
|
||||
long getTimeSlot() {
|
||||
return timeSlot;
|
||||
}
|
||||
|
||||
int getStatCount() {
|
||||
return statMap.size();
|
||||
}
|
||||
|
||||
Set<Entry<StatEntry, StatEntryFunc>> getStatEntrySet() {
|
||||
return statMap.entrySet();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.eagleeye;
|
||||
|
||||
/**
|
||||
* @author jifeng
|
||||
*/
|
||||
final class SyncAppender extends EagleEyeAppender {
|
||||
|
||||
private final EagleEyeAppender delegate;
|
||||
private final Object lock = new Object();
|
||||
|
||||
public SyncAppender(EagleEyeAppender delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void append(String log) {
|
||||
synchronized (lock) {
|
||||
delegate.append(log);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
synchronized (lock) {
|
||||
delegate.flush();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollOver() {
|
||||
synchronized (lock) {
|
||||
delegate.rollOver();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reload() {
|
||||
synchronized (lock) {
|
||||
delegate.reload();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
synchronized (lock) {
|
||||
delegate.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
delegate.cleanup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOutputLocation() {
|
||||
return delegate.getOutputLocation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SyncAppender [appender=" + delegate + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.eagleeye;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
class TokenBucket {
|
||||
|
||||
private final long maxTokens;
|
||||
|
||||
private final long intervalMillis;
|
||||
|
||||
private volatile long nextUpdate;
|
||||
|
||||
private AtomicLong tokens;
|
||||
|
||||
public TokenBucket(long maxTokens, long intervalMillis) {
|
||||
if (maxTokens <= 0) {
|
||||
throw new IllegalArgumentException("maxTokens should > 0, but given: " + maxTokens);
|
||||
}
|
||||
if (intervalMillis < 1000) {
|
||||
throw new IllegalArgumentException("intervalMillis should be at least 1000, but given: " + intervalMillis);
|
||||
}
|
||||
this.maxTokens = maxTokens;
|
||||
this.intervalMillis = intervalMillis;
|
||||
this.nextUpdate = System.currentTimeMillis() / 1000 * 1000 + intervalMillis;
|
||||
this.tokens = new AtomicLong(maxTokens);
|
||||
}
|
||||
|
||||
public boolean accept(long now) {
|
||||
long currTokens;
|
||||
if (now > nextUpdate) {
|
||||
currTokens = tokens.get();
|
||||
if (tokens.compareAndSet(currTokens, maxTokens)) {
|
||||
nextUpdate = System.currentTimeMillis() / 1000 * 1000 + intervalMillis;
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
currTokens = tokens.get();
|
||||
} while (currTokens > 0 && !tokens.compareAndSet(currTokens, currTokens - 1));
|
||||
|
||||
return currTokens > 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.init;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.spi.SpiLoader;
|
||||
|
||||
/**
|
||||
* Load registered init functions and execute in order.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public final class InitExecutor {
|
||||
|
||||
private static AtomicBoolean initialized = new AtomicBoolean(false);
|
||||
|
||||
/**
|
||||
* If one {@link InitFunc} throws an exception, the init process
|
||||
* will immediately be interrupted and the application will exit.
|
||||
*
|
||||
* The initialization will be executed only once.
|
||||
*/
|
||||
public static void doInit() {
|
||||
if (!initialized.compareAndSet(false, true)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
List<InitFunc> initFuncs = SpiLoader.of(InitFunc.class).loadInstanceListSorted();
|
||||
List<OrderWrapper> initList = new ArrayList<OrderWrapper>();
|
||||
for (InitFunc initFunc : initFuncs) {
|
||||
RecordLog.info("[InitExecutor] Found init func: {}", initFunc.getClass().getCanonicalName());
|
||||
insertSorted(initList, initFunc);
|
||||
}
|
||||
for (OrderWrapper w : initList) {
|
||||
w.func.init();
|
||||
RecordLog.info("[InitExecutor] Executing {} with order {}",
|
||||
w.func.getClass().getCanonicalName(), w.order);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
RecordLog.warn("[InitExecutor] WARN: Initialization failed", ex);
|
||||
ex.printStackTrace();
|
||||
} catch (Error error) {
|
||||
RecordLog.warn("[InitExecutor] ERROR: Initialization failed with fatal error", error);
|
||||
error.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static void insertSorted(List<OrderWrapper> list, InitFunc func) {
|
||||
int order = resolveOrder(func);
|
||||
int idx = 0;
|
||||
for (; idx < list.size(); idx++) {
|
||||
if (list.get(idx).getOrder() > order) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
list.add(idx, new OrderWrapper(order, func));
|
||||
}
|
||||
|
||||
private static int resolveOrder(InitFunc func) {
|
||||
if (!func.getClass().isAnnotationPresent(InitOrder.class)) {
|
||||
return InitOrder.LOWEST_PRECEDENCE;
|
||||
} else {
|
||||
return func.getClass().getAnnotation(InitOrder.class).value();
|
||||
}
|
||||
}
|
||||
|
||||
private InitExecutor() {}
|
||||
|
||||
private static class OrderWrapper {
|
||||
private final int order;
|
||||
private final InitFunc func;
|
||||
|
||||
OrderWrapper(int order, InitFunc func) {
|
||||
this.order = order;
|
||||
this.func = func;
|
||||
}
|
||||
|
||||
int getOrder() {
|
||||
return order;
|
||||
}
|
||||
|
||||
InitFunc getFunc() {
|
||||
return func;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.init;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public interface InitFunc {
|
||||
|
||||
void init() throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.init;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.TYPE})
|
||||
@Documented
|
||||
public @interface InitOrder {
|
||||
|
||||
int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
|
||||
int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;
|
||||
|
||||
/**
|
||||
* The order value. Lowest precedence by default.
|
||||
*
|
||||
* @return the order value
|
||||
*/
|
||||
int value() default LOWEST_PRECEDENCE;
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.log;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Properties;
|
||||
|
||||
import static com.alibaba.csp.sentinel.util.ConfigUtil.addSeparator;
|
||||
|
||||
/**
|
||||
* <p>The base config class for logging.</p>
|
||||
*
|
||||
* <p>
|
||||
* The default log base directory is {@code ${user.home}/logs/csp/}. We can use the {@link #LOG_DIR}
|
||||
* property to override it. The default log file name dose not contain pid, but if multi-instances of the same service
|
||||
* are running in the same machine, we may want to distinguish the log file by process ID number.
|
||||
* In this case, {@link #LOG_NAME_USE_PID} property could be configured as "true" to turn on this switch.
|
||||
* </p>
|
||||
*
|
||||
* @author Carpenter Lee
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public class LogBase {
|
||||
|
||||
public static final String LOG_DIR = "csp.sentinel.log.dir";
|
||||
public static final String LOG_NAME_USE_PID = "csp.sentinel.log.use.pid";
|
||||
public static final String LOG_OUTPUT_TYPE = "csp.sentinel.log.output.type";
|
||||
public static final String LOG_CHARSET = "csp.sentinel.log.charset";
|
||||
|
||||
/**
|
||||
* Output biz log (e.g. RecordLog and CommandCenterLog) to file.
|
||||
*/
|
||||
public static final String LOG_OUTPUT_TYPE_FILE = "file";
|
||||
/**
|
||||
* Output biz log (e.g. RecordLog and CommandCenterLog) to console.
|
||||
*/
|
||||
public static final String LOG_OUTPUT_TYPE_CONSOLE = "console";
|
||||
public static final String LOG_CHARSET_UTF8 = "utf-8";
|
||||
|
||||
private static final String DIR_NAME = "logs" + File.separator + "csp";
|
||||
private static final String USER_HOME = "user.home";
|
||||
|
||||
|
||||
private static boolean logNameUsePid;
|
||||
private static String logOutputType;
|
||||
private static String logBaseDir;
|
||||
private static String logCharSet;
|
||||
|
||||
static {
|
||||
try {
|
||||
initializeDefault();
|
||||
loadProperties();
|
||||
} catch (Throwable t) {
|
||||
System.err.println("[LogBase] FATAL ERROR when initializing logging config");
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static void initializeDefault() {
|
||||
logNameUsePid = false;
|
||||
logOutputType = LOG_OUTPUT_TYPE_FILE;
|
||||
logBaseDir = addSeparator(System.getProperty(USER_HOME)) + DIR_NAME + File.separator;
|
||||
logCharSet = LOG_CHARSET_UTF8;
|
||||
}
|
||||
|
||||
private static void loadProperties() {
|
||||
Properties properties = LogConfigLoader.getProperties();
|
||||
|
||||
logOutputType = properties.get(LOG_OUTPUT_TYPE) == null ? logOutputType : properties.getProperty(LOG_OUTPUT_TYPE);
|
||||
if (!LOG_OUTPUT_TYPE_FILE.equalsIgnoreCase(logOutputType) && !LOG_OUTPUT_TYPE_CONSOLE.equalsIgnoreCase(logOutputType)) {
|
||||
logOutputType = LOG_OUTPUT_TYPE_FILE;
|
||||
}
|
||||
System.out.println("INFO: Sentinel log output type is: " + logOutputType);
|
||||
|
||||
logCharSet = properties.getProperty(LOG_CHARSET) == null ? logCharSet : properties.getProperty(LOG_CHARSET);
|
||||
System.out.println("INFO: Sentinel log charset is: " + logCharSet);
|
||||
|
||||
|
||||
logBaseDir = properties.getProperty(LOG_DIR) == null ? logBaseDir : properties.getProperty(LOG_DIR);
|
||||
logBaseDir = addSeparator(logBaseDir);
|
||||
File dir = new File(logBaseDir);
|
||||
if (!dir.exists()) {
|
||||
if (!dir.mkdirs()) {
|
||||
System.err.println("ERROR: create Sentinel log base directory error: " + logBaseDir);
|
||||
}
|
||||
}
|
||||
System.out.println("INFO: Sentinel log base directory is: " + logBaseDir);
|
||||
|
||||
String usePid = properties.getProperty(LOG_NAME_USE_PID);
|
||||
logNameUsePid = "true".equalsIgnoreCase(usePid);
|
||||
System.out.println("INFO: Sentinel log name use pid is: " + logNameUsePid);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Whether log file name should contain pid. This switch is configured by {@link #LOG_NAME_USE_PID} system property.
|
||||
*
|
||||
* @return true if log file name should contain pid, return true, otherwise false
|
||||
*/
|
||||
public static boolean isLogNameUsePid() {
|
||||
return logNameUsePid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the log file base directory path, which is guaranteed ended with {@link File#separator}.
|
||||
*
|
||||
* @return log file base directory path
|
||||
*/
|
||||
public static String getLogBaseDir() {
|
||||
return logBaseDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the log file output type.
|
||||
*
|
||||
* @return log output type, "file" by default
|
||||
*/
|
||||
public static String getLogOutputType() {
|
||||
return logOutputType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the log file charset.
|
||||
*
|
||||
* @return the log file charset, "utf-8" by default
|
||||
*/
|
||||
public static String getLogCharset() {
|
||||
return logCharSet;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.log;
|
||||
|
||||
import com.alibaba.csp.sentinel.util.ConfigUtil;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
/**
|
||||
* <p>The loader that responsible for loading Sentinel log configurations.</p>
|
||||
*
|
||||
* @author lianglin
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public class LogConfigLoader {
|
||||
|
||||
public static final String LOG_CONFIG_ENV_KEY = "CSP_SENTINEL_CONFIG_FILE";
|
||||
public static final String LOG_CONFIG_PROPERTY_KEY = "csp.sentinel.config.file";
|
||||
|
||||
private static final String DEFAULT_LOG_CONFIG_FILE = "classpath:sentinel.properties";
|
||||
|
||||
private static final Properties properties = new Properties();
|
||||
|
||||
static {
|
||||
try {
|
||||
load();
|
||||
} catch (Throwable t) {
|
||||
// NOTE: do not use RecordLog here, or there will be circular class dependency!
|
||||
System.err.println("[LogConfigLoader] Failed to initialize configuration items");
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static void load() {
|
||||
// Order: system property -> system env -> default file (classpath:sentinel.properties) -> legacy path
|
||||
String fileName = System.getProperty(LOG_CONFIG_PROPERTY_KEY);
|
||||
if (StringUtil.isBlank(fileName)) {
|
||||
fileName = System.getenv(LOG_CONFIG_ENV_KEY);
|
||||
if (StringUtil.isBlank(fileName)) {
|
||||
fileName = DEFAULT_LOG_CONFIG_FILE;
|
||||
}
|
||||
}
|
||||
|
||||
Properties p = ConfigUtil.loadProperties(fileName);
|
||||
if (p != null && !p.isEmpty()) {
|
||||
properties.putAll(p);
|
||||
}
|
||||
|
||||
CopyOnWriteArraySet<Map.Entry<Object, Object>> copy = new CopyOnWriteArraySet<>(System.getProperties().entrySet());
|
||||
for (Map.Entry<Object, Object> entry : copy) {
|
||||
String configKey = entry.getKey().toString();
|
||||
String newConfigValue = entry.getValue().toString();
|
||||
properties.put(configKey, newConfigValue);
|
||||
}
|
||||
}
|
||||
|
||||
public static Properties getProperties() {
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.alibaba.csp.sentinel.log;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* @author xue8
|
||||
* @since 1.7.2
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@Documented
|
||||
public @interface LogTarget {
|
||||
/**
|
||||
* Returns the logger name.
|
||||
*
|
||||
* @return the logger name. Record logger by default
|
||||
*/
|
||||
String value() default RecordLog.LOGGER_NAME;
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.alibaba.csp.sentinel.log;
|
||||
|
||||
/**
|
||||
* <p>The universal logger SPI interface.</p>
|
||||
* <p>Notice: the placeholder only supports the most popular placeholder convention (slf4j).
|
||||
* So, if you're not using slf4j, you should create adapters compatible with placeholders "{}".</p>
|
||||
*
|
||||
* @author xue8
|
||||
* @since 1.7.2
|
||||
*/
|
||||
public interface Logger {
|
||||
|
||||
/**
|
||||
* Log a message at the INFO level according to the specified format
|
||||
* and arguments.
|
||||
*
|
||||
* @param format the format string
|
||||
* @param arguments a list of arguments
|
||||
*/
|
||||
void info(String format, Object... arguments);
|
||||
|
||||
/**
|
||||
* Log an exception (throwable) at the INFO level with an
|
||||
* accompanying message.
|
||||
*
|
||||
* @param msg the message accompanying the exception
|
||||
* @param e the exception (throwable) to log
|
||||
*/
|
||||
void info(String msg, Throwable e);
|
||||
|
||||
/**
|
||||
* Log a message at the WARN level according to the specified format
|
||||
* and arguments.
|
||||
*
|
||||
* @param format the format string
|
||||
* @param arguments a list of arguments
|
||||
*/
|
||||
void warn(String format, Object... arguments);
|
||||
|
||||
/**
|
||||
* Log an exception (throwable) at the WARN level with an
|
||||
* accompanying message.
|
||||
*
|
||||
* @param msg the message accompanying the exception
|
||||
* @param e the exception (throwable) to log
|
||||
*/
|
||||
void warn(String msg, Throwable e);
|
||||
|
||||
/**
|
||||
* Log a message at the TRACE level according to the specified format
|
||||
* and arguments.
|
||||
*
|
||||
* @param format the format string
|
||||
* @param arguments a list of arguments
|
||||
*/
|
||||
void trace(String format, Object... arguments);
|
||||
|
||||
/**
|
||||
* Log an exception (throwable) at the TRACE level with an
|
||||
* accompanying message.
|
||||
*
|
||||
* @param msg the message accompanying the exception
|
||||
* @param e the exception (throwable) to log
|
||||
*/
|
||||
void trace(String msg, Throwable e);
|
||||
|
||||
/**
|
||||
* Log a message at the DEBUG level according to the specified format
|
||||
* and arguments.
|
||||
*
|
||||
* @param format the format string
|
||||
* @param arguments a list of arguments
|
||||
*/
|
||||
void debug(String format, Object... arguments);
|
||||
|
||||
/**
|
||||
* Log an exception (throwable) at the DEBUG level with an
|
||||
* accompanying message.
|
||||
*
|
||||
* @param msg the message accompanying the exception
|
||||
* @param e the exception (throwable) to log
|
||||
*/
|
||||
void debug(String msg, Throwable e);
|
||||
|
||||
/**
|
||||
* Log a message at the ERROR level according to the specified format
|
||||
* and arguments.
|
||||
*
|
||||
* @param format the format string
|
||||
* @param arguments a list of arguments
|
||||
*/
|
||||
void error(String format, Object... arguments);
|
||||
|
||||
/**
|
||||
* Log an exception (throwable) at the ERROR level with an
|
||||
* accompanying message.
|
||||
*
|
||||
* @param msg the message accompanying the exception
|
||||
* @param e the exception (throwable) to log
|
||||
*/
|
||||
void error(String msg, Throwable e);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 1999-2020 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.alibaba.csp.sentinel.log;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
|
||||
/**
|
||||
* SPI provider of Sentinel {@link Logger}.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 1.7.2
|
||||
*/
|
||||
public final class LoggerSpiProvider {
|
||||
|
||||
private static final Map<String, Logger> LOGGER_MAP = new HashMap<>();
|
||||
|
||||
static {
|
||||
// NOTE: this class SHOULD NOT depend on any other Sentinel classes
|
||||
// except the util classes to avoid circular dependency.
|
||||
try {
|
||||
resolveLoggers();
|
||||
} catch (Throwable t) {
|
||||
System.err.println("Failed to resolve Sentinel Logger SPI");
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static Logger getLogger(String name) {
|
||||
if (name == null) {
|
||||
return null;
|
||||
}
|
||||
return LOGGER_MAP.get(name);
|
||||
}
|
||||
|
||||
private static void resolveLoggers() {
|
||||
// NOTE: Here we cannot use {@code SpiLoader} directly because it depends on the RecordLog.
|
||||
ServiceLoader<Logger> loggerLoader = ServiceLoader.load(Logger.class);
|
||||
|
||||
for (Logger logger : loggerLoader) {
|
||||
LogTarget annotation = logger.getClass().getAnnotation(LogTarget.class);
|
||||
if (annotation == null) {
|
||||
continue;
|
||||
}
|
||||
String name = annotation.value();
|
||||
// Load first encountered logger if multiple loggers are associated with the same name.
|
||||
if (StringUtil.isNotBlank(name) && !LOGGER_MAP.containsKey(name)) {
|
||||
LOGGER_MAP.put(name, logger);
|
||||
System.out.println("Sentinel Logger SPI loaded for <" + name + ">: "
|
||||
+ logger.getClass().getCanonicalName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private LoggerSpiProvider() {}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.log;
|
||||
|
||||
import com.alibaba.csp.sentinel.log.jul.JavaLoggingAdapter;
|
||||
|
||||
/**
|
||||
* The basic biz logger of Sentinel.
|
||||
*
|
||||
* @author youji.zj
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public class RecordLog {
|
||||
|
||||
public static final String LOGGER_NAME = "sentinelRecordLogger";
|
||||
public static final String DEFAULT_LOG_FILENAME = "sentinel-record.log";
|
||||
|
||||
private static com.alibaba.csp.sentinel.log.Logger logger = null;
|
||||
|
||||
static {
|
||||
try {
|
||||
// Load user-defined logger implementation first.
|
||||
logger = LoggerSpiProvider.getLogger(LOGGER_NAME);
|
||||
if (logger == null) {
|
||||
// If no customized loggers are provided, we use the default logger based on JUL.
|
||||
logger = new JavaLoggingAdapter(LOGGER_NAME, DEFAULT_LOG_FILENAME);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
System.err.println("Error: failed to initialize Sentinel RecordLog");
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void info(String format, Object... arguments) {
|
||||
logger.info(format, arguments);
|
||||
}
|
||||
|
||||
public static void info(String msg, Throwable e) {
|
||||
logger.info(msg, e);
|
||||
}
|
||||
|
||||
public static void warn(String format, Object... arguments) {
|
||||
logger.warn(format, arguments);
|
||||
}
|
||||
|
||||
public static void warn(String msg, Throwable e) {
|
||||
logger.warn(msg, e);
|
||||
}
|
||||
|
||||
public static void trace(String format, Object... arguments) {
|
||||
logger.trace(format, arguments);
|
||||
}
|
||||
|
||||
public static void trace(String msg, Throwable e) {
|
||||
logger.trace(msg, e);
|
||||
}
|
||||
|
||||
public static void debug(String format, Object... arguments) {
|
||||
logger.debug(format, arguments);
|
||||
}
|
||||
|
||||
public static void debug(String msg, Throwable e) {
|
||||
logger.debug(msg, e);
|
||||
}
|
||||
|
||||
public static void error(String format, Object... arguments) {
|
||||
logger.error(format, arguments);
|
||||
}
|
||||
|
||||
public static void error(String msg, Throwable e) {
|
||||
logger.error(msg, e);
|
||||
}
|
||||
|
||||
private RecordLog() {}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.alibaba.csp.sentinel.log.jul;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.alibaba.csp.sentinel.log.LogBase;
|
||||
import com.alibaba.csp.sentinel.util.PidUtil;
|
||||
|
||||
import static com.alibaba.csp.sentinel.log.LogBase.LOG_OUTPUT_TYPE_CONSOLE;
|
||||
import static com.alibaba.csp.sentinel.log.LogBase.LOG_OUTPUT_TYPE_FILE;
|
||||
|
||||
/**
|
||||
* The default logger based on java.util.logging.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 1.7.2
|
||||
*/
|
||||
public class BaseJulLogger {
|
||||
|
||||
protected void log(Logger logger, Handler handler, Level level, String detail, Object... params) {
|
||||
if (detail == null) {
|
||||
return;
|
||||
}
|
||||
disableOtherHandlers(logger, handler);
|
||||
|
||||
// Compatible with slf4j placeholder format "{}".
|
||||
FormattingTuple formattingTuple = MessageFormatter.arrayFormat(detail, params);
|
||||
String message = formattingTuple.getMessage();
|
||||
logger.log(level, message);
|
||||
}
|
||||
|
||||
protected void log(Logger logger, Handler handler, Level level, String detail, Throwable throwable) {
|
||||
if (detail == null) {
|
||||
return;
|
||||
}
|
||||
disableOtherHandlers(logger, handler);
|
||||
logger.log(level, detail, throwable);
|
||||
}
|
||||
|
||||
protected Handler makeLoggingHandler(String logName, Logger heliumRecordLog) {
|
||||
CspFormatter formatter = new CspFormatter();
|
||||
String logCharSet = LogBase.getLogCharset();
|
||||
Handler handler = null;
|
||||
|
||||
// Create handler according to logOutputType, set formatter to CspFormatter, set encoding to LOG_CHARSET
|
||||
switch (LogBase.getLogOutputType()) {
|
||||
case LOG_OUTPUT_TYPE_FILE:
|
||||
String fileName = LogBase.getLogBaseDir() + logName;
|
||||
if (LogBase.isLogNameUsePid()) {
|
||||
fileName += ".pid" + PidUtil.getPid();
|
||||
}
|
||||
try {
|
||||
handler = new DateFileLogHandler(fileName + ".%d", 1024 * 1024 * 200, 4, true);
|
||||
handler.setFormatter(formatter);
|
||||
handler.setEncoding(logCharSet);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
break;
|
||||
case LOG_OUTPUT_TYPE_CONSOLE:
|
||||
try {
|
||||
handler = new ConsoleHandler();
|
||||
handler.setFormatter(formatter);
|
||||
handler.setEncoding(logCharSet);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (handler != null) {
|
||||
disableOtherHandlers(heliumRecordLog, handler);
|
||||
}
|
||||
|
||||
// Set log level to INFO by default
|
||||
heliumRecordLog.setLevel(Level.INFO);
|
||||
return handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all current handlers from the logger and attach it with the given log handler.
|
||||
*
|
||||
* @param logger logger
|
||||
* @param handler the log handler
|
||||
*/
|
||||
static void disableOtherHandlers(Logger logger, Handler handler) {
|
||||
if (logger == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (logger) {
|
||||
Handler[] handlers = logger.getHandlers();
|
||||
if (handlers == null) {
|
||||
return;
|
||||
}
|
||||
if (handlers.length == 1 && handlers[0].equals(handler)) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.setUseParentHandlers(false);
|
||||
// Remove all current handlers.
|
||||
for (Handler h : handlers) {
|
||||
logger.removeHandler(h);
|
||||
}
|
||||
// Attach the given handler.
|
||||
logger.addHandler(handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.log.jul;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.logging.*;
|
||||
|
||||
/**
|
||||
* This Handler publishes log records to console by using {@link java.util.logging.StreamHandler}.
|
||||
*
|
||||
* Print log of WARNING level or above to System.err,
|
||||
* and print log of INFO level or below to System.out.
|
||||
*
|
||||
* To use this handler, add the following VM argument:
|
||||
* <pre>
|
||||
* -Dcsp.sentinel.log.output.type=console
|
||||
* </pre>
|
||||
*
|
||||
* @author cdfive
|
||||
*/
|
||||
class ConsoleHandler extends Handler {
|
||||
|
||||
/**
|
||||
* A Handler which publishes log records to System.out.
|
||||
*/
|
||||
private StreamHandler stdoutHandler;
|
||||
|
||||
/**
|
||||
* A Handler which publishes log records to System.err.
|
||||
*/
|
||||
private StreamHandler stderrHandler;
|
||||
|
||||
public ConsoleHandler() {
|
||||
this.stdoutHandler = new StreamHandler(System.out, new CspFormatter());
|
||||
this.stderrHandler = new StreamHandler(System.err, new CspFormatter());
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setFormatter(Formatter newFormatter) throws SecurityException {
|
||||
this.stdoutHandler.setFormatter(newFormatter);
|
||||
this.stderrHandler.setFormatter(newFormatter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setEncoding(String encoding) throws SecurityException, UnsupportedEncodingException {
|
||||
this.stdoutHandler.setEncoding(encoding);
|
||||
this.stderrHandler.setEncoding(encoding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publish(LogRecord record) {
|
||||
if (record.getLevel().intValue() >= Level.WARNING.intValue()) {
|
||||
stderrHandler.publish(record);
|
||||
stderrHandler.flush();
|
||||
} else {
|
||||
stdoutHandler.publish(record);
|
||||
stdoutHandler.flush();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
stdoutHandler.flush();
|
||||
stderrHandler.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws SecurityException {
|
||||
stdoutHandler.close();
|
||||
stderrHandler.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.log.jul;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.logging.Formatter;
|
||||
import java.util.logging.LogRecord;
|
||||
|
||||
/**
|
||||
* @author xuyue
|
||||
*/
|
||||
class CspFormatter extends Formatter {
|
||||
|
||||
private final ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() {
|
||||
@Override
|
||||
public SimpleDateFormat initialValue() {
|
||||
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public String format(LogRecord record) {
|
||||
final DateFormat df = dateFormatThreadLocal.get();
|
||||
StringBuilder builder = new StringBuilder(1000);
|
||||
builder.append(df.format(new Date(record.getMillis()))).append(" ");
|
||||
builder.append(record.getLevel().getName()).append(" ");
|
||||
builder.append(formatMessage(record));
|
||||
|
||||
String throwable = "";
|
||||
if (record.getThrown() != null) {
|
||||
StringWriter sw = new StringWriter();
|
||||
PrintWriter pw = new PrintWriter(sw);
|
||||
pw.println();
|
||||
record.getThrown().printStackTrace(pw);
|
||||
pw.close();
|
||||
throwable = sw.toString();
|
||||
}
|
||||
builder.append(throwable);
|
||||
if ("".equals(throwable)) {
|
||||
builder.append("\n");
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.log.jul;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.logging.FileHandler;
|
||||
import java.util.logging.Formatter;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.LogRecord;
|
||||
|
||||
class DateFileLogHandler extends Handler {
|
||||
|
||||
private final ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() {
|
||||
@Override
|
||||
public SimpleDateFormat initialValue() {
|
||||
return new SimpleDateFormat("yyyy-MM-dd");
|
||||
}
|
||||
};
|
||||
|
||||
private volatile FileHandler handler;
|
||||
|
||||
private final String pattern;
|
||||
private final int limit;
|
||||
private final int count;
|
||||
private final boolean append;
|
||||
|
||||
private volatile boolean initialized = false;
|
||||
|
||||
private volatile long startDate = System.currentTimeMillis();
|
||||
private volatile long endDate;
|
||||
|
||||
private final Object monitor = new Object();
|
||||
|
||||
DateFileLogHandler(String pattern, int limit, int count, boolean append) throws SecurityException {
|
||||
this.pattern = pattern;
|
||||
this.limit = limit;
|
||||
this.count = count;
|
||||
this.append = append;
|
||||
rotateDate();
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws SecurityException {
|
||||
handler.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
handler.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publish(LogRecord record) {
|
||||
if (shouldRotate(record)) {
|
||||
synchronized (monitor) {
|
||||
if (shouldRotate(record)) {
|
||||
rotateDate();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (System.currentTimeMillis() - startDate > 25 * 60 * 60 * 1000) {
|
||||
String msg = record.getMessage();
|
||||
record.setMessage("missed file rolling at: " + new Date(endDate) + "\n" + msg);
|
||||
}
|
||||
handler.publish(record);
|
||||
}
|
||||
|
||||
private boolean shouldRotate(LogRecord record) {
|
||||
if (endDate <= record.getMillis() || !logFileExits()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFormatter(Formatter newFormatter) {
|
||||
super.setFormatter(newFormatter);
|
||||
if (handler != null) { handler.setFormatter(newFormatter); }
|
||||
}
|
||||
|
||||
private boolean logFileExits() {
|
||||
try {
|
||||
SimpleDateFormat format = dateFormatThreadLocal.get();
|
||||
String fileName = pattern.replace("%d", format.format(new Date()));
|
||||
// When file count is not 1, the first log file name will end with ".0"
|
||||
if (count != 1) {
|
||||
fileName += ".0";
|
||||
}
|
||||
File logFile = new File(fileName);
|
||||
return logFile.exists();
|
||||
} catch (Throwable e) {
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void rotateDate() {
|
||||
this.startDate = System.currentTimeMillis();
|
||||
if (handler != null) {
|
||||
handler.close();
|
||||
}
|
||||
SimpleDateFormat format = dateFormatThreadLocal.get();
|
||||
String newPattern = pattern.replace("%d", format.format(new Date()));
|
||||
// Get current date.
|
||||
Calendar next = Calendar.getInstance();
|
||||
// Begin of next date.
|
||||
next.set(Calendar.HOUR_OF_DAY, 0);
|
||||
next.set(Calendar.MINUTE, 0);
|
||||
next.set(Calendar.SECOND, 0);
|
||||
next.set(Calendar.MILLISECOND, 0);
|
||||
next.add(Calendar.DATE, 1);
|
||||
this.endDate = next.getTimeInMillis();
|
||||
|
||||
try {
|
||||
this.handler = new FileHandler(newPattern, limit, count, append);
|
||||
if (initialized) {
|
||||
handler.setEncoding(this.getEncoding());
|
||||
handler.setErrorManager(this.getErrorManager());
|
||||
handler.setFilter(this.getFilter());
|
||||
handler.setFormatter(this.getFormatter());
|
||||
handler.setLevel(this.getLevel());
|
||||
}
|
||||
} catch (SecurityException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// Copyright notice: This code was copied from SLF4J which licensed under the MIT License.
|
||||
package com.alibaba.csp.sentinel.log.jul;
|
||||
|
||||
/**
|
||||
* Holds the results of formatting done by {@link MessageFormatter}.
|
||||
*
|
||||
* @author Joern Huxhorn
|
||||
*/
|
||||
public class FormattingTuple {
|
||||
|
||||
static public FormattingTuple NULL = new FormattingTuple(null);
|
||||
|
||||
private String message;
|
||||
private Throwable throwable;
|
||||
private Object[] argArray;
|
||||
|
||||
public FormattingTuple(String message) {
|
||||
this(message, null, null);
|
||||
}
|
||||
|
||||
public FormattingTuple(String message, Object[] argArray, Throwable throwable) {
|
||||
this.message = message;
|
||||
this.throwable = throwable;
|
||||
this.argArray = argArray;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public Object[] getArgArray() {
|
||||
return argArray;
|
||||
}
|
||||
|
||||
public Throwable getThrowable() {
|
||||
return throwable;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.alibaba.csp.sentinel.log.jul;
|
||||
|
||||
import java.util.logging.Handler;
|
||||
|
||||
import com.alibaba.csp.sentinel.log.Logger;
|
||||
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||
|
||||
/**
|
||||
* JUL adapter for Sentinel {@link Logger} SPI.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 1.7.2
|
||||
*/
|
||||
public class JavaLoggingAdapter extends BaseJulLogger implements Logger {
|
||||
|
||||
private final String loggerName;
|
||||
private final String fileNamePattern;
|
||||
|
||||
private final java.util.logging.Logger julLogger;
|
||||
private final Handler logHandler;
|
||||
|
||||
public JavaLoggingAdapter(String loggerName, String fileNamePattern) {
|
||||
AssertUtil.assertNotBlank(loggerName, "loggerName cannot be blank");
|
||||
AssertUtil.assertNotBlank(fileNamePattern, "fileNamePattern cannot be blank");
|
||||
this.loggerName = loggerName;
|
||||
this.fileNamePattern = fileNamePattern;
|
||||
|
||||
this.julLogger = java.util.logging.Logger.getLogger(loggerName);
|
||||
this.logHandler = makeLoggingHandler(fileNamePattern, julLogger);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String format, Object... arguments) {
|
||||
log(julLogger, logHandler, Level.INFO, format, arguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String msg, Throwable e) {
|
||||
log(julLogger, logHandler, Level.INFO, msg, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String format, Object... arguments) {
|
||||
log(julLogger, logHandler, Level.WARNING, format, arguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String msg, Throwable e) {
|
||||
log(julLogger, logHandler, Level.WARNING, msg, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trace(String format, Object... arguments) {
|
||||
log(julLogger, logHandler, Level.TRACE, format, arguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trace(String msg, Throwable e) {
|
||||
log(julLogger, logHandler, Level.TRACE, msg, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(String format, Object... arguments) {
|
||||
log(julLogger, logHandler, Level.DEBUG, format, arguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(String msg, Throwable e) {
|
||||
log(julLogger, logHandler, Level.DEBUG, msg, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String format, Object... arguments) {
|
||||
log(julLogger, logHandler, Level.ERROR, format, arguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String msg, Throwable e) {
|
||||
log(julLogger, logHandler, Level.ERROR, msg, e);
|
||||
}
|
||||
|
||||
public String getLoggerName() {
|
||||
return loggerName;
|
||||
}
|
||||
|
||||
public String getFileNamePattern() {
|
||||
return fileNamePattern;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.alibaba.csp.sentinel.log.jul;
|
||||
|
||||
/**
|
||||
* JUL logging levels.
|
||||
*
|
||||
* @author xue8
|
||||
*/
|
||||
public class Level extends java.util.logging.Level {
|
||||
private static final String defaultBundle = "sun.util.logging.resources.logging";
|
||||
|
||||
public static final Level ERROR = new Level("ERROR", 1000);
|
||||
public static final Level WARNING = new Level("WARNING", 900);
|
||||
public static final Level INFO = new Level("INFO", 800);
|
||||
public static final Level DEBUG = new Level("DEBUG", 700);
|
||||
public static final Level TRACE = new Level("TRACE", 600);
|
||||
|
||||
protected Level(String name, int value) {
|
||||
super(name, value, defaultBundle);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,417 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// Copyright notice: This code was copied from SLF4J which licensed under the MIT License.
|
||||
package com.alibaba.csp.sentinel.log.jul;
|
||||
|
||||
|
||||
// contributors: lizongbo: proposed special treatment of array parameter values
|
||||
// Joern Huxhorn: pointed out double[] omission, suggested deep array copy
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Formats messages according to very simple substitution rules. Substitutions
|
||||
* can be made 1, 2 or more arguments.
|
||||
*
|
||||
* <p>
|
||||
* For example,
|
||||
*
|
||||
* <pre>
|
||||
* MessageFormatter.format("Hi {}.", "there")
|
||||
* </pre>
|
||||
*
|
||||
* will return the string "Hi there.".
|
||||
* <p>
|
||||
* The {} pair is called the <em>formatting anchor</em>. It serves to designate
|
||||
* the location where arguments need to be substituted within the message
|
||||
* pattern.
|
||||
* <p>
|
||||
* In case your message contains the '{' or the '}' character, you do not have
|
||||
* to do anything special unless the '}' character immediately follows '{'. For
|
||||
* example,
|
||||
*
|
||||
* <pre>
|
||||
* MessageFormatter.format("Set {1,2,3} is not equal to {}.", "1,2");
|
||||
* </pre>
|
||||
*
|
||||
* will return the string "Set {1,2,3} is not equal to 1,2.".
|
||||
*
|
||||
* <p>
|
||||
* If for whatever reason you need to place the string "{}" in the message
|
||||
* without its <em>formatting anchor</em> meaning, then you need to escape the
|
||||
* '{' character with '\', that is the backslash character. Only the '{'
|
||||
* character should be escaped. There is no need to escape the '}' character.
|
||||
* For example,
|
||||
*
|
||||
* <pre>
|
||||
* MessageFormatter.format("Set \\{} is not equal to {}.", "1,2");
|
||||
* </pre>
|
||||
*
|
||||
* will return the string "Set {} is not equal to 1,2.".
|
||||
*
|
||||
* <p>
|
||||
* The escaping behavior just described can be overridden by escaping the escape
|
||||
* character '\'. Calling
|
||||
*
|
||||
* <pre>
|
||||
* MessageFormatter.format("File name is C:\\\\{}.", "file.zip");
|
||||
* </pre>
|
||||
*
|
||||
* will return the string "File name is C:\file.zip".
|
||||
*
|
||||
* <p>
|
||||
* The formatting conventions are different than those of {@link MessageFormat}
|
||||
* which ships with the Java platform. This is justified by the fact that
|
||||
* SLF4J's implementation is 10 times faster than that of {@link MessageFormat}.
|
||||
* This local performance difference is both measurable and significant in the
|
||||
* larger context of the complete logging processing chain.
|
||||
*
|
||||
* <p>
|
||||
* See also {@link #format(String, Object)},
|
||||
* {@link #format(String, Object, Object)} and
|
||||
* {@link #arrayFormat(String, Object[])} methods for more details.
|
||||
*
|
||||
* @author Ceki Gülcü
|
||||
* @author Joern Huxhorn
|
||||
*/
|
||||
final public class MessageFormatter {
|
||||
static final char DELIM_START = '{';
|
||||
static final char DELIM_STOP = '}';
|
||||
static final String DELIM_STR = "{}";
|
||||
private static final char ESCAPE_CHAR = '\\';
|
||||
|
||||
/**
|
||||
* Performs single argument substitution for the 'messagePattern' passed as
|
||||
* parameter.
|
||||
* <p>
|
||||
* For example,
|
||||
*
|
||||
* <pre>
|
||||
* MessageFormatter.format("Hi {}.", "there");
|
||||
* </pre>
|
||||
*
|
||||
* will return the string "Hi there.".
|
||||
* <p>
|
||||
*
|
||||
* @param messagePattern
|
||||
* The message pattern which will be parsed and formatted
|
||||
* @param arg
|
||||
* The argument to be substituted in place of the formatting anchor
|
||||
* @return The formatted message
|
||||
*/
|
||||
final public static FormattingTuple format(String messagePattern, Object arg) {
|
||||
return arrayFormat(messagePattern, new Object[] { arg });
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Performs a two argument substitution for the 'messagePattern' passed as
|
||||
* parameter.
|
||||
* <p>
|
||||
* For example,
|
||||
*
|
||||
* <pre>
|
||||
* MessageFormatter.format("Hi {}. My name is {}.", "Alice", "Bob");
|
||||
* </pre>
|
||||
*
|
||||
* will return the string "Hi Alice. My name is Bob.".
|
||||
*
|
||||
* @param messagePattern
|
||||
* The message pattern which will be parsed and formatted
|
||||
* @param arg1
|
||||
* The argument to be substituted in place of the first formatting
|
||||
* anchor
|
||||
* @param arg2
|
||||
* The argument to be substituted in place of the second formatting
|
||||
* anchor
|
||||
* @return The formatted message
|
||||
*/
|
||||
final public static FormattingTuple format(final String messagePattern, Object arg1, Object arg2) {
|
||||
return arrayFormat(messagePattern, new Object[] { arg1, arg2 });
|
||||
}
|
||||
|
||||
|
||||
static final Throwable getThrowableCandidate(Object[] argArray) {
|
||||
if (argArray == null || argArray.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Object lastEntry = argArray[argArray.length - 1];
|
||||
if (lastEntry instanceof Throwable) {
|
||||
return (Throwable) lastEntry;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
final public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray) {
|
||||
Throwable throwableCandidate = getThrowableCandidate(argArray);
|
||||
Object[] args = argArray;
|
||||
if (throwableCandidate != null) {
|
||||
args = trimmedCopy(argArray);
|
||||
}
|
||||
return arrayFormat(messagePattern, args, throwableCandidate);
|
||||
}
|
||||
|
||||
private static Object[] trimmedCopy(Object[] argArray) {
|
||||
if (argArray == null || argArray.length == 0) {
|
||||
throw new IllegalStateException("non-sensical empty or null argument array");
|
||||
}
|
||||
final int trimemdLen = argArray.length - 1;
|
||||
Object[] trimmed = new Object[trimemdLen];
|
||||
System.arraycopy(argArray, 0, trimmed, 0, trimemdLen);
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
final public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray, Throwable throwable) {
|
||||
|
||||
if (messagePattern == null) {
|
||||
return new FormattingTuple(null, argArray, throwable);
|
||||
}
|
||||
|
||||
if (argArray == null) {
|
||||
return new FormattingTuple(messagePattern);
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
int j;
|
||||
// use string builder for better multicore performance
|
||||
StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50);
|
||||
|
||||
int L;
|
||||
for (L = 0; L < argArray.length; L++) {
|
||||
|
||||
j = messagePattern.indexOf(DELIM_STR, i);
|
||||
|
||||
if (j == -1) {
|
||||
// no more variables
|
||||
if (i == 0) { // this is a simple string
|
||||
return new FormattingTuple(messagePattern, argArray, throwable);
|
||||
} else { // add the tail string which contains no variables and return
|
||||
// the result.
|
||||
sbuf.append(messagePattern, i, messagePattern.length());
|
||||
return new FormattingTuple(sbuf.toString(), argArray, throwable);
|
||||
}
|
||||
} else {
|
||||
if (isEscapedDelimeter(messagePattern, j)) {
|
||||
if (!isDoubleEscaped(messagePattern, j)) {
|
||||
L--; // DELIM_START was escaped, thus should not be incremented
|
||||
sbuf.append(messagePattern, i, j - 1);
|
||||
sbuf.append(DELIM_START);
|
||||
i = j + 1;
|
||||
} else {
|
||||
// The escape character preceding the delimiter start is
|
||||
// itself escaped: "abc x:\\{}"
|
||||
// we have to consume one backward slash
|
||||
sbuf.append(messagePattern, i, j - 1);
|
||||
deeplyAppendParameter(sbuf, argArray[L], new HashMap<Object[], Object>());
|
||||
i = j + 2;
|
||||
}
|
||||
} else {
|
||||
// normal case
|
||||
sbuf.append(messagePattern, i, j);
|
||||
deeplyAppendParameter(sbuf, argArray[L], new HashMap<Object[], Object>());
|
||||
i = j + 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
// append the characters following the last {} pair.
|
||||
sbuf.append(messagePattern, i, messagePattern.length());
|
||||
return new FormattingTuple(sbuf.toString(), argArray, throwable);
|
||||
}
|
||||
|
||||
final static boolean isEscapedDelimeter(String messagePattern, int delimeterStartIndex) {
|
||||
|
||||
if (delimeterStartIndex == 0) {
|
||||
return false;
|
||||
}
|
||||
char potentialEscape = messagePattern.charAt(delimeterStartIndex - 1);
|
||||
if (potentialEscape == ESCAPE_CHAR) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final static boolean isDoubleEscaped(String messagePattern, int delimeterStartIndex) {
|
||||
if (delimeterStartIndex >= 2 && messagePattern.charAt(delimeterStartIndex - 2) == ESCAPE_CHAR) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// special treatment of array values was suggested by 'lizongbo'
|
||||
private static void deeplyAppendParameter(StringBuilder sbuf, Object o, Map<Object[], Object> seenMap) {
|
||||
if (o == null) {
|
||||
sbuf.append("null");
|
||||
return;
|
||||
}
|
||||
if (!o.getClass().isArray()) {
|
||||
safeObjectAppend(sbuf, o);
|
||||
} else {
|
||||
// check for primitive array types because they
|
||||
// unfortunately cannot be cast to Object[]
|
||||
if (o instanceof boolean[]) {
|
||||
booleanArrayAppend(sbuf, (boolean[]) o);
|
||||
} else if (o instanceof byte[]) {
|
||||
byteArrayAppend(sbuf, (byte[]) o);
|
||||
} else if (o instanceof char[]) {
|
||||
charArrayAppend(sbuf, (char[]) o);
|
||||
} else if (o instanceof short[]) {
|
||||
shortArrayAppend(sbuf, (short[]) o);
|
||||
} else if (o instanceof int[]) {
|
||||
intArrayAppend(sbuf, (int[]) o);
|
||||
} else if (o instanceof long[]) {
|
||||
longArrayAppend(sbuf, (long[]) o);
|
||||
} else if (o instanceof float[]) {
|
||||
floatArrayAppend(sbuf, (float[]) o);
|
||||
} else if (o instanceof double[]) {
|
||||
doubleArrayAppend(sbuf, (double[]) o);
|
||||
} else {
|
||||
objectArrayAppend(sbuf, (Object[]) o, seenMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void safeObjectAppend(StringBuilder sbuf, Object o) {
|
||||
try {
|
||||
String oAsString = o.toString();
|
||||
sbuf.append(oAsString);
|
||||
} catch (Throwable t) {
|
||||
sbuf.append("[FAILED toString()]");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void objectArrayAppend(StringBuilder sbuf, Object[] a, Map<Object[], Object> seenMap) {
|
||||
sbuf.append('[');
|
||||
if (!seenMap.containsKey(a)) {
|
||||
seenMap.put(a, null);
|
||||
final int len = a.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
deeplyAppendParameter(sbuf, a[i], seenMap);
|
||||
if (i != len - 1) {
|
||||
sbuf.append(", ");
|
||||
}
|
||||
}
|
||||
// allow repeats in siblings
|
||||
seenMap.remove(a);
|
||||
} else {
|
||||
sbuf.append("...");
|
||||
}
|
||||
sbuf.append(']');
|
||||
}
|
||||
|
||||
private static void booleanArrayAppend(StringBuilder sbuf, boolean[] a) {
|
||||
sbuf.append('[');
|
||||
final int len = a.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
sbuf.append(a[i]);
|
||||
if (i != len - 1) {
|
||||
sbuf.append(", ");
|
||||
}
|
||||
}
|
||||
sbuf.append(']');
|
||||
}
|
||||
|
||||
private static void byteArrayAppend(StringBuilder sbuf, byte[] a) {
|
||||
sbuf.append('[');
|
||||
final int len = a.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
sbuf.append(a[i]);
|
||||
if (i != len - 1) {
|
||||
sbuf.append(", ");
|
||||
}
|
||||
}
|
||||
sbuf.append(']');
|
||||
}
|
||||
|
||||
private static void charArrayAppend(StringBuilder sbuf, char[] a) {
|
||||
sbuf.append('[');
|
||||
final int len = a.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
sbuf.append(a[i]);
|
||||
if (i != len - 1) {
|
||||
sbuf.append(", ");
|
||||
}
|
||||
}
|
||||
sbuf.append(']');
|
||||
}
|
||||
|
||||
private static void shortArrayAppend(StringBuilder sbuf, short[] a) {
|
||||
sbuf.append('[');
|
||||
final int len = a.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
sbuf.append(a[i]);
|
||||
if (i != len - 1) {
|
||||
sbuf.append(", ");
|
||||
}
|
||||
}
|
||||
sbuf.append(']');
|
||||
}
|
||||
|
||||
private static void intArrayAppend(StringBuilder sbuf, int[] a) {
|
||||
sbuf.append('[');
|
||||
final int len = a.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
sbuf.append(a[i]);
|
||||
if (i != len - 1) {
|
||||
sbuf.append(", ");
|
||||
}
|
||||
}
|
||||
sbuf.append(']');
|
||||
}
|
||||
|
||||
private static void longArrayAppend(StringBuilder sbuf, long[] a) {
|
||||
sbuf.append('[');
|
||||
final int len = a.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
sbuf.append(a[i]);
|
||||
if (i != len - 1) {
|
||||
sbuf.append(", ");
|
||||
}
|
||||
}
|
||||
sbuf.append(']');
|
||||
}
|
||||
|
||||
private static void floatArrayAppend(StringBuilder sbuf, float[] a) {
|
||||
sbuf.append('[');
|
||||
final int len = a.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
sbuf.append(a[i]);
|
||||
if (i != len - 1) {
|
||||
sbuf.append(", ");
|
||||
}
|
||||
}
|
||||
sbuf.append(']');
|
||||
}
|
||||
|
||||
private static void doubleArrayAppend(StringBuilder sbuf, double[] a) {
|
||||
sbuf.append('[');
|
||||
final int len = a.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
sbuf.append(a[i]);
|
||||
if (i != len - 1) {
|
||||
sbuf.append(", ");
|
||||
}
|
||||
}
|
||||
sbuf.append(']');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.metric.extension;
|
||||
|
||||
import com.alibaba.csp.sentinel.EntryType;
|
||||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
|
||||
/**
|
||||
* Extended {@link MetricExtension} extending input parameters of each metric
|
||||
* collection method with {@link EntryType}.
|
||||
*
|
||||
* @author bill_yip
|
||||
* @author Eric Zhao
|
||||
* @since 1.8.0
|
||||
*/
|
||||
public interface AdvancedMetricExtension extends MetricExtension {
|
||||
|
||||
/**
|
||||
* Add current pass count of the resource name.
|
||||
*
|
||||
* @param rw resource representation (including resource name, traffic type, etc.)
|
||||
* @param batchCount count to add
|
||||
* @param args additional arguments of the resource, eg. if the resource is a method name,
|
||||
* the args will be the parameters of the method.
|
||||
*/
|
||||
void onPass(ResourceWrapper rw, int batchCount, Object[] args);
|
||||
|
||||
/**
|
||||
* Add current block count of the resource name.
|
||||
*
|
||||
* @param rw resource representation (including resource name, traffic type, etc.)
|
||||
* @param batchCount count to add
|
||||
* @param origin the origin of caller (if present)
|
||||
* @param e the associated {@code BlockException}
|
||||
* @param args additional arguments of the resource, eg. if the resource is a method name,
|
||||
* the args will be the parameters of the method.
|
||||
*/
|
||||
void onBlocked(ResourceWrapper rw, int batchCount, String origin, BlockException e,
|
||||
Object[] args);
|
||||
|
||||
/**
|
||||
* Add current completed count of the resource name.
|
||||
*
|
||||
* @param rw resource representation (including resource name, traffic type, etc.)
|
||||
* @param batchCount count to add
|
||||
* @param rt response time of current invocation
|
||||
* @param args additional arguments of the resource
|
||||
*/
|
||||
void onComplete(ResourceWrapper rw, long rt, int batchCount, Object[] args);
|
||||
|
||||
/**
|
||||
* Add current exception count of the resource name.
|
||||
*
|
||||
* @param rw resource representation (including resource name, traffic type, etc.)
|
||||
* @param batchCount count to add
|
||||
* @param throwable exception related.
|
||||
* @param args additional arguments of the resource
|
||||
*/
|
||||
void onError(ResourceWrapper rw, Throwable throwable, int batchCount, Object[] args);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.metric.extension;
|
||||
|
||||
import com.alibaba.csp.sentinel.init.InitFunc;
|
||||
import com.alibaba.csp.sentinel.metric.extension.callback.MetricEntryCallback;
|
||||
import com.alibaba.csp.sentinel.metric.extension.callback.MetricExitCallback;
|
||||
import com.alibaba.csp.sentinel.slots.statistic.StatisticSlotCallbackRegistry;
|
||||
|
||||
/**
|
||||
* Register callbacks for metric extension.
|
||||
*
|
||||
* @author Carpenter Lee
|
||||
* @since 1.6.1
|
||||
*/
|
||||
public class MetricCallbackInit implements InitFunc {
|
||||
@Override
|
||||
public void init() throws Exception {
|
||||
StatisticSlotCallbackRegistry.addEntryCallback(MetricEntryCallback.class.getCanonicalName(),
|
||||
new MetricEntryCallback());
|
||||
StatisticSlotCallbackRegistry.addExitCallback(MetricExitCallback.class.getCanonicalName(),
|
||||
new MetricExitCallback());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.metric.extension;
|
||||
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
|
||||
/**
|
||||
* This interface provides extension to Sentinel internal statistics.
|
||||
* <p>
|
||||
* Please note that all method in this class will invoke in the same thread of biz logic.
|
||||
* It's necessary to not do time-consuming operation in any of the interface's method,
|
||||
* otherwise biz logic will be blocked.
|
||||
* </p>
|
||||
*
|
||||
* @author Carpenter Lee
|
||||
* @since 1.6.1
|
||||
*/
|
||||
public interface MetricExtension {
|
||||
|
||||
/**
|
||||
* Add current pass count of the resource name.
|
||||
*
|
||||
* @param n count to add
|
||||
* @param resource resource name
|
||||
* @param args additional arguments of the resource, eg. if the resource is a method name,
|
||||
* the args will be the parameters of the method.
|
||||
*/
|
||||
void addPass(String resource, int n, Object... args);
|
||||
|
||||
/**
|
||||
* Add current block count of the resource name.
|
||||
*
|
||||
* @param n count to add
|
||||
* @param resource resource name
|
||||
* @param origin the original invoker.
|
||||
* @param blockException block exception related.
|
||||
* @param args additional arguments of the resource, eg. if the resource is a method name,
|
||||
* the args will be the parameters of the method.
|
||||
*/
|
||||
void addBlock(String resource, int n, String origin, BlockException blockException, Object... args);
|
||||
|
||||
/**
|
||||
* Add current completed count of the resource name.
|
||||
*
|
||||
* @param n count to add
|
||||
* @param resource resource name
|
||||
* @param args additional arguments of the resource, eg. if the resource is a method name,
|
||||
* the args will be the parameters of the method.
|
||||
*/
|
||||
void addSuccess(String resource, int n, Object... args);
|
||||
|
||||
/**
|
||||
* Add current exception count of the resource name.
|
||||
*
|
||||
* @param n count to add
|
||||
* @param resource resource name
|
||||
* @param throwable exception related.
|
||||
*/
|
||||
void addException(String resource, int n, Throwable throwable);
|
||||
|
||||
/**
|
||||
* Add response time of the resource name.
|
||||
*
|
||||
* @param rt response time in millisecond
|
||||
* @param resource resource name
|
||||
* @param args additional arguments of the resource, eg. if the resource is a method name,
|
||||
* the args will be the parameters of the method.
|
||||
*/
|
||||
void addRt(String resource, long rt, Object... args);
|
||||
|
||||
/**
|
||||
* Increase current thread count of the resource name.
|
||||
*
|
||||
* @param resource resource name
|
||||
* @param args additional arguments of the resource, eg. if the resource is a method name,
|
||||
* the args will be the parameters of the method.
|
||||
*/
|
||||
void increaseThreadNum(String resource, Object... args);
|
||||
|
||||
/**
|
||||
* Decrease current thread count of the resource name.
|
||||
*
|
||||
* @param resource resource name
|
||||
* @param args additional arguments of the resource, eg. if the resource is a method name,
|
||||
* the args will be the parameters of the method.
|
||||
*/
|
||||
void decreaseThreadNum(String resource, Object... args);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.metric.extension;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.spi.SpiLoader;
|
||||
|
||||
/**
|
||||
* Get all {@link MetricExtension} via SPI.
|
||||
*
|
||||
* @author Carpenter Lee
|
||||
* @since 1.6.1
|
||||
*/
|
||||
public class MetricExtensionProvider {
|
||||
private static List<MetricExtension> metricExtensions = new ArrayList<>();
|
||||
|
||||
static {
|
||||
resolveInstance();
|
||||
}
|
||||
|
||||
private static void resolveInstance() {
|
||||
List<MetricExtension> extensions = SpiLoader.of(MetricExtension.class).loadInstanceList();
|
||||
|
||||
if (extensions.isEmpty()) {
|
||||
RecordLog.info("[MetricExtensionProvider] No existing MetricExtension found");
|
||||
} else {
|
||||
metricExtensions.addAll(extensions);
|
||||
RecordLog.info("[MetricExtensionProvider] MetricExtension resolved, size={}", extensions.size());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Get all registered metric extensions.</p>
|
||||
* <p>DO NOT MODIFY the returned list, use {@link #addMetricExtension(MetricExtension)}.</p>
|
||||
*
|
||||
* @return all registered metric extensions
|
||||
*/
|
||||
public static List<MetricExtension> getMetricExtensions() {
|
||||
return metricExtensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add metric extension.
|
||||
* <p>
|
||||
* Note that this method is NOT thread safe.
|
||||
* </p>
|
||||
*
|
||||
* @param metricExtension the metric extension to add.
|
||||
*/
|
||||
public static void addMetricExtension(MetricExtension metricExtension) {
|
||||
metricExtensions.add(metricExtension);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.metric.extension.callback;
|
||||
|
||||
import com.alibaba.csp.sentinel.context.Context;
|
||||
import com.alibaba.csp.sentinel.metric.extension.AdvancedMetricExtension;
|
||||
import com.alibaba.csp.sentinel.metric.extension.MetricExtension;
|
||||
import com.alibaba.csp.sentinel.metric.extension.MetricExtensionProvider;
|
||||
import com.alibaba.csp.sentinel.node.DefaultNode;
|
||||
import com.alibaba.csp.sentinel.slotchain.ProcessorSlotEntryCallback;
|
||||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
|
||||
/**
|
||||
* Metric extension entry callback.
|
||||
*
|
||||
* @author Carpenter Lee
|
||||
* @since 1.6.1
|
||||
*/
|
||||
public class MetricEntryCallback implements ProcessorSlotEntryCallback<DefaultNode> {
|
||||
|
||||
@Override
|
||||
public void onPass(Context context, ResourceWrapper rw, DefaultNode param, int count, Object... args)
|
||||
throws Exception {
|
||||
for (MetricExtension m : MetricExtensionProvider.getMetricExtensions()) {
|
||||
if (m instanceof AdvancedMetricExtension) {
|
||||
((AdvancedMetricExtension) m).onPass(rw, count, args);
|
||||
} else {
|
||||
m.increaseThreadNum(rw.getName(), args);
|
||||
m.addPass(rw.getName(), count, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlocked(BlockException ex, Context context, ResourceWrapper resourceWrapper, DefaultNode param,
|
||||
int count, Object... args) {
|
||||
for (MetricExtension m : MetricExtensionProvider.getMetricExtensions()) {
|
||||
if (m instanceof AdvancedMetricExtension) {
|
||||
((AdvancedMetricExtension) m).onBlocked(resourceWrapper, count, context.getOrigin(), ex, args);
|
||||
} else {
|
||||
m.addBlock(resourceWrapper.getName(), count, context.getOrigin(), ex, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.metric.extension.callback;
|
||||
|
||||
import com.alibaba.csp.sentinel.Entry;
|
||||
import com.alibaba.csp.sentinel.context.Context;
|
||||
import com.alibaba.csp.sentinel.metric.extension.AdvancedMetricExtension;
|
||||
import com.alibaba.csp.sentinel.metric.extension.MetricExtension;
|
||||
import com.alibaba.csp.sentinel.metric.extension.MetricExtensionProvider;
|
||||
import com.alibaba.csp.sentinel.slotchain.ProcessorSlotExitCallback;
|
||||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
|
||||
import com.alibaba.csp.sentinel.util.TimeUtil;
|
||||
|
||||
/**
|
||||
* Metric extension exit callback.
|
||||
*
|
||||
* @author Carpenter Lee
|
||||
* @author Eric Zhao
|
||||
* @since 1.6.1
|
||||
*/
|
||||
public class MetricExitCallback implements ProcessorSlotExitCallback {
|
||||
|
||||
@Override
|
||||
public void onExit(Context context, ResourceWrapper rw, int acquireCount, Object... args) {
|
||||
Entry curEntry = context.getCurEntry();
|
||||
if (curEntry == null) {
|
||||
return;
|
||||
}
|
||||
for (MetricExtension m : MetricExtensionProvider.getMetricExtensions()) {
|
||||
if (curEntry.getBlockError() != null) {
|
||||
continue;
|
||||
}
|
||||
String resource = rw.getName();
|
||||
Throwable ex = curEntry.getError();
|
||||
long completeTime = curEntry.getCompleteTimestamp();
|
||||
if (completeTime <= 0) {
|
||||
completeTime = TimeUtil.currentTimeMillis();
|
||||
}
|
||||
long rt = completeTime - curEntry.getCreateTimestamp();
|
||||
|
||||
if (m instanceof AdvancedMetricExtension) {
|
||||
// Since 1.8.0 (as a temporary workaround for compatibility)
|
||||
((AdvancedMetricExtension) m).onComplete(rw, rt, acquireCount, args);
|
||||
if (ex != null) {
|
||||
((AdvancedMetricExtension) m).onError(rw, ex, acquireCount, args);
|
||||
}
|
||||
} else {
|
||||
m.addRt(resource, rt, args);
|
||||
m.addSuccess(resource, acquireCount, args);
|
||||
m.decreaseThreadNum(resource, args);
|
||||
if (null != ex) {
|
||||
m.addException(resource, acquireCount, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.node;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import com.alibaba.csp.sentinel.ResourceTypeConstants;
|
||||
import com.alibaba.csp.sentinel.context.ContextUtil;
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* This class stores summary runtime statistics of the resource, including rt, thread count, qps
|
||||
* and so on. Same resource shares the same {@link ClusterNode} globally, no matter in which
|
||||
* {@link com.alibaba.csp.sentinel.context.Context}.
|
||||
* </p>
|
||||
* <p>
|
||||
* To distinguish invocation from different origin (declared in
|
||||
* {@link ContextUtil#enter(String name, String origin)}),
|
||||
* one {@link ClusterNode} holds an {@link #originCountMap}, this map holds {@link StatisticNode}
|
||||
* of different origin. Use {@link #getOrCreateOriginNode(String)} to get {@link Node} of the specific
|
||||
* origin.<br/>
|
||||
* Note that 'origin' usually is Service Consumer's app name.
|
||||
* </p>
|
||||
*
|
||||
* @author qinan.qn
|
||||
* @author jialiang.linjl
|
||||
*/
|
||||
public class ClusterNode extends StatisticNode {
|
||||
|
||||
private final String name;
|
||||
private final int resourceType;
|
||||
|
||||
public ClusterNode(String name) {
|
||||
this(name, ResourceTypeConstants.COMMON);
|
||||
}
|
||||
|
||||
public ClusterNode(String name, int resourceType) {
|
||||
AssertUtil.notEmpty(name, "name cannot be empty");
|
||||
this.name = name;
|
||||
this.resourceType = resourceType;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>The origin map holds the pair: (origin, originNode) for one specific resource.</p>
|
||||
* <p>
|
||||
* The longer the application runs, the more stable this mapping will become.
|
||||
* So we didn't use concurrent map here, but a lock, as this lock only happens
|
||||
* at the very beginning while concurrent map will hold the lock all the time.
|
||||
* </p>
|
||||
*/
|
||||
private Map<String, StatisticNode> originCountMap = new HashMap<>();
|
||||
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
|
||||
/**
|
||||
* Get resource name of the resource node.
|
||||
*
|
||||
* @return resource name
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get classification (type) of the resource.
|
||||
*
|
||||
* @return resource type
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public int getResourceType() {
|
||||
return resourceType;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Get {@link Node} of the specific origin. Usually the origin is the Service Consumer's app name.</p>
|
||||
* <p>If the origin node for given origin is absent, then a new {@link StatisticNode}
|
||||
* for the origin will be created and returned.</p>
|
||||
*
|
||||
* @param origin The caller's name, which is designated in the {@code parameter} parameter
|
||||
* {@link ContextUtil#enter(String name, String origin)}.
|
||||
* @return the {@link Node} of the specific origin
|
||||
*/
|
||||
public Node getOrCreateOriginNode(String origin) {
|
||||
StatisticNode statisticNode = originCountMap.get(origin);
|
||||
if (statisticNode == null) {
|
||||
lock.lock();
|
||||
try {
|
||||
statisticNode = originCountMap.get(origin);
|
||||
if (statisticNode == null) {
|
||||
// The node is absent, create a new node for the origin.
|
||||
statisticNode = new StatisticNode();
|
||||
HashMap<String, StatisticNode> newMap = new HashMap<>(originCountMap.size() + 1);
|
||||
newMap.putAll(originCountMap);
|
||||
newMap.put(origin, statisticNode);
|
||||
originCountMap = newMap;
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
return statisticNode;
|
||||
}
|
||||
|
||||
public Map<String, StatisticNode> getOriginCountMap() {
|
||||
return originCountMap;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.node;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.SphO;
|
||||
import com.alibaba.csp.sentinel.SphU;
|
||||
import com.alibaba.csp.sentinel.context.Context;
|
||||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
|
||||
import com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* A {@link Node} used to hold statistics for specific resource name in the specific context.
|
||||
* Each distinct resource in each distinct {@link Context} will corresponding to a {@link DefaultNode}.
|
||||
* </p>
|
||||
* <p>
|
||||
* This class may have a list of sub {@link DefaultNode}s. Child nodes will be created when
|
||||
* calling {@link SphU}#entry() or {@link SphO}@entry() multiple times in the same {@link Context}.
|
||||
* </p>
|
||||
*
|
||||
* @author qinan.qn
|
||||
* @see NodeSelectorSlot
|
||||
*/
|
||||
public class DefaultNode extends StatisticNode {
|
||||
|
||||
/**
|
||||
* The resource associated with the node.
|
||||
*/
|
||||
private ResourceWrapper id;
|
||||
|
||||
/**
|
||||
* The list of all child nodes.
|
||||
*/
|
||||
private volatile Set<Node> childList = new HashSet<>();
|
||||
|
||||
/**
|
||||
* Associated cluster node.
|
||||
*/
|
||||
private ClusterNode clusterNode;
|
||||
|
||||
public DefaultNode(ResourceWrapper id, ClusterNode clusterNode) {
|
||||
this.id = id;
|
||||
this.clusterNode = clusterNode;
|
||||
}
|
||||
|
||||
public ResourceWrapper getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public ClusterNode getClusterNode() {
|
||||
return clusterNode;
|
||||
}
|
||||
|
||||
public void setClusterNode(ClusterNode clusterNode) {
|
||||
this.clusterNode = clusterNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add child node to current node.
|
||||
*
|
||||
* @param node valid child node
|
||||
*/
|
||||
public void addChild(Node node) {
|
||||
if (node == null) {
|
||||
RecordLog.warn("Trying to add null child to node <{}>, ignored", id.getName());
|
||||
return;
|
||||
}
|
||||
if (!childList.contains(node)) {
|
||||
synchronized (this) {
|
||||
if (!childList.contains(node)) {
|
||||
Set<Node> newSet = new HashSet<>(childList.size() + 1);
|
||||
newSet.addAll(childList);
|
||||
newSet.add(node);
|
||||
childList = newSet;
|
||||
}
|
||||
}
|
||||
RecordLog.info("Add child <{}> to node <{}>", ((DefaultNode)node).id.getName(), id.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the child node list.
|
||||
*/
|
||||
public void removeChildList() {
|
||||
this.childList = new HashSet<>();
|
||||
}
|
||||
|
||||
public Set<Node> getChildList() {
|
||||
return childList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void increaseBlockQps(int count) {
|
||||
super.increaseBlockQps(count);
|
||||
this.clusterNode.increaseBlockQps(count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void increaseExceptionQps(int count) {
|
||||
super.increaseExceptionQps(count);
|
||||
this.clusterNode.increaseExceptionQps(count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRtAndSuccess(long rt, int successCount) {
|
||||
super.addRtAndSuccess(rt, successCount);
|
||||
this.clusterNode.addRtAndSuccess(rt, successCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void increaseThreadNum() {
|
||||
super.increaseThreadNum();
|
||||
this.clusterNode.increaseThreadNum();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decreaseThreadNum() {
|
||||
super.decreaseThreadNum();
|
||||
this.clusterNode.decreaseThreadNum();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPassRequest(int count) {
|
||||
super.addPassRequest(count);
|
||||
this.clusterNode.addPassRequest(count);
|
||||
}
|
||||
|
||||
public void printDefaultNode() {
|
||||
visitTree(0, this);
|
||||
}
|
||||
|
||||
private void visitTree(int level, DefaultNode node) {
|
||||
for (int i = 0; i < level; ++i) {
|
||||
System.out.print("-");
|
||||
}
|
||||
if (!(node instanceof EntranceNode)) {
|
||||
System.out.println(
|
||||
String.format("%s(thread:%s pq:%s bq:%s tq:%s rt:%s 1mp:%s 1mb:%s 1mt:%s)", node.id.getShowName(),
|
||||
node.curThreadNum(), node.passQps(), node.blockQps(), node.totalQps(), node.avgRt(),
|
||||
node.totalRequest() - node.blockRequest(), node.blockRequest(), node.totalRequest()));
|
||||
} else {
|
||||
System.out.println(
|
||||
String.format("Entry-%s(t:%s pq:%s bq:%s tq:%s rt:%s 1mp:%s 1mb:%s 1mt:%s)", node.id.getShowName(),
|
||||
node.curThreadNum(), node.passQps(), node.blockQps(), node.totalQps(), node.avgRt(),
|
||||
node.totalRequest() - node.blockRequest(), node.blockRequest(), node.totalRequest()));
|
||||
}
|
||||
for (Node n : node.getChildList()) {
|
||||
DefaultNode dn = (DefaultNode)n;
|
||||
visitTree(level + 1, dn);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.node;
|
||||
|
||||
import com.alibaba.csp.sentinel.context.Context;
|
||||
import com.alibaba.csp.sentinel.context.ContextUtil;
|
||||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
|
||||
import com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* A {@link Node} represents the entrance of the invocation tree.
|
||||
* </p>
|
||||
* <p>
|
||||
* One {@link Context} will related to a {@link EntranceNode},
|
||||
* which represents the entrance of the invocation tree. New {@link EntranceNode} will be created if
|
||||
* current context does't have one. Note that same context name will share same {@link EntranceNode}
|
||||
* globally.
|
||||
* </p>
|
||||
*
|
||||
* @author qinan.qn
|
||||
* @see ContextUtil
|
||||
* @see ContextUtil#enter(String, String)
|
||||
* @see NodeSelectorSlot
|
||||
*/
|
||||
public class EntranceNode extends DefaultNode {
|
||||
|
||||
public EntranceNode(ResourceWrapper id, ClusterNode clusterNode) {
|
||||
super(id, clusterNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double avgRt() {
|
||||
double total = 0;
|
||||
double totalQps = 0;
|
||||
for (Node node : getChildList()) {
|
||||
total += node.avgRt() * node.passQps();
|
||||
totalQps += node.passQps();
|
||||
}
|
||||
return total / (totalQps == 0 ? 1 : totalQps);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double blockQps() {
|
||||
double blockQps = 0;
|
||||
for (Node node : getChildList()) {
|
||||
blockQps += node.blockQps();
|
||||
}
|
||||
return blockQps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long blockRequest() {
|
||||
long r = 0;
|
||||
for (Node node : getChildList()) {
|
||||
r += node.blockRequest();
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int curThreadNum() {
|
||||
int r = 0;
|
||||
for (Node node : getChildList()) {
|
||||
r += node.curThreadNum();
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double totalQps() {
|
||||
double r = 0;
|
||||
for (Node node : getChildList()) {
|
||||
r += node.totalQps();
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double successQps() {
|
||||
double r = 0;
|
||||
for (Node node : getChildList()) {
|
||||
r += node.successQps();
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double passQps() {
|
||||
double r = 0;
|
||||
for (Node node : getChildList()) {
|
||||
r += node.passQps();
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long totalRequest() {
|
||||
long r = 0;
|
||||
for (Node node : getChildList()) {
|
||||
r += node.totalRequest();
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long totalPass() {
|
||||
long r = 0;
|
||||
for (Node node : getChildList()) {
|
||||
r += node.totalPass();
|
||||
}
|
||||
return r;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.node;
|
||||
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.property.SentinelProperty;
|
||||
import com.alibaba.csp.sentinel.property.SimplePropertyListener;
|
||||
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
|
||||
import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot;
|
||||
|
||||
/**
|
||||
* QPS statistics interval.
|
||||
*
|
||||
* @author youji.zj
|
||||
* @author jialiang.linjl
|
||||
* @author Carpenter Lee
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public class IntervalProperty {
|
||||
|
||||
/**
|
||||
* <p>Interval in milliseconds. This variable determines sensitivity of the QPS calculation.</p>
|
||||
* <p>
|
||||
* DO NOT MODIFY this value directly, use {@link #updateInterval(int)}, otherwise the modification will not
|
||||
* take effect.
|
||||
* </p>
|
||||
*/
|
||||
public static volatile int INTERVAL = RuleConstant.DEFAULT_WINDOW_INTERVAL_MS;
|
||||
|
||||
public static void register2Property(SentinelProperty<Integer> property) {
|
||||
property.addListener(new SimplePropertyListener<Integer>() {
|
||||
@Override
|
||||
public void configUpdate(Integer value) {
|
||||
if (value != null) {
|
||||
updateInterval(value);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the {@link #INTERVAL}, All {@link ClusterNode}s will be reset if newInterval is
|
||||
* different from {@link #INTERVAL}
|
||||
*
|
||||
* @param newInterval New interval to set.
|
||||
*/
|
||||
public static void updateInterval(int newInterval) {
|
||||
if (newInterval != INTERVAL) {
|
||||
INTERVAL = newInterval;
|
||||
ClusterBuilderSlot.resetClusterNodes();
|
||||
}
|
||||
RecordLog.info("[IntervalProperty] INTERVAL updated to: {}", INTERVAL);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.node;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.csp.sentinel.Entry;
|
||||
import com.alibaba.csp.sentinel.node.metric.MetricNode;
|
||||
import com.alibaba.csp.sentinel.slots.statistic.metric.DebugSupport;
|
||||
import com.alibaba.csp.sentinel.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Holds real-time statistics for resources.
|
||||
*
|
||||
* @author qinan.qn
|
||||
* @author leyou
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public interface Node extends OccupySupport, DebugSupport {
|
||||
|
||||
/**
|
||||
* Get incoming request per minute ({@code pass + block}).
|
||||
*
|
||||
* @return total request count per minute
|
||||
*/
|
||||
long totalRequest();
|
||||
|
||||
/**
|
||||
* Get pass count per minute.
|
||||
*
|
||||
* @return total passed request count per minute
|
||||
* @since 1.5.0
|
||||
*/
|
||||
long totalPass();
|
||||
|
||||
/**
|
||||
* Get {@link Entry#exit()} count per minute.
|
||||
*
|
||||
* @return total completed request count per minute
|
||||
*/
|
||||
long totalSuccess();
|
||||
|
||||
/**
|
||||
* Get blocked request count per minute (totalBlockRequest).
|
||||
*
|
||||
* @return total blocked request count per minute
|
||||
*/
|
||||
long blockRequest();
|
||||
|
||||
/**
|
||||
* Get exception count per minute.
|
||||
*
|
||||
* @return total business exception count per minute
|
||||
*/
|
||||
long totalException();
|
||||
|
||||
/**
|
||||
* Get pass request per second.
|
||||
*
|
||||
* @return QPS of passed requests
|
||||
*/
|
||||
double passQps();
|
||||
|
||||
/**
|
||||
* Get block request per second.
|
||||
*
|
||||
* @return QPS of blocked requests
|
||||
*/
|
||||
double blockQps();
|
||||
|
||||
/**
|
||||
* Get {@link #passQps()} + {@link #blockQps()} request per second.
|
||||
*
|
||||
* @return QPS of passed and blocked requests
|
||||
*/
|
||||
double totalQps();
|
||||
|
||||
/**
|
||||
* Get {@link Entry#exit()} request per second.
|
||||
*
|
||||
* @return QPS of completed requests
|
||||
*/
|
||||
double successQps();
|
||||
|
||||
/**
|
||||
* Get estimated max success QPS till now.
|
||||
*
|
||||
* @return max completed QPS
|
||||
*/
|
||||
double maxSuccessQps();
|
||||
|
||||
/**
|
||||
* Get exception count per second.
|
||||
*
|
||||
* @return QPS of exception occurs
|
||||
*/
|
||||
double exceptionQps();
|
||||
|
||||
/**
|
||||
* Get average rt per second.
|
||||
*
|
||||
* @return average response time per second
|
||||
*/
|
||||
double avgRt();
|
||||
|
||||
/**
|
||||
* Get minimal response time.
|
||||
*
|
||||
* @return recorded minimal response time
|
||||
*/
|
||||
double minRt();
|
||||
|
||||
/**
|
||||
* Get current active thread count.
|
||||
*
|
||||
* @return current active thread count
|
||||
*/
|
||||
int curThreadNum();
|
||||
|
||||
/**
|
||||
* Get last second block QPS.
|
||||
*/
|
||||
double previousBlockQps();
|
||||
|
||||
/**
|
||||
* Last window QPS.
|
||||
*/
|
||||
double previousPassQps();
|
||||
|
||||
/**
|
||||
* Fetch all valid metric nodes of resources.
|
||||
*
|
||||
* @return valid metric nodes of resources
|
||||
*/
|
||||
Map<Long, MetricNode> metrics();
|
||||
|
||||
/**
|
||||
* Fetch all raw metric items that satisfies the time predicate.
|
||||
*
|
||||
* @param timePredicate time predicate
|
||||
* @return raw metric items that satisfies the time predicate
|
||||
* @since 1.7.0
|
||||
*/
|
||||
List<MetricNode> rawMetricsInMin(Predicate<Long> timePredicate);
|
||||
|
||||
/**
|
||||
* Add pass count.
|
||||
*
|
||||
* @param count count to add pass
|
||||
*/
|
||||
void addPassRequest(int count);
|
||||
|
||||
/**
|
||||
* Add rt and success count.
|
||||
*
|
||||
* @param rt response time
|
||||
* @param success success count to add
|
||||
*/
|
||||
void addRtAndSuccess(long rt, int success);
|
||||
|
||||
/**
|
||||
* Increase the block count.
|
||||
*
|
||||
* @param count count to add
|
||||
*/
|
||||
void increaseBlockQps(int count);
|
||||
|
||||
/**
|
||||
* Add the biz exception count.
|
||||
*
|
||||
* @param count count to add
|
||||
*/
|
||||
void increaseExceptionQps(int count);
|
||||
|
||||
/**
|
||||
* Increase current thread count.
|
||||
*/
|
||||
void increaseThreadNum();
|
||||
|
||||
/**
|
||||
* Decrease current thread count.
|
||||
*/
|
||||
void decreaseThreadNum();
|
||||
|
||||
/**
|
||||
* Reset the internal counter. Reset is needed when {@link IntervalProperty#INTERVAL} or
|
||||
* {@link SampleCountProperty#SAMPLE_COUNT} is changed.
|
||||
*/
|
||||
void reset();
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.node;
|
||||
|
||||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
|
||||
|
||||
/**
|
||||
* Builds new {@link DefaultNode} and {@link ClusterNode}.
|
||||
*
|
||||
* @author qinan.qn
|
||||
*/
|
||||
@Deprecated
|
||||
public interface NodeBuilder {
|
||||
|
||||
/**
|
||||
* Create a new {@link DefaultNode} as tree node.
|
||||
*
|
||||
* @param id resource
|
||||
* @param clusterNode the cluster node of the provided resource
|
||||
* @return new created tree node
|
||||
*/
|
||||
DefaultNode buildTreeNode(ResourceWrapper id, ClusterNode clusterNode);
|
||||
|
||||
/**
|
||||
* Create a new {@link ClusterNode} as universal statistic node for a single resource.
|
||||
*
|
||||
* @return new created cluster node
|
||||
*/
|
||||
ClusterNode buildClusterNode();
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.node;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.5.0
|
||||
*/
|
||||
public interface OccupySupport {
|
||||
|
||||
/**
|
||||
* Try to occupy latter time windows' tokens. If occupy success, a value less than
|
||||
* {@code occupyTimeout} in {@link OccupyTimeoutProperty} will be return.
|
||||
*
|
||||
* <p>
|
||||
* Each time we occupy tokens of the future window, current thread should sleep for the
|
||||
* corresponding time for smoothing QPS. We can't occupy tokens of the future with unlimited,
|
||||
* the sleep time limit is {@code occupyTimeout} in {@link OccupyTimeoutProperty}.
|
||||
* </p>
|
||||
*
|
||||
* @param currentTime current time millis.
|
||||
* @param acquireCount tokens count to acquire.
|
||||
* @param threshold qps threshold.
|
||||
* @return time should sleep. Time >= {@code occupyTimeout} in {@link OccupyTimeoutProperty} means
|
||||
* occupy fail, in this case, the request should be rejected immediately.
|
||||
*/
|
||||
long tryOccupyNext(long currentTime, int acquireCount, double threshold);
|
||||
|
||||
/**
|
||||
* Get current waiting amount. Useful for debug.
|
||||
*
|
||||
* @return current waiting amount
|
||||
*/
|
||||
long waiting();
|
||||
|
||||
/**
|
||||
* Add request that occupied.
|
||||
*
|
||||
* @param futureTime future timestamp that the acquireCount should be added on.
|
||||
* @param acquireCount tokens count.
|
||||
*/
|
||||
void addWaitingRequest(long futureTime, int acquireCount);
|
||||
|
||||
/**
|
||||
* Add occupied pass request, which represents pass requests that borrow the latter windows' token.
|
||||
*
|
||||
* @param acquireCount tokens count.
|
||||
*/
|
||||
void addOccupiedPass(int acquireCount);
|
||||
|
||||
/**
|
||||
* Get current occupied pass QPS.
|
||||
*
|
||||
* @return current occupied pass QPS
|
||||
*/
|
||||
double occupiedPassQps();
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.node;
|
||||
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.property.SentinelProperty;
|
||||
import com.alibaba.csp.sentinel.property.SimplePropertyListener;
|
||||
|
||||
/**
|
||||
* @author jialiang.linjl
|
||||
* @author Carpenter Lee
|
||||
* @since 1.5.0
|
||||
*/
|
||||
public class OccupyTimeoutProperty {
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Max occupy timeout in milliseconds. Requests with priority can occupy tokens of the future statistic
|
||||
* window, and {@code occupyTimeout} limit the max time length that can be occupied.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that the timeout value should never be greeter than {@link IntervalProperty#INTERVAL}.
|
||||
* </p>
|
||||
* DO NOT MODIFY this value directly, use {@link #updateTimeout(int)},
|
||||
* otherwise the modification will not take effect.
|
||||
*/
|
||||
private static volatile int occupyTimeout = 500;
|
||||
|
||||
public static void register2Property(SentinelProperty<Integer> property) {
|
||||
property.addListener(new SimplePropertyListener<Integer>() {
|
||||
@Override
|
||||
public void configUpdate(Integer value) {
|
||||
if (value != null) {
|
||||
updateTimeout(value);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static int getOccupyTimeout() {
|
||||
return occupyTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the timeout value.</br>
|
||||
* Note that the time out should never greeter than {@link IntervalProperty#INTERVAL},
|
||||
* or it will be ignored.
|
||||
*
|
||||
* @param newInterval new value.
|
||||
*/
|
||||
public static void updateTimeout(int newInterval) {
|
||||
if (newInterval < 0) {
|
||||
RecordLog.warn("[OccupyTimeoutProperty] Illegal timeout value will be ignored: " + occupyTimeout);
|
||||
return;
|
||||
}
|
||||
if (newInterval > IntervalProperty.INTERVAL) {
|
||||
RecordLog.warn("[OccupyTimeoutProperty] Illegal timeout value will be ignored: {}, should <= {}",
|
||||
occupyTimeout, IntervalProperty.INTERVAL);
|
||||
return;
|
||||
}
|
||||
if (newInterval != occupyTimeout) {
|
||||
occupyTimeout = newInterval;
|
||||
}
|
||||
RecordLog.info("[OccupyTimeoutProperty] occupyTimeout updated to: {}", occupyTimeout);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.node;
|
||||
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.property.SentinelProperty;
|
||||
import com.alibaba.csp.sentinel.property.SimplePropertyListener;
|
||||
import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot;
|
||||
|
||||
/**
|
||||
* Holds statistic buckets count per second.
|
||||
*
|
||||
* @author jialiang.linjl
|
||||
* @author CarpenterLee
|
||||
*/
|
||||
public class SampleCountProperty {
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Statistic buckets count per second. This variable determines sensitivity of the QPS calculation.
|
||||
* DO NOT MODIFY this value directly, use {@link #updateSampleCount(int)}, otherwise the modification will not
|
||||
* take effect.
|
||||
* </p>
|
||||
* Node that this value must be divisor of 1000.
|
||||
*/
|
||||
public static volatile int SAMPLE_COUNT = 2;
|
||||
|
||||
public static void register2Property(SentinelProperty<Integer> property) {
|
||||
property.addListener(new SimplePropertyListener<Integer>() {
|
||||
@Override
|
||||
public void configUpdate(Integer value) {
|
||||
if (value != null) {
|
||||
updateSampleCount(value);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the {@link #SAMPLE_COUNT}. All {@link ClusterNode}s will be reset if newSampleCount
|
||||
* is different from {@link #SAMPLE_COUNT}.
|
||||
*
|
||||
* @param newSampleCount New sample count to set. This value must be divisor of 1000.
|
||||
*/
|
||||
public static void updateSampleCount(int newSampleCount) {
|
||||
if (newSampleCount != SAMPLE_COUNT) {
|
||||
SAMPLE_COUNT = newSampleCount;
|
||||
ClusterBuilderSlot.resetClusterNodes();
|
||||
}
|
||||
RecordLog.info("SAMPLE_COUNT updated to: {}", SAMPLE_COUNT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.node;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.LongAdder;
|
||||
|
||||
import com.alibaba.csp.sentinel.node.metric.MetricNode;
|
||||
import com.alibaba.csp.sentinel.slots.statistic.metric.ArrayMetric;
|
||||
import com.alibaba.csp.sentinel.slots.statistic.metric.Metric;
|
||||
import com.alibaba.csp.sentinel.util.TimeUtil;
|
||||
import com.alibaba.csp.sentinel.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* <p>The statistic node keep three kinds of real-time statistics metrics:</p>
|
||||
* <ol>
|
||||
* <li>metrics in second level ({@code rollingCounterInSecond})</li>
|
||||
* <li>metrics in minute level ({@code rollingCounterInMinute})</li>
|
||||
* <li>thread count</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p>
|
||||
* Sentinel use sliding window to record and count the resource statistics in real-time.
|
||||
* The sliding window infrastructure behind the {@link ArrayMetric} is {@code LeapArray}.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* case 1: When the first request comes in, Sentinel will create a new window bucket of
|
||||
* a specified time-span to store running statics, such as total response time(rt),
|
||||
* incoming request(QPS), block request(bq), etc. And the time-span is defined by sample count.
|
||||
* </p>
|
||||
* <pre>
|
||||
* 0 100ms
|
||||
* +-------+--→ Sliding Windows
|
||||
* ^
|
||||
* |
|
||||
* request
|
||||
* </pre>
|
||||
* <p>
|
||||
* Sentinel use the statics of the valid buckets to decide whether this request can be passed.
|
||||
* For example, if a rule defines that only 100 requests can be passed,
|
||||
* it will sum all qps in valid buckets, and compare it to the threshold defined in rule.
|
||||
* </p>
|
||||
*
|
||||
* <p>case 2: continuous requests</p>
|
||||
* <pre>
|
||||
* 0 100ms 200ms 300ms
|
||||
* +-------+-------+-------+-----→ Sliding Windows
|
||||
* ^
|
||||
* |
|
||||
* request
|
||||
* </pre>
|
||||
*
|
||||
* <p>case 3: requests keeps coming, and previous buckets become invalid</p>
|
||||
* <pre>
|
||||
* 0 100ms 200ms 800ms 900ms 1000ms 1300ms
|
||||
* +-------+-------+ ...... +-------+-------+ ...... +-------+-----→ Sliding Windows
|
||||
* ^
|
||||
* |
|
||||
* request
|
||||
* </pre>
|
||||
*
|
||||
* <p>The sliding window should become:</p>
|
||||
* <pre>
|
||||
* 300ms 800ms 900ms 1000ms 1300ms
|
||||
* + ...... +-------+ ...... +-------+-----→ Sliding Windows
|
||||
* ^
|
||||
* |
|
||||
* request
|
||||
* </pre>
|
||||
*
|
||||
* @author qinan.qn
|
||||
* @author jialiang.linjl
|
||||
*/
|
||||
public class StatisticNode implements Node {
|
||||
|
||||
/**
|
||||
* Holds statistics of the recent {@code INTERVAL} milliseconds. The {@code INTERVAL} is divided into time spans
|
||||
* by given {@code sampleCount}.
|
||||
*/
|
||||
private transient volatile Metric rollingCounterInSecond = new ArrayMetric(SampleCountProperty.SAMPLE_COUNT,
|
||||
IntervalProperty.INTERVAL);
|
||||
|
||||
/**
|
||||
* Holds statistics of the recent 60 seconds. The windowLengthInMs is deliberately set to 1000 milliseconds,
|
||||
* meaning each bucket per second, in this way we can get accurate statistics of each second.
|
||||
*/
|
||||
private transient Metric rollingCounterInMinute = new ArrayMetric(60, 60 * 1000, false);
|
||||
|
||||
/**
|
||||
* The counter for thread count.
|
||||
*/
|
||||
private LongAdder curThreadNum = new LongAdder();
|
||||
|
||||
/**
|
||||
* The last timestamp when metrics were fetched.
|
||||
*/
|
||||
private long lastFetchTime = -1;
|
||||
|
||||
@Override
|
||||
public Map<Long, MetricNode> metrics() {
|
||||
// The fetch operation is thread-safe under a single-thread scheduler pool.
|
||||
long currentTime = TimeUtil.currentTimeMillis();
|
||||
currentTime = currentTime - currentTime % 1000;
|
||||
Map<Long, MetricNode> metrics = new ConcurrentHashMap<>();
|
||||
List<MetricNode> nodesOfEverySecond = rollingCounterInMinute.details();
|
||||
long newLastFetchTime = lastFetchTime;
|
||||
// Iterate metrics of all resources, filter valid metrics (not-empty and up-to-date).
|
||||
for (MetricNode node : nodesOfEverySecond) {
|
||||
if (isNodeInTime(node, currentTime) && isValidMetricNode(node)) {
|
||||
metrics.put(node.getTimestamp(), node);
|
||||
newLastFetchTime = Math.max(newLastFetchTime, node.getTimestamp());
|
||||
}
|
||||
}
|
||||
lastFetchTime = newLastFetchTime;
|
||||
|
||||
return metrics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MetricNode> rawMetricsInMin(Predicate<Long> timePredicate) {
|
||||
return rollingCounterInMinute.detailsOnCondition(timePredicate);
|
||||
}
|
||||
|
||||
private boolean isNodeInTime(MetricNode node, long currentTime) {
|
||||
return node.getTimestamp() > lastFetchTime && node.getTimestamp() < currentTime;
|
||||
}
|
||||
|
||||
private boolean isValidMetricNode(MetricNode node) {
|
||||
return node.getPassQps() > 0 || node.getBlockQps() > 0 || node.getSuccessQps() > 0
|
||||
|| node.getExceptionQps() > 0 || node.getRt() > 0 || node.getOccupiedPassQps() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
rollingCounterInSecond = new ArrayMetric(SampleCountProperty.SAMPLE_COUNT, IntervalProperty.INTERVAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long totalRequest() {
|
||||
return rollingCounterInMinute.pass() + rollingCounterInMinute.block();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long blockRequest() {
|
||||
return rollingCounterInMinute.block();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double blockQps() {
|
||||
return rollingCounterInSecond.block() / rollingCounterInSecond.getWindowIntervalInSec();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double previousBlockQps() {
|
||||
return this.rollingCounterInMinute.previousWindowBlock();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double previousPassQps() {
|
||||
return this.rollingCounterInMinute.previousWindowPass();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double totalQps() {
|
||||
return passQps() + blockQps();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long totalSuccess() {
|
||||
return rollingCounterInMinute.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double exceptionQps() {
|
||||
return rollingCounterInSecond.exception() / rollingCounterInSecond.getWindowIntervalInSec();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long totalException() {
|
||||
return rollingCounterInMinute.exception();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double passQps() {
|
||||
return rollingCounterInSecond.pass() / rollingCounterInSecond.getWindowIntervalInSec();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long totalPass() {
|
||||
return rollingCounterInMinute.pass();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double successQps() {
|
||||
return rollingCounterInSecond.success() / rollingCounterInSecond.getWindowIntervalInSec();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double maxSuccessQps() {
|
||||
return (double) rollingCounterInSecond.maxSuccess() * rollingCounterInSecond.getSampleCount()
|
||||
/ rollingCounterInSecond.getWindowIntervalInSec();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double occupiedPassQps() {
|
||||
return rollingCounterInSecond.occupiedPass() / rollingCounterInSecond.getWindowIntervalInSec();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double avgRt() {
|
||||
long successCount = rollingCounterInSecond.success();
|
||||
if (successCount == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return rollingCounterInSecond.rt() * 1.0 / successCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double minRt() {
|
||||
return rollingCounterInSecond.minRt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int curThreadNum() {
|
||||
return (int)curThreadNum.sum();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPassRequest(int count) {
|
||||
rollingCounterInSecond.addPass(count);
|
||||
rollingCounterInMinute.addPass(count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRtAndSuccess(long rt, int successCount) {
|
||||
rollingCounterInSecond.addSuccess(successCount);
|
||||
rollingCounterInSecond.addRT(rt);
|
||||
|
||||
rollingCounterInMinute.addSuccess(successCount);
|
||||
rollingCounterInMinute.addRT(rt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void increaseBlockQps(int count) {
|
||||
rollingCounterInSecond.addBlock(count);
|
||||
rollingCounterInMinute.addBlock(count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void increaseExceptionQps(int count) {
|
||||
rollingCounterInSecond.addException(count);
|
||||
rollingCounterInMinute.addException(count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void increaseThreadNum() {
|
||||
curThreadNum.increment();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decreaseThreadNum() {
|
||||
curThreadNum.decrement();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug() {
|
||||
rollingCounterInSecond.debug();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long tryOccupyNext(long currentTime, int acquireCount, double threshold) {
|
||||
double maxCount = threshold * IntervalProperty.INTERVAL / 1000;
|
||||
long currentBorrow = rollingCounterInSecond.waiting();
|
||||
if (currentBorrow >= maxCount) {
|
||||
return OccupyTimeoutProperty.getOccupyTimeout();
|
||||
}
|
||||
|
||||
int windowLength = IntervalProperty.INTERVAL / SampleCountProperty.SAMPLE_COUNT;
|
||||
long earliestTime = currentTime - currentTime % windowLength + windowLength - IntervalProperty.INTERVAL;
|
||||
|
||||
int idx = 0;
|
||||
/*
|
||||
* Note: here {@code currentPass} may be less than it really is NOW, because time difference
|
||||
* since call rollingCounterInSecond.pass(). So in high concurrency, the following code may
|
||||
* lead more tokens be borrowed.
|
||||
*/
|
||||
long currentPass = rollingCounterInSecond.pass();
|
||||
while (earliestTime < currentTime) {
|
||||
long waitInMs = idx * windowLength + windowLength - currentTime % windowLength;
|
||||
if (waitInMs >= OccupyTimeoutProperty.getOccupyTimeout()) {
|
||||
break;
|
||||
}
|
||||
long windowPass = rollingCounterInSecond.getWindowPass(earliestTime);
|
||||
if (currentPass + currentBorrow + acquireCount - windowPass <= maxCount) {
|
||||
return waitInMs;
|
||||
}
|
||||
earliestTime += windowLength;
|
||||
currentPass -= windowPass;
|
||||
idx++;
|
||||
}
|
||||
|
||||
return OccupyTimeoutProperty.getOccupyTimeout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long waiting() {
|
||||
return rollingCounterInSecond.waiting();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addWaitingRequest(long futureTime, int acquireCount) {
|
||||
rollingCounterInSecond.addWaiting(futureTime, acquireCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOccupiedPass(int acquireCount) {
|
||||
rollingCounterInMinute.addOccupiedPass(acquireCount);
|
||||
rollingCounterInMinute.addPass(acquireCount);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.node.metric;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Metrics data for a specific resource at given {@code timestamp}.
|
||||
*
|
||||
* @author jialiang.linjl
|
||||
* @author Carpenter Lee
|
||||
*/
|
||||
public class MetricNode {
|
||||
|
||||
private String resource;
|
||||
/**
|
||||
* Resource classification (e.g. SQL or RPC)
|
||||
* @since 1.7.0
|
||||
*/
|
||||
private int classification;
|
||||
|
||||
private long timestamp;
|
||||
private long passQps;
|
||||
private long blockQps;
|
||||
private long successQps;
|
||||
private long exceptionQps;
|
||||
private long rt;
|
||||
|
||||
/**
|
||||
* @since 1.5.0
|
||||
*/
|
||||
private long occupiedPassQps;
|
||||
/**
|
||||
* @since 1.7.0
|
||||
*/
|
||||
private int concurrency;
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public long getOccupiedPassQps() {
|
||||
return occupiedPassQps;
|
||||
}
|
||||
|
||||
public void setOccupiedPassQps(long occupiedPassQps) {
|
||||
this.occupiedPassQps = occupiedPassQps;
|
||||
}
|
||||
|
||||
public void setTimestamp(long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public long getSuccessQps() {
|
||||
return successQps;
|
||||
}
|
||||
|
||||
public void setSuccessQps(long successQps) {
|
||||
this.successQps = successQps;
|
||||
}
|
||||
|
||||
public long getPassQps() {
|
||||
return passQps;
|
||||
}
|
||||
|
||||
public void setPassQps(long passQps) {
|
||||
this.passQps = passQps;
|
||||
}
|
||||
|
||||
public long getExceptionQps() {
|
||||
return exceptionQps;
|
||||
}
|
||||
|
||||
public void setExceptionQps(long exceptionQps) {
|
||||
this.exceptionQps = exceptionQps;
|
||||
}
|
||||
|
||||
public long getBlockQps() {
|
||||
return blockQps;
|
||||
}
|
||||
|
||||
public void setBlockQps(long blockQps) {
|
||||
this.blockQps = blockQps;
|
||||
}
|
||||
|
||||
public long getRt() {
|
||||
return rt;
|
||||
}
|
||||
|
||||
public void setRt(long rt) {
|
||||
this.rt = rt;
|
||||
}
|
||||
|
||||
public String getResource() {
|
||||
return resource;
|
||||
}
|
||||
|
||||
public void setResource(String resource) {
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
public int getClassification() {
|
||||
return classification;
|
||||
}
|
||||
|
||||
public MetricNode setClassification(int classification) {
|
||||
this.classification = classification;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getConcurrency() {
|
||||
return concurrency;
|
||||
}
|
||||
|
||||
public MetricNode setConcurrency(int concurrency) {
|
||||
this.concurrency = concurrency;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MetricNode{" +
|
||||
"resource='" + resource + '\'' +
|
||||
", classification=" + classification +
|
||||
", timestamp=" + timestamp +
|
||||
", passQps=" + passQps +
|
||||
", blockQps=" + blockQps +
|
||||
", successQps=" + successQps +
|
||||
", exceptionQps=" + exceptionQps +
|
||||
", rt=" + rt +
|
||||
", concurrency=" + concurrency +
|
||||
", occupiedPassQps=" + occupiedPassQps +
|
||||
'}';
|
||||
}
|
||||
|
||||
/**
|
||||
* To formatting string. All "|" in {@link #resource} will be replaced with
|
||||
* "_", format is: <br/>
|
||||
* <code>
|
||||
* timestamp|resource|passQps|blockQps|successQps|exceptionQps|rt|occupiedPassQps
|
||||
* </code>
|
||||
*
|
||||
* @return string format of this.
|
||||
*/
|
||||
public String toThinString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(timestamp).append("|");
|
||||
String legalName = resource.replaceAll("\\|", "_");
|
||||
sb.append(legalName).append("|");
|
||||
sb.append(passQps).append("|");
|
||||
sb.append(blockQps).append("|");
|
||||
sb.append(successQps).append("|");
|
||||
sb.append(exceptionQps).append("|");
|
||||
sb.append(rt).append("|");
|
||||
sb.append(occupiedPassQps).append("|");
|
||||
sb.append(concurrency).append("|");
|
||||
sb.append(classification);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse {@link MetricNode} from thin string, see {@link #toThinString()}
|
||||
*
|
||||
* @param line
|
||||
* @return
|
||||
*/
|
||||
public static MetricNode fromThinString(String line) {
|
||||
MetricNode node = new MetricNode();
|
||||
String[] strs = line.split("\\|");
|
||||
node.setTimestamp(Long.parseLong(strs[0]));
|
||||
node.setResource(strs[1]);
|
||||
node.setPassQps(Long.parseLong(strs[2]));
|
||||
node.setBlockQps(Long.parseLong(strs[3]));
|
||||
node.setSuccessQps(Long.parseLong(strs[4]));
|
||||
node.setExceptionQps(Long.parseLong(strs[5]));
|
||||
node.setRt(Long.parseLong(strs[6]));
|
||||
if (strs.length >= 8) {
|
||||
node.setOccupiedPassQps(Long.parseLong(strs[7]));
|
||||
}
|
||||
if (strs.length >= 9) {
|
||||
node.setConcurrency(Integer.parseInt(strs[8]));
|
||||
}
|
||||
if (strs.length == 10) {
|
||||
node.setClassification(Integer.parseInt(strs[9]));
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* To formatting string. All "|" in {@link MetricNode#resource} will be
|
||||
* replaced with "_", format is: <br/>
|
||||
* <code>
|
||||
* timestamp|yyyy-MM-dd HH:mm:ss|resource|passQps|blockQps|successQps|exceptionQps|rt|occupiedPassQps\n
|
||||
* </code>
|
||||
*
|
||||
* @return string format of this.
|
||||
*/
|
||||
public String toFatString() {
|
||||
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
StringBuilder sb = new StringBuilder(32);
|
||||
sb.delete(0, sb.length());
|
||||
sb.append(getTimestamp()).append("|");
|
||||
sb.append(df.format(new Date(getTimestamp()))).append("|");
|
||||
String legalName = getResource().replaceAll("\\|", "_");
|
||||
sb.append(legalName).append("|");
|
||||
sb.append(getPassQps()).append("|");
|
||||
sb.append(getBlockQps()).append("|");
|
||||
sb.append(getSuccessQps()).append("|");
|
||||
sb.append(getExceptionQps()).append("|");
|
||||
sb.append(getRt()).append("|");
|
||||
sb.append(getOccupiedPassQps()).append("|");
|
||||
sb.append(concurrency).append("|");
|
||||
sb.append(classification);
|
||||
sb.append('\n');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse {@link MetricNode} from fat string, see {@link #toFatString()}
|
||||
*
|
||||
* @param line
|
||||
* @return the {@link MetricNode} parsed.
|
||||
*/
|
||||
public static MetricNode fromFatString(String line) {
|
||||
String[] strs = line.split("\\|");
|
||||
Long time = Long.parseLong(strs[0]);
|
||||
MetricNode node = new MetricNode();
|
||||
node.setTimestamp(time);
|
||||
node.setResource(strs[2]);
|
||||
node.setPassQps(Long.parseLong(strs[3]));
|
||||
node.setBlockQps(Long.parseLong(strs[4]));
|
||||
node.setSuccessQps(Long.parseLong(strs[5]));
|
||||
node.setExceptionQps(Long.parseLong(strs[6]));
|
||||
node.setRt(Long.parseLong(strs[7]));
|
||||
if (strs.length >= 9) {
|
||||
node.setOccupiedPassQps(Long.parseLong(strs[8]));
|
||||
}
|
||||
if (strs.length >= 10) {
|
||||
node.setConcurrency(Integer.parseInt(strs[9]));
|
||||
}
|
||||
if (strs.length == 11) {
|
||||
node.setClassification(Integer.parseInt(strs[10]));
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.node.metric;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
|
||||
import com.alibaba.csp.sentinel.config.SentinelConfig;
|
||||
|
||||
/**
|
||||
* 从指定目录下找出所有的metric文件,并按照指定时间戳进行检索,参考{@link MetricSearcher#find(long, int)}。
|
||||
* 会借助索引以提高检索效率,参考{@link MetricWriter};还会在内部缓存上一次检索的文件指针,以便下一次顺序检索时
|
||||
* 减少读盘次数。
|
||||
*
|
||||
* @author leyou
|
||||
*/
|
||||
public class MetricSearcher {
|
||||
|
||||
private static final Charset defaultCharset = Charset.forName(SentinelConfig.charset());
|
||||
private final MetricsReader metricsReader;
|
||||
|
||||
private String baseDir;
|
||||
private String baseFileName;
|
||||
|
||||
private Position lastPosition = new Position();
|
||||
|
||||
/**
|
||||
* @param baseDir metric文件所在目录
|
||||
* @param baseFileName metric文件名的关键字,比如 alihot-metrics.log
|
||||
*/
|
||||
public MetricSearcher(String baseDir, String baseFileName) {
|
||||
this(baseDir, baseFileName, defaultCharset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param baseDir metric文件所在目录
|
||||
* @param baseFileName metric文件名的关键字,比如 alihot-metrics.log
|
||||
* @param charset
|
||||
*/
|
||||
public MetricSearcher(String baseDir, String baseFileName, Charset charset) {
|
||||
if (baseDir == null) {
|
||||
throw new IllegalArgumentException("baseDir can't be null");
|
||||
}
|
||||
if (baseFileName == null) {
|
||||
throw new IllegalArgumentException("baseFileName can't be null");
|
||||
}
|
||||
if (charset == null) {
|
||||
throw new IllegalArgumentException("charset can't be null");
|
||||
}
|
||||
this.baseDir = baseDir;
|
||||
if (!baseDir.endsWith(File.separator)) {
|
||||
this.baseDir += File.separator;
|
||||
}
|
||||
this.baseFileName = baseFileName;
|
||||
metricsReader = new MetricsReader(charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从beginTime开始,检索recommendLines条(大概)记录。同一秒中的数据是原子的,不能分割成多次查询。
|
||||
*
|
||||
* @param beginTimeMs 检索的最小时间戳
|
||||
* @param recommendLines 查询最多想得到的记录条数,返回条数会尽可能不超过这个数字。但是为保证每一秒的数据不被分割,有时候
|
||||
* 返回的记录条数会大于该数字。
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public synchronized List<MetricNode> find(long beginTimeMs, int recommendLines) throws Exception {
|
||||
List<String> fileNames = MetricWriter.listMetricFiles(baseDir, baseFileName);
|
||||
int i = 0;
|
||||
long offsetInIndex = 0;
|
||||
if (validPosition(beginTimeMs)) {
|
||||
i = fileNames.indexOf(lastPosition.metricFileName);
|
||||
if (i == -1) {
|
||||
i = 0;
|
||||
} else {
|
||||
offsetInIndex = lastPosition.offsetInIndex;
|
||||
}
|
||||
}
|
||||
for (; i < fileNames.size(); i++) {
|
||||
String fileName = fileNames.get(i);
|
||||
long offset = findOffset(beginTimeMs, fileName,
|
||||
MetricWriter.formIndexFileName(fileName), offsetInIndex);
|
||||
offsetInIndex = 0;
|
||||
if (offset != -1) {
|
||||
return metricsReader.readMetrics(fileNames, i, offset, recommendLines);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find metric between [beginTimeMs, endTimeMs], both side inclusive.
|
||||
* When identity is null, all metric between the time intervalMs will be read, otherwise, only the specific
|
||||
* identity will be read.
|
||||
*/
|
||||
public synchronized List<MetricNode> findByTimeAndResource(long beginTimeMs, long endTimeMs, String identity)
|
||||
throws Exception {
|
||||
List<String> fileNames = MetricWriter.listMetricFiles(baseDir, baseFileName);
|
||||
//RecordLog.info("pid=" + pid + ", findByTimeAndResource([" + beginTimeMs + ", " + endTimeMs
|
||||
// + "], " + identity + ")");
|
||||
int i = 0;
|
||||
long offsetInIndex = 0;
|
||||
if (validPosition(beginTimeMs)) {
|
||||
i = fileNames.indexOf(lastPosition.metricFileName);
|
||||
if (i == -1) {
|
||||
i = 0;
|
||||
} else {
|
||||
offsetInIndex = lastPosition.offsetInIndex;
|
||||
}
|
||||
} else {
|
||||
//RecordLog.info("lastPosition is invalidate, will re iterate all files, pid = " + pid);
|
||||
}
|
||||
|
||||
for (; i < fileNames.size(); i++) {
|
||||
String fileName = fileNames.get(i);
|
||||
long offset = findOffset(beginTimeMs, fileName,
|
||||
MetricWriter.formIndexFileName(fileName), offsetInIndex);
|
||||
offsetInIndex = 0;
|
||||
if (offset != -1) {
|
||||
return metricsReader.readMetricsByEndTime(fileNames, i, offset, beginTimeMs, endTimeMs, identity);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录上一次读取的index文件位置和数值
|
||||
*/
|
||||
private static final class Position {
|
||||
String metricFileName;
|
||||
String indexFileName;
|
||||
/**
|
||||
* 索引文件内的偏移
|
||||
*/
|
||||
long offsetInIndex;
|
||||
/**
|
||||
* 索引文件中offsetInIndex位置上的数字,秒数。
|
||||
*/
|
||||
long second;
|
||||
}
|
||||
|
||||
/**
|
||||
* The position we cached is useful only when {@code beginTimeMs} is >= {@code lastPosition.second}
|
||||
* and the index file exists and the second we cached is same as in the index file.
|
||||
*/
|
||||
private boolean validPosition(long beginTimeMs) {
|
||||
if (beginTimeMs / 1000 < lastPosition.second) {
|
||||
return false;
|
||||
}
|
||||
if (lastPosition.indexFileName == null) {
|
||||
return false;
|
||||
}
|
||||
// index file dose not exits
|
||||
if (!new File(lastPosition.indexFileName).exists()) {
|
||||
return false;
|
||||
}
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(lastPosition.indexFileName);
|
||||
in.getChannel().position(lastPosition.offsetInIndex);
|
||||
DataInputStream indexIn = new DataInputStream(in);
|
||||
// timestamp(second) in the specific position == that we cached
|
||||
return indexIn.readLong() == lastPosition.second;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
} finally {
|
||||
if (in != null) {
|
||||
try {
|
||||
in.close();
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private long findOffset(long beginTime, String metricFileName,
|
||||
String idxFileName, long offsetInIndex) throws Exception {
|
||||
lastPosition.metricFileName = null;
|
||||
lastPosition.indexFileName = null;
|
||||
if (!new File(idxFileName).exists()) {
|
||||
return -1;
|
||||
}
|
||||
long beginSecond = beginTime / 1000;
|
||||
FileInputStream in = new FileInputStream(idxFileName);
|
||||
in.getChannel().position(offsetInIndex);
|
||||
DataInputStream indexIn = new DataInputStream(in);
|
||||
long offset;
|
||||
try {
|
||||
long second;
|
||||
lastPosition.offsetInIndex = in.getChannel().position();
|
||||
while ((second = indexIn.readLong()) < beginSecond) {
|
||||
offset = indexIn.readLong();
|
||||
lastPosition.offsetInIndex = in.getChannel().position();
|
||||
}
|
||||
offset = indexIn.readLong();
|
||||
lastPosition.metricFileName = metricFileName;
|
||||
lastPosition.indexFileName = idxFileName;
|
||||
lastPosition.second = second;
|
||||
return offset;
|
||||
} catch (EOFException ignore) {
|
||||
return -1;
|
||||
} finally {
|
||||
indexIn.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.node.metric;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import com.alibaba.csp.sentinel.Constants;
|
||||
import com.alibaba.csp.sentinel.config.SentinelConfig;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.node.ClusterNode;
|
||||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
|
||||
import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot;
|
||||
|
||||
/**
|
||||
* @author jialiang.linjl
|
||||
*/
|
||||
public class MetricTimerListener implements Runnable {
|
||||
|
||||
private static final MetricWriter metricWriter = new MetricWriter(SentinelConfig.singleMetricFileSize(),
|
||||
SentinelConfig.totalMetricFileCount());
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Map<Long, List<MetricNode>> maps = new TreeMap<>();
|
||||
for (Entry<ResourceWrapper, ClusterNode> e : ClusterBuilderSlot.getClusterNodeMap().entrySet()) {
|
||||
ClusterNode node = e.getValue();
|
||||
Map<Long, MetricNode> metrics = node.metrics();
|
||||
aggregate(maps, metrics, node);
|
||||
}
|
||||
aggregate(maps, Constants.ENTRY_NODE.metrics(), Constants.ENTRY_NODE);
|
||||
if (!maps.isEmpty()) {
|
||||
for (Entry<Long, List<MetricNode>> entry : maps.entrySet()) {
|
||||
try {
|
||||
metricWriter.write(entry.getKey(), entry.getValue());
|
||||
} catch (Exception e) {
|
||||
RecordLog.warn("[MetricTimerListener] Write metric error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void aggregate(Map<Long, List<MetricNode>> maps, Map<Long, MetricNode> metrics, ClusterNode node) {
|
||||
for (Entry<Long, MetricNode> entry : metrics.entrySet()) {
|
||||
long time = entry.getKey();
|
||||
MetricNode metricNode = entry.getValue();
|
||||
metricNode.setResource(node.getName());
|
||||
metricNode.setClassification(node.getResourceType());
|
||||
maps.computeIfAbsent(time, k -> new ArrayList<MetricNode>());
|
||||
List<MetricNode> nodes = maps.get(time);
|
||||
nodes.add(entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,402 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.node.metric;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import com.alibaba.csp.sentinel.log.LogBase;
|
||||
import com.alibaba.csp.sentinel.util.PidUtil;
|
||||
import com.alibaba.csp.sentinel.config.SentinelConfig;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
|
||||
/**
|
||||
* This class is responsible for writing {@link MetricNode} to disk:
|
||||
* <ol>
|
||||
* <li>metric with the same second should write to the same file;</li>
|
||||
* <li>single file size must be controlled;</li>
|
||||
* <li>file name is like: {@code ${appName}-metrics.log.pid${pid}.yyyy-MM-dd.[number]}</li>
|
||||
* <li>metric of different day should in different file;</li>
|
||||
* <li>every metric file is accompanied with an index file, which file name is {@code ${metricFileName}.idx}</li>
|
||||
* </ol>
|
||||
*
|
||||
* @author Carpenter Lee
|
||||
*/
|
||||
public class MetricWriter {
|
||||
|
||||
private static final String CHARSET = SentinelConfig.charset();
|
||||
public static final String METRIC_BASE_DIR = LogBase.getLogBaseDir();
|
||||
/**
|
||||
* Note: {@link MetricFileNameComparator}'s implementation relies on the metric file name,
|
||||
* so we should be careful when changing the metric file name.
|
||||
*
|
||||
* @see #formMetricFileName(String, int)
|
||||
*/
|
||||
public static final String METRIC_FILE = "metrics.log";
|
||||
public static final String METRIC_FILE_INDEX_SUFFIX = ".idx";
|
||||
public static final Comparator<String> METRIC_FILE_NAME_CMP = new MetricFileNameComparator();
|
||||
|
||||
private final DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
/**
|
||||
* 排除时差干扰
|
||||
*/
|
||||
private long timeSecondBase;
|
||||
private String baseDir;
|
||||
private String baseFileName;
|
||||
/**
|
||||
* file must exist when writing
|
||||
*/
|
||||
private File curMetricFile;
|
||||
private File curMetricIndexFile;
|
||||
|
||||
private FileOutputStream outMetric;
|
||||
private DataOutputStream outIndex;
|
||||
private BufferedOutputStream outMetricBuf;
|
||||
private long singleFileSize;
|
||||
private int totalFileCount;
|
||||
private boolean append = false;
|
||||
private final int pid = PidUtil.getPid();
|
||||
|
||||
/**
|
||||
* 秒级统计,忽略毫秒数。
|
||||
*/
|
||||
private long lastSecond = -1;
|
||||
|
||||
public MetricWriter(long singleFileSize) {
|
||||
this(singleFileSize, 6);
|
||||
}
|
||||
|
||||
public MetricWriter(long singleFileSize, int totalFileCount) {
|
||||
if (singleFileSize <= 0 || totalFileCount <= 0) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
RecordLog.info("[MetricWriter] Creating new MetricWriter, singleFileSize={}, totalFileCount={}",
|
||||
singleFileSize, totalFileCount);
|
||||
this.baseDir = METRIC_BASE_DIR;
|
||||
File dir = new File(baseDir);
|
||||
if (!dir.exists()) {
|
||||
dir.mkdirs();
|
||||
}
|
||||
|
||||
long time = System.currentTimeMillis();
|
||||
this.lastSecond = time / 1000;
|
||||
this.singleFileSize = singleFileSize;
|
||||
this.totalFileCount = totalFileCount;
|
||||
try {
|
||||
this.timeSecondBase = df.parse("1970-01-01 00:00:00").getTime() / 1000;
|
||||
} catch (Exception e) {
|
||||
RecordLog.warn("[MetricWriter] Create new MetricWriter error", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果传入了time,就认为nodes中所有的时间时间戳都是time.
|
||||
*
|
||||
* @param time
|
||||
* @param nodes
|
||||
*/
|
||||
public synchronized void write(long time, List<MetricNode> nodes) throws Exception {
|
||||
if (nodes == null) {
|
||||
return;
|
||||
}
|
||||
for (MetricNode node : nodes) {
|
||||
node.setTimestamp(time);
|
||||
}
|
||||
|
||||
String appName = SentinelConfig.getAppName();
|
||||
if (appName == null) {
|
||||
appName = "";
|
||||
}
|
||||
// first write, should create file
|
||||
if (curMetricFile == null) {
|
||||
baseFileName = formMetricFileName(appName, pid);
|
||||
closeAndNewFile(nextFileNameOfDay(time));
|
||||
}
|
||||
if (!(curMetricFile.exists() && curMetricIndexFile.exists())) {
|
||||
closeAndNewFile(nextFileNameOfDay(time));
|
||||
}
|
||||
|
||||
long second = time / 1000;
|
||||
if (second < lastSecond) {
|
||||
// 时间靠前的直接忽略,不应该发生。
|
||||
} else if (second == lastSecond) {
|
||||
for (MetricNode node : nodes) {
|
||||
outMetricBuf.write(node.toFatString().getBytes(CHARSET));
|
||||
}
|
||||
outMetricBuf.flush();
|
||||
if (!validSize()) {
|
||||
closeAndNewFile(nextFileNameOfDay(time));
|
||||
}
|
||||
} else {
|
||||
writeIndex(second, outMetric.getChannel().position());
|
||||
if (isNewDay(lastSecond, second)) {
|
||||
closeAndNewFile(nextFileNameOfDay(time));
|
||||
for (MetricNode node : nodes) {
|
||||
outMetricBuf.write(node.toFatString().getBytes(CHARSET));
|
||||
}
|
||||
outMetricBuf.flush();
|
||||
if (!validSize()) {
|
||||
closeAndNewFile(nextFileNameOfDay(time));
|
||||
}
|
||||
} else {
|
||||
for (MetricNode node : nodes) {
|
||||
outMetricBuf.write(node.toFatString().getBytes(CHARSET));
|
||||
}
|
||||
outMetricBuf.flush();
|
||||
if (!validSize()) {
|
||||
closeAndNewFile(nextFileNameOfDay(time));
|
||||
}
|
||||
}
|
||||
lastSecond = second;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void close() throws Exception {
|
||||
if (outMetricBuf != null) {
|
||||
outMetricBuf.close();
|
||||
}
|
||||
if (outIndex != null) {
|
||||
outIndex.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void writeIndex(long time, long offset) throws Exception {
|
||||
outIndex.writeLong(time);
|
||||
outIndex.writeLong(offset);
|
||||
outIndex.flush();
|
||||
}
|
||||
|
||||
private String nextFileNameOfDay(long time) {
|
||||
List<String> list = new ArrayList<String>();
|
||||
File baseFile = new File(baseDir);
|
||||
DateFormat fileNameDf = new SimpleDateFormat("yyyy-MM-dd");
|
||||
String dateStr = fileNameDf.format(new Date(time));
|
||||
String fileNameModel = baseFileName + "." + dateStr;
|
||||
for (File file : baseFile.listFiles()) {
|
||||
String fileName = file.getName();
|
||||
if (fileName.contains(fileNameModel)
|
||||
&& !fileName.endsWith(METRIC_FILE_INDEX_SUFFIX)
|
||||
&& !fileName.endsWith(".lck")) {
|
||||
list.add(file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
Collections.sort(list, METRIC_FILE_NAME_CMP);
|
||||
if (list.isEmpty()) {
|
||||
return baseDir + fileNameModel;
|
||||
}
|
||||
String last = list.get(list.size() - 1);
|
||||
int n = 0;
|
||||
String[] strs = last.split("\\.");
|
||||
if (strs.length > 0 && strs[strs.length - 1].matches("[0-9]{1,10}")) {
|
||||
n = Integer.parseInt(strs[strs.length - 1]);
|
||||
}
|
||||
return baseDir + fileNameModel + "." + (n + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* A comparator for metric file name. Metric file name is like: <br/>
|
||||
* <pre>
|
||||
* metrics.log.2018-03-06
|
||||
* metrics.log.2018-03-07
|
||||
* metrics.log.2018-03-07.10
|
||||
* metrics.log.2018-03-06.100
|
||||
* </pre>
|
||||
* <p>
|
||||
* File name with the early date is smaller, if date is same, the one with the small file number is smaller.
|
||||
* Note that if the name is an absolute path, only the fileName({@link File#getName()}) part will be considered.
|
||||
* So the above file names should be sorted as: <br/>
|
||||
* <pre>
|
||||
* metrics.log.2018-03-06
|
||||
* metrics.log.2018-03-06.100
|
||||
* metrics.log.2018-03-07
|
||||
* metrics.log.2018-03-07.10
|
||||
*
|
||||
* </pre>
|
||||
* </p>
|
||||
*/
|
||||
private static final class MetricFileNameComparator implements Comparator<String> {
|
||||
private final String pid = "pid";
|
||||
|
||||
@Override
|
||||
public int compare(String o1, String o2) {
|
||||
String name1 = new File(o1).getName();
|
||||
String name2 = new File(o2).getName();
|
||||
String dateStr1 = name1.split("\\.")[2];
|
||||
String dateStr2 = name2.split("\\.")[2];
|
||||
// in case of file name contains pid, skip it, like Sentinel-Admin-metrics.log.pid22568.2018-12-24
|
||||
if (dateStr1.startsWith(pid)) {
|
||||
dateStr1 = name1.split("\\.")[3];
|
||||
dateStr2 = name2.split("\\.")[3];
|
||||
}
|
||||
|
||||
// compare date first
|
||||
int t = dateStr1.compareTo(dateStr2);
|
||||
if (t != 0) {
|
||||
return t;
|
||||
}
|
||||
|
||||
// same date, compare file number
|
||||
t = name1.length() - name2.length();
|
||||
if (t != 0) {
|
||||
return t;
|
||||
}
|
||||
return name1.compareTo(name2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all metric files' name in {@code baseDir}. The file name must like
|
||||
* <pre>
|
||||
* baseFileName + ".yyyy-MM-dd.number"
|
||||
* </pre>
|
||||
* and not endsWith {@link #METRIC_FILE_INDEX_SUFFIX} or ".lck".
|
||||
*
|
||||
* @param baseDir the directory to search.
|
||||
* @param baseFileName the file name pattern.
|
||||
* @return the metric files' absolute path({@link File#getAbsolutePath()})
|
||||
* @throws Exception
|
||||
*/
|
||||
static List<String> listMetricFiles(String baseDir, String baseFileName) throws Exception {
|
||||
List<String> list = new ArrayList<String>();
|
||||
File baseFile = new File(baseDir);
|
||||
File[] files = baseFile.listFiles();
|
||||
if (files == null) {
|
||||
return list;
|
||||
}
|
||||
for (File file : files) {
|
||||
String fileName = file.getName();
|
||||
if (file.isFile()
|
||||
&& fileNameMatches(fileName, baseFileName)
|
||||
&& !fileName.endsWith(MetricWriter.METRIC_FILE_INDEX_SUFFIX)
|
||||
&& !fileName.endsWith(".lck")) {
|
||||
list.add(file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
Collections.sort(list, MetricWriter.METRIC_FILE_NAME_CMP);
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether fileName matches baseFileName. fileName matches baseFileName when
|
||||
* <pre>
|
||||
* fileName = baseFileName + ".yyyy-MM-dd.number"
|
||||
* </pre>
|
||||
*
|
||||
* @param fileName file name
|
||||
* @param baseFileName base file name.
|
||||
* @return if fileName matches baseFileName return true, else return false.
|
||||
*/
|
||||
public static boolean fileNameMatches(String fileName, String baseFileName) {
|
||||
if (fileName.startsWith(baseFileName)) {
|
||||
String part = fileName.substring(baseFileName.length());
|
||||
// part is like: ".yyyy-MM-dd.number", eg. ".2018-12-24.11"
|
||||
return part.matches("\\.[0-9]{4}-[0-9]{2}-[0-9]{2}(\\.[0-9]*)?");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void removeMoreFiles() throws Exception {
|
||||
List<String> list = listMetricFiles(baseDir, baseFileName);
|
||||
if (list == null || list.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < list.size() - totalFileCount + 1; i++) {
|
||||
String fileName = list.get(i);
|
||||
String indexFile = formIndexFileName(fileName);
|
||||
new File(fileName).delete();
|
||||
RecordLog.info("[MetricWriter] Removing metric file: {}", fileName);
|
||||
new File(indexFile).delete();
|
||||
RecordLog.info("[MetricWriter] Removing metric index file: {}", indexFile);
|
||||
}
|
||||
}
|
||||
|
||||
private void closeAndNewFile(String fileName) throws Exception {
|
||||
removeMoreFiles();
|
||||
if (outMetricBuf != null) {
|
||||
outMetricBuf.close();
|
||||
}
|
||||
if (outIndex != null) {
|
||||
outIndex.close();
|
||||
}
|
||||
outMetric = new FileOutputStream(fileName, append);
|
||||
outMetricBuf = new BufferedOutputStream(outMetric);
|
||||
curMetricFile = new File(fileName);
|
||||
String idxFile = formIndexFileName(fileName);
|
||||
curMetricIndexFile = new File(idxFile);
|
||||
outIndex = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(idxFile, append)));
|
||||
RecordLog.info("[MetricWriter] New metric file created: {}", fileName);
|
||||
RecordLog.info("[MetricWriter] New metric index file created: {}", idxFile);
|
||||
}
|
||||
|
||||
private boolean validSize() throws Exception {
|
||||
long size = outMetric.getChannel().size();
|
||||
return size < singleFileSize;
|
||||
}
|
||||
|
||||
private boolean isNewDay(long lastSecond, long second) {
|
||||
long lastDay = (lastSecond - timeSecondBase) / 86400;
|
||||
long newDay = (second - timeSecondBase) / 86400;
|
||||
return newDay > lastDay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form metric file name use the specific appName and pid. Note that only
|
||||
* form the file name, not include path.
|
||||
*
|
||||
* Note: {@link MetricFileNameComparator}'s implementation relays on the metric file name,
|
||||
* we should be careful when changing the metric file name.
|
||||
*
|
||||
* @param appName
|
||||
* @param pid
|
||||
* @return metric file name.
|
||||
*/
|
||||
public static String formMetricFileName(String appName, int pid) {
|
||||
if (appName == null) {
|
||||
appName = "";
|
||||
}
|
||||
// dot is special char that should be replaced.
|
||||
final String dot = ".";
|
||||
final String separator = "-";
|
||||
if (appName.contains(dot)) {
|
||||
appName = appName.replace(dot, separator);
|
||||
}
|
||||
String name = appName + separator + METRIC_FILE;
|
||||
if (LogBase.isLogNameUsePid()) {
|
||||
name += ".pid" + pid;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form index file name of the {@code metricFileName}
|
||||
*
|
||||
* @param metricFileName
|
||||
* @return the index file name of the metricFileName
|
||||
*/
|
||||
public static String formIndexFileName(String metricFileName) {
|
||||
return metricFileName + METRIC_FILE_INDEX_SUFFIX;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.node.metric;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Reads metrics data from log file.
|
||||
*/
|
||||
class MetricsReader {
|
||||
|
||||
/**
|
||||
* Avoid OOM in any cases.
|
||||
*/
|
||||
private static final int MAX_LINES_RETURN = 100000;
|
||||
|
||||
private final Charset charset;
|
||||
|
||||
public MetricsReader(Charset charset) {
|
||||
this.charset = charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if should continue read, return true, else false.
|
||||
*/
|
||||
boolean readMetricsInOneFileByEndTime(List<MetricNode> list, String fileName, long offset,
|
||||
long beginTimeMs, long endTimeMs, String identity) throws Exception {
|
||||
FileInputStream in = null;
|
||||
long beginSecond = beginTimeMs / 1000;
|
||||
long endSecond = endTimeMs / 1000;
|
||||
try {
|
||||
in = new FileInputStream(fileName);
|
||||
in.getChannel().position(offset);
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(in, charset));
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
MetricNode node = MetricNode.fromFatString(line);
|
||||
long currentSecond = node.getTimestamp() / 1000;
|
||||
// currentSecond should >= beginSecond, otherwise a wrong metric file must occur
|
||||
if (currentSecond < beginSecond) {
|
||||
return false;
|
||||
}
|
||||
if (currentSecond <= endSecond) {
|
||||
// read all
|
||||
if (identity == null) {
|
||||
list.add(node);
|
||||
} else if (node.getResource().equals(identity)) {
|
||||
list.add(node);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
if (list.size() >= MAX_LINES_RETURN) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (in != null) {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void readMetricsInOneFile(List<MetricNode> list, String fileName,
|
||||
long offset, int recommendLines) throws Exception {
|
||||
//if(list.size() >= recommendLines){
|
||||
// return;
|
||||
//}
|
||||
long lastSecond = -1;
|
||||
if (list.size() > 0) {
|
||||
lastSecond = list.get(list.size() - 1).getTimestamp() / 1000;
|
||||
}
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(fileName);
|
||||
in.getChannel().position(offset);
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(in, charset));
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
MetricNode node = MetricNode.fromFatString(line);
|
||||
long currentSecond = node.getTimestamp() / 1000;
|
||||
|
||||
if (list.size() < recommendLines) {
|
||||
list.add(node);
|
||||
} else if (currentSecond == lastSecond) {
|
||||
list.add(node);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
lastSecond = currentSecond;
|
||||
}
|
||||
} finally {
|
||||
if (in != null) {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When identity is null, all metric between the time intervalMs will be read, otherwise, only the specific
|
||||
* identity will be read.
|
||||
*/
|
||||
List<MetricNode> readMetricsByEndTime(List<String> fileNames, int pos, long offset,
|
||||
long beginTimeMs, long endTimeMs, String identity) throws Exception {
|
||||
List<MetricNode> list = new ArrayList<MetricNode>(1024);
|
||||
if (readMetricsInOneFileByEndTime(list, fileNames.get(pos++), offset, beginTimeMs, endTimeMs, identity)) {
|
||||
while (pos < fileNames.size()
|
||||
&& readMetricsInOneFileByEndTime(list, fileNames.get(pos++), 0, beginTimeMs, endTimeMs, identity)) {
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
List<MetricNode> readMetrics(List<String> fileNames, int pos,
|
||||
long offset, int recommendLines) throws Exception {
|
||||
List<MetricNode> list = new ArrayList<MetricNode>(recommendLines);
|
||||
readMetricsInOneFile(list, fileNames.get(pos++), offset, recommendLines);
|
||||
while (list.size() < recommendLines && pos < fileNames.size()) {
|
||||
readMetricsInOneFile(list, fileNames.get(pos++), 0, recommendLines);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.property;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
|
||||
public class DynamicSentinelProperty<T> implements SentinelProperty<T> {
|
||||
|
||||
protected Set<PropertyListener<T>> listeners = Collections.synchronizedSet(new HashSet<PropertyListener<T>>());
|
||||
private T value = null;
|
||||
|
||||
public DynamicSentinelProperty() {
|
||||
}
|
||||
|
||||
public DynamicSentinelProperty(T value) {
|
||||
super();
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(PropertyListener<T> listener) {
|
||||
listeners.add(listener);
|
||||
listener.configLoad(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(PropertyListener<T> listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateValue(T newValue) {
|
||||
if (isEqual(value, newValue)) {
|
||||
return false;
|
||||
}
|
||||
RecordLog.info("[DynamicSentinelProperty] Config will be updated to: {}", newValue);
|
||||
|
||||
value = newValue;
|
||||
for (PropertyListener<T> listener : listeners) {
|
||||
listener.configUpdate(newValue);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isEqual(T oldValue, T newValue) {
|
||||
if (oldValue == null && newValue == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (oldValue == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return oldValue.equals(newValue);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
listeners.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.property;
|
||||
|
||||
/**
|
||||
* A {@link SentinelProperty} that will never inform the {@link PropertyListener} on it.
|
||||
*
|
||||
* @author leyou
|
||||
*/
|
||||
public final class NoOpSentinelProperty implements SentinelProperty<Object> {
|
||||
|
||||
@Override
|
||||
public void addListener(PropertyListener<Object> listener) { }
|
||||
|
||||
@Override
|
||||
public void removeListener(PropertyListener<Object> listener) { }
|
||||
|
||||
@Override
|
||||
public boolean updateValue(Object newValue) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.property;
|
||||
|
||||
/**
|
||||
* This class holds callback method when {@link SentinelProperty#updateValue(Object)} need inform the listener
|
||||
*
|
||||
* @author jialiang.linjl
|
||||
*/
|
||||
public interface PropertyListener<T> {
|
||||
|
||||
/**
|
||||
* Callback method when {@link SentinelProperty#updateValue(Object)} need inform the listener.
|
||||
*
|
||||
* @param value updated value.
|
||||
*/
|
||||
void configUpdate(T value);
|
||||
|
||||
/**
|
||||
* The first time of the {@code value}'s load.
|
||||
*
|
||||
* @param value the value loaded.
|
||||
*/
|
||||
void configLoad(T value);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.property;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* This class holds current value of the config, and is responsible for informing all {@link PropertyListener}s
|
||||
* added on this when the config is updated.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that not every {@link #updateValue(Object newValue)} invocation should inform the listeners, only when
|
||||
* {@code newValue} is not Equals to the old value, informing is needed.
|
||||
* </p>
|
||||
*
|
||||
* @param <T> the target type.
|
||||
* @author Carpenter Lee
|
||||
*/
|
||||
public interface SentinelProperty<T> {
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Add a {@link PropertyListener} to this {@link SentinelProperty}. After the listener is added,
|
||||
* {@link #updateValue(Object)} will inform the listener if needed.
|
||||
* </p>
|
||||
* <p>
|
||||
* This method can invoke multi times to add more than one listeners.
|
||||
* </p>
|
||||
*
|
||||
* @param listener listener to add.
|
||||
*/
|
||||
void addListener(PropertyListener<T> listener);
|
||||
|
||||
/**
|
||||
* Remove the {@link PropertyListener} on this. After removing, {@link #updateValue(Object)}
|
||||
* will not inform the listener.
|
||||
*
|
||||
* @param listener the listener to remove.
|
||||
*/
|
||||
void removeListener(PropertyListener<T> listener);
|
||||
|
||||
/**
|
||||
* Update the {@code newValue} as the current value of this property and inform all {@link PropertyListener}s
|
||||
* added on this only when new {@code newValue} is not Equals to the old value.
|
||||
*
|
||||
* @param newValue the new value.
|
||||
* @return true if the value in property has been updated, otherwise false
|
||||
*/
|
||||
boolean updateValue(T newValue);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.property;
|
||||
|
||||
public abstract class SimplePropertyListener<T> implements PropertyListener<T> {
|
||||
|
||||
@Override
|
||||
public void configLoad(T value) {
|
||||
configUpdate(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.slotchain;
|
||||
|
||||
import com.alibaba.csp.sentinel.context.Context;
|
||||
|
||||
/**
|
||||
* @author qinan.qn
|
||||
* @author jialiang.linjl
|
||||
*/
|
||||
public abstract class AbstractLinkedProcessorSlot<T> implements ProcessorSlot<T> {
|
||||
|
||||
private AbstractLinkedProcessorSlot<?> next = null;
|
||||
|
||||
@Override
|
||||
public void fireEntry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args)
|
||||
throws Throwable {
|
||||
if (next != null) {
|
||||
next.transformEntry(context, resourceWrapper, obj, count, prioritized, args);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
void transformEntry(Context context, ResourceWrapper resourceWrapper, Object o, int count, boolean prioritized, Object... args)
|
||||
throws Throwable {
|
||||
T t = (T)o;
|
||||
entry(context, resourceWrapper, t, count, prioritized, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fireExit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
|
||||
if (next != null) {
|
||||
next.exit(context, resourceWrapper, count, args);
|
||||
}
|
||||
}
|
||||
|
||||
public AbstractLinkedProcessorSlot<?> getNext() {
|
||||
return next;
|
||||
}
|
||||
|
||||
public void setNext(AbstractLinkedProcessorSlot<?> next) {
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.slotchain;
|
||||
|
||||
import com.alibaba.csp.sentinel.context.Context;
|
||||
|
||||
/**
|
||||
* @author qinan.qn
|
||||
* @author jialiang.linjl
|
||||
*/
|
||||
public class DefaultProcessorSlotChain extends ProcessorSlotChain {
|
||||
|
||||
AbstractLinkedProcessorSlot<?> first = new AbstractLinkedProcessorSlot<Object>() {
|
||||
|
||||
@Override
|
||||
public void entry(Context context, ResourceWrapper resourceWrapper, Object t, int count, boolean prioritized, Object... args)
|
||||
throws Throwable {
|
||||
super.fireEntry(context, resourceWrapper, t, count, prioritized, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
|
||||
super.fireExit(context, resourceWrapper, count, args);
|
||||
}
|
||||
|
||||
};
|
||||
AbstractLinkedProcessorSlot<?> end = first;
|
||||
|
||||
@Override
|
||||
public void addFirst(AbstractLinkedProcessorSlot<?> protocolProcessor) {
|
||||
protocolProcessor.setNext(first.getNext());
|
||||
first.setNext(protocolProcessor);
|
||||
if (end == first) {
|
||||
end = protocolProcessor;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLast(AbstractLinkedProcessorSlot<?> protocolProcessor) {
|
||||
end.setNext(protocolProcessor);
|
||||
end = protocolProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link #addLast(AbstractLinkedProcessorSlot)}.
|
||||
*
|
||||
* @param next processor to be added.
|
||||
*/
|
||||
@Override
|
||||
public void setNext(AbstractLinkedProcessorSlot<?> next) {
|
||||
addLast(next);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractLinkedProcessorSlot<?> getNext() {
|
||||
return first.getNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void entry(Context context, ResourceWrapper resourceWrapper, Object t, int count, boolean prioritized, Object... args)
|
||||
throws Throwable {
|
||||
first.transformEntry(context, resourceWrapper, t, count, prioritized, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
|
||||
first.exit(context, resourceWrapper, count, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.slotchain;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import com.alibaba.csp.sentinel.EntryType;
|
||||
import com.alibaba.csp.sentinel.ResourceTypeConstants;
|
||||
import com.alibaba.csp.sentinel.util.IdUtil;
|
||||
import com.alibaba.csp.sentinel.util.MethodUtil;
|
||||
|
||||
/**
|
||||
* Resource wrapper for method invocation.
|
||||
*
|
||||
* @author qinan.qn
|
||||
*/
|
||||
public class MethodResourceWrapper extends ResourceWrapper {
|
||||
|
||||
private final transient Method method;
|
||||
|
||||
public MethodResourceWrapper(Method method, EntryType e) {
|
||||
this(method, e, ResourceTypeConstants.COMMON);
|
||||
}
|
||||
|
||||
public MethodResourceWrapper(Method method, EntryType e, int resType) {
|
||||
super(MethodUtil.resolveMethodName(method), e, resType);
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
public Method getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getShowName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MethodResourceWrapper{" +
|
||||
"name='" + name + '\'' +
|
||||
", entryType=" + entryType +
|
||||
", resourceType=" + resourceType +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.slotchain;
|
||||
|
||||
import com.alibaba.csp.sentinel.context.Context;
|
||||
|
||||
/**
|
||||
* A container of some process and ways of notification when the process is finished.
|
||||
*
|
||||
* @author qinan.qn
|
||||
* @author jialiang.linjl
|
||||
* @author leyou(lihao)
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public interface ProcessorSlot<T> {
|
||||
|
||||
/**
|
||||
* Entrance of this slot.
|
||||
*
|
||||
* @param context current {@link Context}
|
||||
* @param resourceWrapper current resource
|
||||
* @param param generics parameter, usually is a {@link com.alibaba.csp.sentinel.node.Node}
|
||||
* @param count tokens needed
|
||||
* @param prioritized whether the entry is prioritized
|
||||
* @param args parameters of the original call
|
||||
* @throws Throwable blocked exception or unexpected error
|
||||
*/
|
||||
void entry(Context context, ResourceWrapper resourceWrapper, T param, int count, boolean prioritized,
|
||||
Object... args) throws Throwable;
|
||||
|
||||
/**
|
||||
* Means finish of {@link #entry(Context, ResourceWrapper, Object, int, boolean, Object...)}.
|
||||
*
|
||||
* @param context current {@link Context}
|
||||
* @param resourceWrapper current resource
|
||||
* @param obj relevant object (e.g. Node)
|
||||
* @param count tokens needed
|
||||
* @param prioritized whether the entry is prioritized
|
||||
* @param args parameters of the original call
|
||||
* @throws Throwable blocked exception or unexpected error
|
||||
*/
|
||||
void fireEntry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized,
|
||||
Object... args) throws Throwable;
|
||||
|
||||
/**
|
||||
* Exit of this slot.
|
||||
*
|
||||
* @param context current {@link Context}
|
||||
* @param resourceWrapper current resource
|
||||
* @param count tokens needed
|
||||
* @param args parameters of the original call
|
||||
*/
|
||||
void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args);
|
||||
|
||||
/**
|
||||
* Means finish of {@link #exit(Context, ResourceWrapper, int, Object...)}.
|
||||
*
|
||||
* @param context current {@link Context}
|
||||
* @param resourceWrapper current resource
|
||||
* @param count tokens needed
|
||||
* @param args parameters of the original call
|
||||
*/
|
||||
void fireExit(Context context, ResourceWrapper resourceWrapper, int count, Object... args);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.slotchain;
|
||||
|
||||
/**
|
||||
* Link all processor slots as a chain.
|
||||
*
|
||||
* @author qinan.qn
|
||||
*/
|
||||
public abstract class ProcessorSlotChain extends AbstractLinkedProcessorSlot<Object> {
|
||||
|
||||
/**
|
||||
* Add a processor to the head of this slot chain.
|
||||
*
|
||||
* @param protocolProcessor processor to be added.
|
||||
*/
|
||||
public abstract void addFirst(AbstractLinkedProcessorSlot<?> protocolProcessor);
|
||||
|
||||
/**
|
||||
* Add a processor to the tail of this slot chain.
|
||||
*
|
||||
* @param protocolProcessor processor to be added.
|
||||
*/
|
||||
public abstract void addLast(AbstractLinkedProcessorSlot<?> protocolProcessor);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.slotchain;
|
||||
|
||||
import com.alibaba.csp.sentinel.context.Context;
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
|
||||
/**
|
||||
* Callback for entering {@link com.alibaba.csp.sentinel.slots.statistic.StatisticSlot} (passed and blocked).
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public interface ProcessorSlotEntryCallback<T> {
|
||||
|
||||
void onPass(Context context, ResourceWrapper resourceWrapper, T param, int count, Object... args) throws Exception;
|
||||
|
||||
void onBlocked(BlockException ex, Context context, ResourceWrapper resourceWrapper, T param, int count, Object... args);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.slotchain;
|
||||
|
||||
import com.alibaba.csp.sentinel.context.Context;
|
||||
|
||||
/**
|
||||
* Callback for exiting {@link com.alibaba.csp.sentinel.slots.statistic.StatisticSlot} (passed and blocked).
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public interface ProcessorSlotExitCallback {
|
||||
|
||||
void onExit(Context context, ResourceWrapper resourceWrapper, int count, Object... args);
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.slotchain;
|
||||
|
||||
import com.alibaba.csp.sentinel.EntryType;
|
||||
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||
|
||||
/**
|
||||
* A wrapper of resource name and type.
|
||||
*
|
||||
* @author qinan.qn
|
||||
* @author jialiang.linjl
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public abstract class ResourceWrapper {
|
||||
|
||||
protected final String name;
|
||||
|
||||
protected final EntryType entryType;
|
||||
protected final int resourceType;
|
||||
|
||||
public ResourceWrapper(String name, EntryType entryType, int resourceType) {
|
||||
AssertUtil.notEmpty(name, "resource name cannot be empty");
|
||||
AssertUtil.notNull(entryType, "entryType cannot be null");
|
||||
this.name = name;
|
||||
this.entryType = entryType;
|
||||
this.resourceType = resourceType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resource name.
|
||||
*
|
||||
* @return the resource name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get {@link EntryType} of this wrapper.
|
||||
*
|
||||
* @return {@link EntryType} of this wrapper.
|
||||
*/
|
||||
public EntryType getEntryType() {
|
||||
return entryType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the classification of this resource.
|
||||
*
|
||||
* @return the classification of this resource
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public int getResourceType() {
|
||||
return resourceType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the beautified resource name to be showed.
|
||||
*
|
||||
* @return the beautified resource name
|
||||
*/
|
||||
public abstract String getShowName();
|
||||
|
||||
/**
|
||||
* Only {@link #getName()} is considered.
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getName().hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Only {@link #getName()} is considered.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof ResourceWrapper) {
|
||||
ResourceWrapper rw = (ResourceWrapper)obj;
|
||||
return rw.getName().equals(getName());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user