- 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); | |||
DefaultNode node = mock(DefaultNode.class); | |||
doCallRealMethod().when(flowSlot).checkFlow(any(ResourceWrapper.class), any(Context.class), | |||
any(DefaultNode.class), anyInt()); | |||
any(DefaultNode.class), anyInt(), anyBoolean()); | |||
String resA = "resAK"; | |||
String resB = "resBK"; | |||
@@ -63,13 +63,13 @@ public class FlowSlotTest { | |||
// Here we only load rules for resA. | |||
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); | |||
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); | |||
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) | |||
@@ -78,15 +78,15 @@ public class FlowSlotTest { | |||
Context context = mock(Context.class); | |||
DefaultNode node = mock(DefaultNode.class); | |||
doCallRealMethod().when(flowSlot).checkFlow(any(ResourceWrapper.class), any(Context.class), | |||
any(DefaultNode.class), anyInt()); | |||
any(DefaultNode.class), anyInt(), anyBoolean()); | |||
String resA = "resAK"; | |||
FlowRule rule = new FlowRule(resA).setCount(10); | |||
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); | |||
flowSlot.checkFlow(new StringResourceWrapper(resA, EntryType.IN), context, node, 1); | |||
flowSlot.checkFlow(new StringResourceWrapper(resA, EntryType.IN), context, node, 1, false); | |||
} | |||
} |