This commit is contained in:
shuhongfan
2023-09-04 16:40:17 +08:00
commit cf5ac25c14
8267 changed files with 1305066 additions and 0 deletions

View File

@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-parent</artifactId>
<version>1.8.3</version>
</parent>
<artifactId>sentinel-core</artifactId>
<packaging>jar</packaging>
<description>The core of Sentinel</description>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-libray</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>java-hamcrest</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestEntries>
<Implementation-Version>${project.version}</Implementation-Version>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -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;
}
}

View File

@@ -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() {}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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() {}
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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 {};
}

View File

@@ -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;
}

View File

@@ -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 +
'}';
}
}

View File

@@ -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() {
}
}

View File

@@ -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 + '\'' +
'}';
}
}

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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() {}
}

View File

@@ -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() {}
}

View File

@@ -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() {}
}

View File

@@ -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;
}

View File

@@ -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 {
}

View File

@@ -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() {}
}

View File

@@ -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;
}
}

View File

@@ -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() {}
}

View File

@@ -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;
}
}

View File

@@ -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 +
'}';
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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");
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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
}
}
}

View File

@@ -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() {}
}

View File

@@ -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 + "]";
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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() {
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}

View File

@@ -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 + "]";
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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() {}
}

View File

@@ -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() {}
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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(&quot;Hi {}.&quot;, &quot;there&quot;)
* </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(&quot;Set {1,2,3} is not equal to {}.&quot;, &quot;1,2&quot;);
* </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(&quot;Set \\{} is not equal to {}.&quot;, &quot;1,2&quot;);
* </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(&quot;File name is C:\\\\{}.&quot;, &quot;file.zip&quot;);
* </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&uuml;lc&uuml;
* @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(&quot;Hi {}.&quot;, &quot;there&quot;);
* </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(&quot;Hi {}. My name is {}.&quot;, &quot;Alice&quot;, &quot;Bob&quot;);
* </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(']');
}
}

View File

@@ -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);
}

View File

@@ -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());
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}
}

View File

@@ -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());
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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 +
'}';
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

Some files were not shown because too many files have changed in this diff Show More