- Add a universal `TokenService` SPI interface for both local flow control and distributed flow control - Add TokenResult entity to represents result of acquiring token - Add `ClusterTokenClient` as the SPI interface for client of Sentinel cluster flow control Signed-off-by: Eric Zhao <sczyh16@gmail.com>master
@@ -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.cluster; | |||||
/** | |||||
* 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 | |||||
*/ | |||||
TokenServerDescriptor currentServer(); | |||||
} |
@@ -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; | |||||
import java.util.ArrayList; | |||||
import java.util.List; | |||||
import java.util.ServiceLoader; | |||||
import com.alibaba.csp.sentinel.log.RecordLog; | |||||
/** | |||||
* Provider for a universal {@link ClusterTokenClient} instance. | |||||
* | |||||
* @author Eric Zhao | |||||
* @since 1.4.0 | |||||
*/ | |||||
public final class TokenClientProvider { | |||||
private static ClusterTokenClient client = null; | |||||
private static final ServiceLoader<ClusterTokenClient> LOADER = ServiceLoader.load(ClusterTokenClient.class); | |||||
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() { | |||||
List<ClusterTokenClient> clients = new ArrayList<ClusterTokenClient>(); | |||||
for (ClusterTokenClient client : LOADER) { | |||||
clients.add(client); | |||||
} | |||||
if (!clients.isEmpty()) { | |||||
// Get first. | |||||
client = clients.get(0); | |||||
RecordLog.info("[TokenClientProvider] Token client resolved: " + client.getClass().getCanonicalName()); | |||||
} else { | |||||
RecordLog.warn("[TokenClientProvider] No existing token client, resolve failed"); | |||||
} | |||||
} | |||||
private TokenClientProvider() {} | |||||
} |
@@ -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.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 Map<String, String> attachments; | |||||
public TokenResult() {} | |||||
public TokenResult(Integer status) { | |||||
this.status = status; | |||||
} | |||||
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 + | |||||
'}'; | |||||
} | |||||
} |
@@ -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.cluster; | |||||
/** | |||||
* @author Eric Zhao | |||||
* @since 1.4.0 | |||||
*/ | |||||
public final class TokenResultStatus { | |||||
/** | |||||
* Bad client request. | |||||
*/ | |||||
public static final int BAD_REQUEST = -4; | |||||
/** | |||||
* 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; | |||||
private TokenResultStatus() {} | |||||
} |
@@ -0,0 +1,72 @@ | |||||
/* | |||||
* 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 String host; | |||||
private int port; | |||||
private String type; | |||||
public TokenServerDescriptor() {} | |||||
public TokenServerDescriptor(String host, int port) { | |||||
this.host = host; | |||||
this.port = port; | |||||
} | |||||
public String getHost() { | |||||
return host; | |||||
} | |||||
public TokenServerDescriptor setHost(String host) { | |||||
this.host = host; | |||||
return this; | |||||
} | |||||
public int getPort() { | |||||
return port; | |||||
} | |||||
public TokenServerDescriptor setPort(int port) { | |||||
this.port = port; | |||||
return this; | |||||
} | |||||
public String getType() { | |||||
return type; | |||||
} | |||||
public TokenServerDescriptor setType(String type) { | |||||
this.type = type; | |||||
return this; | |||||
} | |||||
@Override | |||||
public String toString() { | |||||
return "TokenServerDescriptor{" + | |||||
"host='" + host + '\'' + | |||||
", port=" + port + | |||||
", type='" + type + '\'' + | |||||
'}'; | |||||
} | |||||
} |
@@ -0,0 +1,47 @@ | |||||
/* | |||||
* 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(Integer 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(Integer ruleId, int acquireCount, Collection<Object> params); | |||||
} |
@@ -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() {} | |||||
} |
@@ -54,7 +54,7 @@ public class FlowSlotTest { | |||||
Context context = mock(Context.class); | Context context = mock(Context.class); | ||||
DefaultNode node = mock(DefaultNode.class); | DefaultNode node = mock(DefaultNode.class); | ||||
doCallRealMethod().when(flowSlot).checkFlow(any(ResourceWrapper.class), any(Context.class), | doCallRealMethod().when(flowSlot).checkFlow(any(ResourceWrapper.class), any(Context.class), | ||||
any(DefaultNode.class), anyInt()); | |||||
any(DefaultNode.class), anyInt(), anyBoolean()); | |||||
String resA = "resAK"; | String resA = "resAK"; | ||||
String resB = "resBK"; | String resB = "resBK"; | ||||
@@ -63,13 +63,13 @@ public class FlowSlotTest { | |||||
// Here we only load rules for resA. | // Here we only load rules for resA. | ||||
FlowRuleManager.loadRules(Collections.singletonList(rule1)); | FlowRuleManager.loadRules(Collections.singletonList(rule1)); | ||||
when(flowSlot.canPassCheck(eq(rule1), any(Context.class), any(DefaultNode.class), anyInt())) | |||||
when(flowSlot.canPassCheck(eq(rule1), any(Context.class), any(DefaultNode.class), anyInt(), anyBoolean())) | |||||
.thenReturn(true); | .thenReturn(true); | ||||
when(flowSlot.canPassCheck(eq(rule2), any(Context.class), any(DefaultNode.class), anyInt())) | |||||
when(flowSlot.canPassCheck(eq(rule2), any(Context.class), any(DefaultNode.class), anyInt(), anyBoolean())) | |||||
.thenReturn(false); | .thenReturn(false); | ||||
flowSlot.checkFlow(new StringResourceWrapper(resA, EntryType.IN), context, node, 1); | |||||
flowSlot.checkFlow(new StringResourceWrapper(resB, EntryType.IN), context, node, 1); | |||||
flowSlot.checkFlow(new StringResourceWrapper(resA, EntryType.IN), context, node, 1, false); | |||||
flowSlot.checkFlow(new StringResourceWrapper(resB, EntryType.IN), context, node, 1, false); | |||||
} | } | ||||
@Test(expected = FlowException.class) | @Test(expected = FlowException.class) | ||||
@@ -78,15 +78,15 @@ public class FlowSlotTest { | |||||
Context context = mock(Context.class); | Context context = mock(Context.class); | ||||
DefaultNode node = mock(DefaultNode.class); | DefaultNode node = mock(DefaultNode.class); | ||||
doCallRealMethod().when(flowSlot).checkFlow(any(ResourceWrapper.class), any(Context.class), | doCallRealMethod().when(flowSlot).checkFlow(any(ResourceWrapper.class), any(Context.class), | ||||
any(DefaultNode.class), anyInt()); | |||||
any(DefaultNode.class), anyInt(), anyBoolean()); | |||||
String resA = "resAK"; | String resA = "resAK"; | ||||
FlowRule rule = new FlowRule(resA).setCount(10); | FlowRule rule = new FlowRule(resA).setCount(10); | ||||
FlowRuleManager.loadRules(Collections.singletonList(rule)); | FlowRuleManager.loadRules(Collections.singletonList(rule)); | ||||
when(flowSlot.canPassCheck(any(FlowRule.class), any(Context.class), any(DefaultNode.class), anyInt())) | |||||
when(flowSlot.canPassCheck(any(FlowRule.class), any(Context.class), any(DefaultNode.class), anyInt(), anyBoolean())) | |||||
.thenReturn(false); | .thenReturn(false); | ||||
flowSlot.checkFlow(new StringResourceWrapper(resA, EntryType.IN), context, node, 1); | |||||
flowSlot.checkFlow(new StringResourceWrapper(resA, EntryType.IN), context, node, 1, false); | |||||
} | } | ||||
} | } |