/* * 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.slots.clusterbuilder; import java.util.HashMap; import java.util.Map; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.node.IntervalProperty; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.node.SampleCountProperty; import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; /** * <p> * This slot maintains resource running statistics (response time, qps, thread * count, exception), and a list of callers as well which is marked by * {@link ContextUtil#enter(String origin)} * </p> * <p> * One resource has only one cluster node, while one resource can have multiple * default nodes. * </p> * * @author jialiang.linjl */ public class ClusterBuilderSlot extends AbstractLinkedProcessorSlot<DefaultNode> { /** * <p> * Remember that same resource({@link ResourceWrapper#equals(Object)}) will share * the same {@link ProcessorSlotChain} globally, no matter in witch context. So if * code goes into {@link #entry(Context, ResourceWrapper, DefaultNode, int, boolean, Object...)}, * the resource name must be same but context name may not. * </p> * <p> * To get total statistics of the same resource in different context, same resource * shares the same {@link ClusterNode} globally. All {@link ClusterNode}s are cached * in this map. * </p> * <p> * The longer the application runs, the more stable this mapping will * become. so we don't concurrent map but a lock. as this lock only happens * at the very beginning while concurrent map will hold the lock all the time. * </p> */ private static volatile Map<ResourceWrapper, ClusterNode> clusterNodeMap = new HashMap<>(); private static final Object lock = new Object(); private volatile ClusterNode clusterNode = null; @Override public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) throws Throwable { if (clusterNode == null) { synchronized (lock) { if (clusterNode == null) { // Create the cluster node. clusterNode = new ClusterNode(); HashMap<ResourceWrapper, ClusterNode> newMap = new HashMap<>(Math.max(clusterNodeMap.size(), 16)); newMap.putAll(clusterNodeMap); newMap.put(node.getId(), clusterNode); clusterNodeMap = newMap; } } } node.setClusterNode(clusterNode); /* * if context origin is set, we should get or create a new {@link Node} of * the specific origin. */ if (!"".equals(context.getOrigin())) { Node originNode = node.getClusterNode().getOrCreateOriginNode(context.getOrigin()); context.getCurEntry().setOriginNode(originNode); } fireEntry(context, resourceWrapper, node, count, prioritized, args); } @Override public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { fireExit(context, resourceWrapper, count, args); } /** * Get {@link ClusterNode} of the resource of the specific type. * * @param id resource name. * @param type invoke type. * @return the {@link ClusterNode} */ public static ClusterNode getClusterNode(String id, EntryType type) { return clusterNodeMap.get(new StringResourceWrapper(id, type)); } /** * Get {@link ClusterNode} of the resource name. * * @param id resource name. * @return the {@link ClusterNode}. */ public static ClusterNode getClusterNode(String id) { if (id == null) { return null; } ClusterNode clusterNode = null; for (EntryType nodeType : EntryType.values()) { clusterNode = clusterNodeMap.get(new StringResourceWrapper(id, nodeType)); if (clusterNode != null) { break; } } return clusterNode; } /** * Get {@link ClusterNode}s map, this map holds all {@link ClusterNode}s, it's key is resource name, * value is the related {@link ClusterNode}. <br/> * DO NOT MODIFY the map returned. * * @return all {@link ClusterNode}s */ public static Map<ResourceWrapper, ClusterNode> getClusterNodeMap() { return clusterNodeMap; } /** * Reset all {@link ClusterNode}s. Reset is needed when {@link IntervalProperty#INTERVAL} or * {@link SampleCountProperty#SAMPLE_COUNT} is changed. */ public static void resetClusterNodes() { for (ClusterNode node : clusterNodeMap.values()) { node.reset(); } } }