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,7 @@
# Sentinel Cluster Flow Control
This is the default implementation of Sentinel cluster flow control.
- `sentinel-cluster-common-default`: common module for cluster transport and functions
- `sentinel-cluster-client-default`: default cluster client module using Netty as underlying transport library
- `sentinel-cluster-server-default`: default cluster server module

View File

@@ -0,0 +1,40 @@
<?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">
<parent>
<artifactId>sentinel-parent</artifactId>
<groupId>com.alibaba.csp</groupId>
<version>1.8.3</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging>
<artifactId>sentinel-cluster</artifactId>
<description>The parent module of Sentinel cluster server</description>
<properties>
<netty.version>4.1.48.Final</netty.version>
</properties>
<modules>
<module>sentinel-cluster-client-default</module>
<module>sentinel-cluster-server-default</module>
<module>sentinel-cluster-common-default</module>
<module>sentinel-cluster-server-envoy-rls</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-handler</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>${netty.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>

View File

@@ -0,0 +1 @@
# Sentinel Cluster Client (Default)

View File

@@ -0,0 +1,62 @@
<?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">
<parent>
<artifactId>sentinel-cluster</artifactId>
<groupId>com.alibaba.csp</groupId>
<version>1.8.3</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sentinel-cluster-client-default</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-common</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-cluster-common-default</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-handler</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-parameter-flow-control</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,33 @@
/*
* 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;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public final class ClientConstants {
public static final int TYPE_PING = 0;
public static final int TYPE_FLOW = 1;
public static final int TYPE_PARAM_FLOW = 2;
public static final int CLIENT_STATUS_OFF = 0;
public static final int CLIENT_STATUS_PENDING = 1;
public static final int CLIENT_STATUS_STARTED = 2;
private ClientConstants() {}
}

View File

@@ -0,0 +1,233 @@
/*
* 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 java.util.Collection;
import java.util.concurrent.atomic.AtomicBoolean;
import com.alibaba.csp.sentinel.cluster.ClusterConstants;
import com.alibaba.csp.sentinel.cluster.ClusterErrorMessages;
import com.alibaba.csp.sentinel.cluster.ClusterTransportClient;
import com.alibaba.csp.sentinel.cluster.TokenResult;
import com.alibaba.csp.sentinel.cluster.TokenResultStatus;
import com.alibaba.csp.sentinel.cluster.TokenServerDescriptor;
import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientAssignConfig;
import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfigManager;
import com.alibaba.csp.sentinel.cluster.client.config.ServerChangeObserver;
import com.alibaba.csp.sentinel.cluster.log.ClusterClientStatLogUtil;
import com.alibaba.csp.sentinel.cluster.request.ClusterRequest;
import com.alibaba.csp.sentinel.cluster.request.data.FlowRequestData;
import com.alibaba.csp.sentinel.cluster.request.data.ParamFlowRequestData;
import com.alibaba.csp.sentinel.cluster.response.ClusterResponse;
import com.alibaba.csp.sentinel.cluster.response.data.FlowTokenResponseData;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.util.StringUtil;
/**
* Default implementation of {@link ClusterTokenClient}.
*
* @author Eric Zhao
* @since 1.4.0
*/
public class DefaultClusterTokenClient implements ClusterTokenClient {
private ClusterTransportClient transportClient;
private TokenServerDescriptor serverDescriptor;
private final AtomicBoolean shouldStart = new AtomicBoolean(false);
public DefaultClusterTokenClient() {
ClusterClientConfigManager.addServerChangeObserver(new ServerChangeObserver() {
@Override
public void onRemoteServerChange(ClusterClientAssignConfig assignConfig) {
changeServer(assignConfig);
}
});
initNewConnection();
}
private boolean serverEqual(TokenServerDescriptor descriptor, ClusterClientAssignConfig config) {
if (descriptor == null || config == null) {
return false;
}
return descriptor.getHost().equals(config.getServerHost()) && descriptor.getPort() == config.getServerPort();
}
private void initNewConnection() {
if (transportClient != null) {
return;
}
String host = ClusterClientConfigManager.getServerHost();
int port = ClusterClientConfigManager.getServerPort();
if (StringUtil.isBlank(host) || port <= 0) {
return;
}
try {
this.transportClient = new NettyTransportClient(host, port);
this.serverDescriptor = new TokenServerDescriptor(host, port);
RecordLog.info("[DefaultClusterTokenClient] New client created: {}", serverDescriptor);
} catch (Exception ex) {
RecordLog.warn("[DefaultClusterTokenClient] Failed to initialize new token client", ex);
}
}
private void changeServer(/*@Valid*/ ClusterClientAssignConfig config) {
if (serverEqual(serverDescriptor, config)) {
return;
}
try {
if (transportClient != null) {
transportClient.stop();
}
// Replace with new, even if the new client is not ready.
this.transportClient = new NettyTransportClient(config.getServerHost(), config.getServerPort());
this.serverDescriptor = new TokenServerDescriptor(config.getServerHost(), config.getServerPort());
startClientIfScheduled();
RecordLog.info("[DefaultClusterTokenClient] New client created: {}", serverDescriptor);
} catch (Exception ex) {
RecordLog.warn("[DefaultClusterTokenClient] Failed to change remote token server", ex);
}
}
private void startClientIfScheduled() throws Exception {
if (shouldStart.get()) {
if (transportClient != null) {
transportClient.start();
} else {
RecordLog.warn("[DefaultClusterTokenClient] Cannot start transport client: client not created");
}
}
}
private void stopClientIfStarted() throws Exception {
if (shouldStart.compareAndSet(true, false)) {
if (transportClient != null) {
transportClient.stop();
}
}
}
@Override
public void start() throws Exception {
if (shouldStart.compareAndSet(false, true)) {
startClientIfScheduled();
}
}
@Override
public void stop() throws Exception {
stopClientIfStarted();
}
@Override
public int getState() {
if (transportClient == null) {
return ClientConstants.CLIENT_STATUS_OFF;
}
return transportClient.isReady() ? ClientConstants.CLIENT_STATUS_STARTED : ClientConstants.CLIENT_STATUS_OFF;
}
@Override
public TokenServerDescriptor currentServer() {
return serverDescriptor;
}
@Override
public TokenResult requestToken(Long flowId, int acquireCount, boolean prioritized) {
if (notValidRequest(flowId, acquireCount)) {
return badRequest();
}
FlowRequestData data = new FlowRequestData().setCount(acquireCount)
.setFlowId(flowId).setPriority(prioritized);
ClusterRequest<FlowRequestData> request = new ClusterRequest<>(ClusterConstants.MSG_TYPE_FLOW, data);
try {
TokenResult result = sendTokenRequest(request);
logForResult(result);
return result;
} catch (Exception ex) {
ClusterClientStatLogUtil.log(ex.getMessage());
return new TokenResult(TokenResultStatus.FAIL);
}
}
@Override
public TokenResult requestParamToken(Long flowId, int acquireCount, Collection<Object> params) {
if (notValidRequest(flowId, acquireCount) || params == null || params.isEmpty()) {
return badRequest();
}
ParamFlowRequestData data = new ParamFlowRequestData().setCount(acquireCount)
.setFlowId(flowId).setParams(params);
ClusterRequest<ParamFlowRequestData> request = new ClusterRequest<>(ClusterConstants.MSG_TYPE_PARAM_FLOW, data);
try {
TokenResult result = sendTokenRequest(request);
logForResult(result);
return result;
} catch (Exception ex) {
ClusterClientStatLogUtil.log(ex.getMessage());
return new TokenResult(TokenResultStatus.FAIL);
}
}
@Override
public TokenResult requestConcurrentToken(String clientAddress, Long ruleId, int acquireCount) {
return null;
}
@Override
public void releaseConcurrentToken(Long tokenId) {
}
private void logForResult(TokenResult result) {
switch (result.getStatus()) {
case TokenResultStatus.NO_RULE_EXISTS:
ClusterClientStatLogUtil.log(ClusterErrorMessages.NO_RULES_IN_SERVER);
break;
case TokenResultStatus.TOO_MANY_REQUEST:
ClusterClientStatLogUtil.log(ClusterErrorMessages.TOO_MANY_REQUESTS);
break;
default:
}
}
private TokenResult sendTokenRequest(ClusterRequest request) throws Exception {
if (transportClient == null) {
RecordLog.warn(
"[DefaultClusterTokenClient] Client not created, please check your config for cluster client");
return clientFail();
}
ClusterResponse response = transportClient.sendRequest(request);
TokenResult result = new TokenResult(response.getStatus());
if (response.getData() != null) {
FlowTokenResponseData responseData = (FlowTokenResponseData)response.getData();
result.setRemaining(responseData.getRemainingCount())
.setWaitInMs(responseData.getWaitInMs());
}
return result;
}
private boolean notValidRequest(Long id, int count) {
return id == null || id <= 0 || count <= 0;
}
private TokenResult badRequest() {
return new TokenResult(TokenResultStatus.BAD_REQUEST);
}
private TokenResult clientFail() {
return new TokenResult(TokenResultStatus.FAIL);
}
}

View File

@@ -0,0 +1,272 @@
/*
* 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 java.util.AbstractMap.SimpleEntry;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import com.alibaba.csp.sentinel.cluster.ClusterErrorMessages;
import com.alibaba.csp.sentinel.cluster.ClusterTransportClient;
import com.alibaba.csp.sentinel.cluster.client.codec.netty.NettyRequestEncoder;
import com.alibaba.csp.sentinel.cluster.client.codec.netty.NettyResponseDecoder;
import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfigManager;
import com.alibaba.csp.sentinel.cluster.client.handler.TokenClientHandler;
import com.alibaba.csp.sentinel.cluster.client.handler.TokenClientPromiseHolder;
import com.alibaba.csp.sentinel.cluster.exception.SentinelClusterException;
import com.alibaba.csp.sentinel.cluster.request.ClusterRequest;
import com.alibaba.csp.sentinel.cluster.request.Request;
import com.alibaba.csp.sentinel.cluster.response.ClusterResponse;
import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.util.AssertUtil;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.util.concurrent.GenericFutureListener;
/**
* Netty transport client implementation for Sentinel cluster transport.
*
* @author Eric Zhao
* @since 1.4.0
*/
public class NettyTransportClient implements ClusterTransportClient {
@SuppressWarnings("PMD.ThreadPoolCreationRule")
private static final ScheduledExecutorService SCHEDULER = Executors.newScheduledThreadPool(1,
new NamedThreadFactory("sentinel-cluster-transport-client-scheduler", true));
public static final int RECONNECT_DELAY_MS = 2000;
private final String host;
private final int port;
private Channel channel;
private NioEventLoopGroup eventLoopGroup;
private TokenClientHandler clientHandler;
private final AtomicInteger idGenerator = new AtomicInteger(0);
private final AtomicInteger currentState = new AtomicInteger(ClientConstants.CLIENT_STATUS_OFF);
private final AtomicInteger failConnectedTime = new AtomicInteger(0);
private final AtomicBoolean shouldRetry = new AtomicBoolean(true);
public NettyTransportClient(String host, int port) {
AssertUtil.assertNotBlank(host, "remote host cannot be blank");
AssertUtil.isTrue(port > 0, "port should be positive");
this.host = host;
this.port = port;
}
private Bootstrap initClientBootstrap() {
Bootstrap b = new Bootstrap();
eventLoopGroup = new NioEventLoopGroup();
b.group(eventLoopGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, ClusterClientConfigManager.getConnectTimeout())
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
clientHandler = new TokenClientHandler(currentState, disconnectCallback);
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 0, 2, 0, 2));
pipeline.addLast(new NettyResponseDecoder());
pipeline.addLast(new LengthFieldPrepender(2));
pipeline.addLast(new NettyRequestEncoder());
pipeline.addLast(clientHandler);
}
});
return b;
}
private void connect(Bootstrap b) {
if (currentState.compareAndSet(ClientConstants.CLIENT_STATUS_OFF, ClientConstants.CLIENT_STATUS_PENDING)) {
b.connect(host, port)
.addListener(new GenericFutureListener<ChannelFuture>() {
@Override
public void operationComplete(ChannelFuture future) {
if (future.cause() != null) {
RecordLog.warn(
String.format("[NettyTransportClient] Could not connect to <%s:%d> after %d times",
host, port, failConnectedTime.get()), future.cause());
failConnectedTime.incrementAndGet();
channel = null;
} else {
failConnectedTime.set(0);
channel = future.channel();
RecordLog.info("[NettyTransportClient] Successfully connect to server <{}:{}>", host, port);
}
}
});
}
}
private Runnable disconnectCallback = new Runnable() {
@Override
public void run() {
if (!shouldRetry.get()) {
return;
}
SCHEDULER.schedule(new Runnable() {
@Override
public void run() {
if (shouldRetry.get()) {
RecordLog.info("[NettyTransportClient] Reconnecting to server <{}:{}>", host, port);
try {
startInternal();
} catch (Exception e) {
RecordLog.warn("[NettyTransportClient] Failed to reconnect to server", e);
}
}
}
}, RECONNECT_DELAY_MS * (failConnectedTime.get() + 1), TimeUnit.MILLISECONDS);
cleanUp();
}
};
@Override
public void start() throws Exception {
shouldRetry.set(true);
startInternal();
}
private void startInternal() {
connect(initClientBootstrap());
}
private void cleanUp() {
if (channel != null) {
channel.close();
channel = null;
}
if (eventLoopGroup != null) {
eventLoopGroup.shutdownGracefully();
}
}
@Override
public void stop() throws Exception {
// Stop retrying for connection.
shouldRetry.set(false);
while (currentState.get() == ClientConstants.CLIENT_STATUS_PENDING) {
try {
Thread.sleep(200);
} catch (Exception ex) {
// Ignore.
}
}
cleanUp();
failConnectedTime.set(0);
RecordLog.info("[NettyTransportClient] Cluster transport client stopped");
}
private boolean validRequest(Request request) {
return request != null && request.getType() >= 0;
}
@Override
public boolean isReady() {
return channel != null && clientHandler != null && clientHandler.hasStarted();
}
@Override
public ClusterResponse sendRequest(ClusterRequest request) throws Exception {
if (!isReady()) {
throw new SentinelClusterException(ClusterErrorMessages.CLIENT_NOT_READY);
}
if (!validRequest(request)) {
throw new SentinelClusterException(ClusterErrorMessages.BAD_REQUEST);
}
int xid = getCurrentId();
try {
request.setId(xid);
channel.writeAndFlush(request);
ChannelPromise promise = channel.newPromise();
TokenClientPromiseHolder.putPromise(xid, promise);
if (!promise.await(ClusterClientConfigManager.getRequestTimeout())) {
throw new SentinelClusterException(ClusterErrorMessages.REQUEST_TIME_OUT);
}
SimpleEntry<ChannelPromise, ClusterResponse> entry = TokenClientPromiseHolder.getEntry(xid);
if (entry == null || entry.getValue() == null) {
// Should not go through here.
throw new SentinelClusterException(ClusterErrorMessages.UNEXPECTED_STATUS);
}
return entry.getValue();
} finally {
TokenClientPromiseHolder.remove(xid);
}
}
private int getCurrentId() {
int pre, next;
do {
pre = idGenerator.get();
next = pre >= MAX_ID ? MIN_ID : pre + 1;
} while (!idGenerator.compareAndSet(pre, next));
return next;
}
/*public CompletableFuture<ClusterResponse> sendRequestAsync(ClusterRequest request) {
// Uncomment this when min target JDK is 1.8.
if (!validRequest(request)) {
return CompletableFuture.failedFuture(new IllegalArgumentException("Bad request"));
}
int xid = getCurrentId();
request.setId(xid);
CompletableFuture<ClusterResponse> future = new CompletableFuture<>();
channel.writeAndFlush(request)
.addListener(f -> {
if (f.isSuccess()) {
future.complete(someResult);
} else if (f.cause() != null) {
future.completeExceptionally(f.cause());
} else {
future.cancel(false);
}
});
return future;
}*/
private static final int MIN_ID = 1;
private static final int MAX_ID = 999_999_999;
}

View File

@@ -0,0 +1,64 @@
/*
* 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.codec;
import com.alibaba.csp.sentinel.spi.SpiLoader;
import com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityWriter;
import com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityDecoder;
import com.alibaba.csp.sentinel.log.RecordLog;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public final class ClientEntityCodecProvider {
private static RequestEntityWriter requestEntityWriter = null;
private static ResponseEntityDecoder responseEntityDecoder = null;
static {
resolveInstance();
}
private static void resolveInstance() {
RequestEntityWriter writer = SpiLoader.of(RequestEntityWriter.class).loadFirstInstance();
if (writer == null) {
RecordLog.warn("[ClientEntityCodecProvider] No existing request entity writer, resolve failed");
} else {
requestEntityWriter = writer;
RecordLog.info("[ClientEntityCodecProvider] Request entity writer resolved: {}",
requestEntityWriter.getClass().getCanonicalName());
}
ResponseEntityDecoder decoder = SpiLoader.of(ResponseEntityDecoder.class).loadFirstInstance();
if (decoder == null) {
RecordLog.warn("[ClientEntityCodecProvider] No existing response entity decoder, resolve failed");
} else {
responseEntityDecoder = decoder;
RecordLog.info("[ClientEntityCodecProvider] Response entity decoder resolved: {}",
responseEntityDecoder.getClass().getCanonicalName());
}
}
public static RequestEntityWriter getRequestEntityWriter() {
return requestEntityWriter;
}
public static ResponseEntityDecoder getResponseEntityDecoder() {
return responseEntityDecoder;
}
private ClientEntityCodecProvider() {}
}

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.client.codec;
import com.alibaba.csp.sentinel.cluster.client.codec.registry.RequestDataWriterRegistry;
import com.alibaba.csp.sentinel.cluster.codec.EntityWriter;
import com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityWriter;
import com.alibaba.csp.sentinel.cluster.request.ClusterRequest;
import com.alibaba.csp.sentinel.cluster.request.Request;
import com.alibaba.csp.sentinel.log.RecordLog;
import io.netty.buffer.ByteBuf;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public class DefaultRequestEntityWriter implements RequestEntityWriter<ClusterRequest, ByteBuf> {
@Override
public void writeTo(ClusterRequest request, ByteBuf target) {
int type = request.getType();
EntityWriter<Object, ByteBuf> requestDataWriter = RequestDataWriterRegistry.getWriter(type);
if (requestDataWriter == null) {
RecordLog.warn("[DefaultRequestEntityWriter] Cannot find matching request writer for type <{}>,"
+ " dropping the request", type);
return;
}
// Write head part of request.
writeHead(request, target);
// Write data part.
requestDataWriter.writeTo(request.getData(), target);
}
private void writeHead(Request request, ByteBuf out) {
out.writeInt(request.getId());
out.writeByte(request.getType());
}
}

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.cluster.client.codec;
import com.alibaba.csp.sentinel.cluster.client.codec.registry.ResponseDataDecodeRegistry;
import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder;
import com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityDecoder;
import com.alibaba.csp.sentinel.cluster.response.ClusterResponse;
import com.alibaba.csp.sentinel.log.RecordLog;
import io.netty.buffer.ByteBuf;
/**
* <p>Default entity decoder for any {@link ClusterResponse} entity.</p>
*
* <p>Decode format:</p>
* <pre>
* +--------+---------+-----------+---------+
* | xid(4) | type(1) | status(1) | data... |
* +--------+---------+-----------+---------+
* </pre>
*
* @author Eric Zhao
* @since 1.4.0
*/
public class DefaultResponseEntityDecoder implements ResponseEntityDecoder<ByteBuf, ClusterResponse> {
@Override
public ClusterResponse decode(ByteBuf source) {
if (source.readableBytes() >= 6) {
int xid = source.readInt();
int type = source.readByte();
int status = source.readByte();
EntityDecoder<ByteBuf, ?> decoder = ResponseDataDecodeRegistry.getDecoder(type);
if (decoder == null) {
RecordLog.warn("Unknown type of response data decoder: {}", type);
return null;
}
Object data;
if (source.readableBytes() == 0) {
data = null;
} else {
data = decoder.decode(source);
}
return new ClusterResponse<>(xid, type, status, data);
}
return null;
}
}

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.client.codec.data;
import com.alibaba.csp.sentinel.cluster.codec.EntityWriter;
import com.alibaba.csp.sentinel.cluster.request.data.FlowRequestData;
import io.netty.buffer.ByteBuf;
/**
* +-------------------+--------------+----------------+---------------+------------------+
* | RequestID(8 byte) | Type(1 byte) | FlowID(8 byte) | Count(4 byte) | PriorityFlag (1) |
* +-------------------+--------------+----------------+---------------+------------------+
*
* @author Eric Zhao
* @since 1.4.0
*/
public class FlowRequestDataWriter implements EntityWriter<FlowRequestData, ByteBuf> {
@Override
public void writeTo(FlowRequestData entity, ByteBuf target) {
target.writeLong(entity.getFlowId());
target.writeInt(entity.getCount());
target.writeBoolean(entity.isPriority());
}
}

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.client.codec.data;
import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder;
import com.alibaba.csp.sentinel.cluster.response.data.FlowTokenResponseData;
import io.netty.buffer.ByteBuf;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public class FlowResponseDataDecoder implements EntityDecoder<ByteBuf, FlowTokenResponseData> {
@Override
public FlowTokenResponseData decode(ByteBuf source) {
FlowTokenResponseData data = new FlowTokenResponseData();
if (source.readableBytes() == 8) {
data.setRemainingCount(source.readInt());
data.setWaitInMs(source.readInt());
}
return data;
}
}

View File

@@ -0,0 +1,160 @@
/*
* 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.codec.data;
import com.alibaba.csp.sentinel.cluster.ClusterConstants;
import com.alibaba.csp.sentinel.cluster.codec.EntityWriter;
import com.alibaba.csp.sentinel.cluster.request.data.ParamFlowRequestData;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.util.AssertUtil;
import io.netty.buffer.ByteBuf;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* @author jialiang.linjl
* @author Eric Zhao
* @since 1.4.0
*/
public class ParamFlowRequestDataWriter implements EntityWriter<ParamFlowRequestData, ByteBuf> {
private final int maxParamByteSize;
public ParamFlowRequestDataWriter() {
this(DEFAULT_PARAM_MAX_SIZE);
}
public ParamFlowRequestDataWriter(int maxParamByteSize) {
AssertUtil.isTrue(maxParamByteSize > 0, "maxParamByteSize should be positive");
this.maxParamByteSize = maxParamByteSize;
}
@Override
public void writeTo(ParamFlowRequestData entity, ByteBuf target) {
target.writeLong(entity.getFlowId());
target.writeInt(entity.getCount());
Collection<Object> params = entity.getParams();
params = resolveValidParams(params);
target.writeInt(params.size());
// Serialize parameters with type flag.
for (Object param : params) {
encodeValue(param, target);
}
}
/**
* Get valid parameters in provided parameter list
*
* @param params
* @return
*/
public List<Object> resolveValidParams(Collection<Object> params) {
List<Object> validParams = new ArrayList<>();
int size = 0;
for (Object param : params) {
int s = calculateParamTransportSize(param);
if (s <= 0) {
RecordLog.warn("[ParamFlowRequestDataWriter] WARN: Non-primitive type detected in params of "
+ "cluster parameter flow control, which is not supported: " + param);
continue;
}
if (size + s > maxParamByteSize) {
RecordLog.warn("[ParamFlowRequestDataWriter] WARN: params size is too big." +
" the configure value is : " + maxParamByteSize + ", the params size is: " + params.size());
break;
}
size += s;
validParams.add(param);
}
return validParams;
}
private void encodeValue(Object param, ByteBuf target) {
// Handle primitive type.
if (param instanceof Integer || int.class.isInstance(param)) {
target.writeByte(ClusterConstants.PARAM_TYPE_INTEGER);
target.writeInt((Integer) param);
} else if (param instanceof String) {
encodeString((String) param, target);
} else if (boolean.class.isInstance(param) || param instanceof Boolean) {
target.writeByte(ClusterConstants.PARAM_TYPE_BOOLEAN);
target.writeBoolean((Boolean) param);
} else if (long.class.isInstance(param) || param instanceof Long) {
target.writeByte(ClusterConstants.PARAM_TYPE_LONG);
target.writeLong((Long) param);
} else if (double.class.isInstance(param) || param instanceof Double) {
target.writeByte(ClusterConstants.PARAM_TYPE_DOUBLE);
target.writeDouble((Double) param);
} else if (float.class.isInstance(param) || param instanceof Float) {
target.writeByte(ClusterConstants.PARAM_TYPE_FLOAT);
target.writeFloat((Float) param);
} else if (byte.class.isInstance(param) || param instanceof Byte) {
target.writeByte(ClusterConstants.PARAM_TYPE_BYTE);
target.writeByte((Byte) param);
} else if (short.class.isInstance(param) || param instanceof Short) {
target.writeByte(ClusterConstants.PARAM_TYPE_SHORT);
target.writeShort((Short) param);
} else {
// Unexpected type, drop.
}
}
private void encodeString(String param, ByteBuf target) {
target.writeByte(ClusterConstants.PARAM_TYPE_STRING);
byte[] tmpChars = param.getBytes();
target.writeInt(tmpChars.length);
target.writeBytes(tmpChars);
}
int calculateParamTransportSize(Object value) {
if (value == null) {
return 0;
}
// Layout for primitives: |type flag(1)|value|
// size = original size + type flag (1)
if (value instanceof Integer || int.class.isInstance(value)) {
return 5;
} else if (value instanceof String) {
// Layout for string: |type flag(1)|length(4)|string content|
String tmpValue = (String) value;
byte[] tmpChars = tmpValue.getBytes();
return 1 + 4 + tmpChars.length;
} else if (boolean.class.isInstance(value) || value instanceof Boolean) {
return 2;
} else if (long.class.isInstance(value) || value instanceof Long) {
return 9;
} else if (double.class.isInstance(value) || value instanceof Double) {
return 9;
} else if (float.class.isInstance(value) || value instanceof Float) {
return 5;
} else if (byte.class.isInstance(value) || value instanceof Byte) {
return 2;
} else if (short.class.isInstance(value) || value instanceof Short) {
return 3;
} else {
// Ignore unexpected type.
return 0;
}
}
private static final int DEFAULT_PARAM_MAX_SIZE = 1024;
}

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.cluster.client.codec.data;
import com.alibaba.csp.sentinel.cluster.codec.EntityWriter;
import com.alibaba.csp.sentinel.util.StringUtil;
import io.netty.buffer.ByteBuf;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public class PingRequestDataWriter implements EntityWriter<String, ByteBuf> {
@Override
public void writeTo(String entity, ByteBuf target) {
if (StringUtil.isBlank(entity) || target == null) {
return;
}
byte[] bytes = entity.getBytes();
target.writeInt(bytes.length);
target.writeBytes(bytes);
}
}

View File

@@ -0,0 +1,40 @@
/*
* 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.codec.data;
import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder;
import io.netty.buffer.ByteBuf;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public class PingResponseDataDecoder implements EntityDecoder<ByteBuf, Integer> {
@Override
public Integer decode(ByteBuf source) {
int size = source.readableBytes();
if (size == 1) {
// Compatible with old version (< 1.7.0).
return (int) source.readByte();
}
if (size >= 4) {
return source.readInt();
}
return -1;
}
}

View File

@@ -0,0 +1,44 @@
/*
* 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.codec.netty;
import com.alibaba.csp.sentinel.cluster.client.codec.ClientEntityCodecProvider;
import com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityWriter;
import com.alibaba.csp.sentinel.cluster.request.ClusterRequest;
import com.alibaba.csp.sentinel.cluster.request.Request;
import com.alibaba.csp.sentinel.log.RecordLog;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public class NettyRequestEncoder extends MessageToByteEncoder<ClusterRequest> {
@Override
protected void encode(ChannelHandlerContext ctx, ClusterRequest request, ByteBuf out) throws Exception {
RequestEntityWriter<Request, ByteBuf> requestEntityWriter = ClientEntityCodecProvider.getRequestEntityWriter();
if (requestEntityWriter == null) {
RecordLog.warn("[NettyRequestEncoder] Cannot resolve the global request entity writer, dropping the request");
return;
}
requestEntityWriter.writeTo(request, out);
}
}

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.client.codec.netty;
import java.util.List;
import com.alibaba.csp.sentinel.cluster.client.codec.ClientEntityCodecProvider;
import com.alibaba.csp.sentinel.cluster.client.codec.registry.ResponseDataDecodeRegistry;
import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder;
import com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityDecoder;
import com.alibaba.csp.sentinel.cluster.response.ClusterResponse;
import com.alibaba.csp.sentinel.cluster.response.Response;
import com.alibaba.csp.sentinel.log.RecordLog;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public class NettyResponseDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
ResponseEntityDecoder<ByteBuf, Response> responseDecoder = ClientEntityCodecProvider.getResponseEntityDecoder();
if (responseDecoder == null) {
RecordLog.warn("[NettyResponseDecoder] Cannot resolve the global response entity decoder, "
+ "dropping the response");
return;
}
// TODO: handle decode error here.
Response response = responseDecoder.decode(in);
if (response != null) {
out.add(response);
}
}
}

View File

@@ -0,0 +1,48 @@
/*
* 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.codec.registry;
import java.util.HashMap;
import java.util.Map;
import com.alibaba.csp.sentinel.cluster.codec.EntityWriter;
import io.netty.buffer.ByteBuf;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public final class RequestDataWriterRegistry {
private static final Map<Integer, EntityWriter<Object, ByteBuf>> WRITER_MAP = new HashMap<>();
public static <T> boolean addWriter(int type, EntityWriter<T, ByteBuf> writer) {
if (WRITER_MAP.containsKey(type)) {
return false;
}
WRITER_MAP.put(type, (EntityWriter<Object, ByteBuf>)writer);
return true;
}
public static EntityWriter<Object, ByteBuf> getWriter(int type) {
return WRITER_MAP.get(type);
}
public static boolean remove(int type) {
return WRITER_MAP.remove(type) != null;
}
}

View File

@@ -0,0 +1,48 @@
/*
* 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.codec.registry;
import java.util.HashMap;
import java.util.Map;
import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder;
import io.netty.buffer.ByteBuf;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public final class ResponseDataDecodeRegistry {
private static final Map<Integer, EntityDecoder<ByteBuf, ?>> DECODER_MAP = new HashMap<>();
public static boolean addDecoder(int type, EntityDecoder<ByteBuf, ?> decoder) {
if (DECODER_MAP.containsKey(type)) {
return false;
}
DECODER_MAP.put(type, decoder);
return true;
}
public static EntityDecoder<ByteBuf, Object> getDecoder(int type) {
return (EntityDecoder<ByteBuf, Object>)DECODER_MAP.get(type);
}
public static boolean removeDecoder(int type) {
return DECODER_MAP.remove(type) != null;
}
}

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.client.config;
/**
* @author Eric Zhao
* @since 1.4.1
*/
public class ClusterClientAssignConfig {
private String serverHost;
private Integer serverPort;
public ClusterClientAssignConfig() {}
public ClusterClientAssignConfig(String serverHost, Integer serverPort) {
this.serverHost = serverHost;
this.serverPort = serverPort;
}
public String getServerHost() {
return serverHost;
}
public ClusterClientAssignConfig setServerHost(String serverHost) {
this.serverHost = serverHost;
return this;
}
public Integer getServerPort() {
return serverPort;
}
public ClusterClientAssignConfig setServerPort(Integer serverPort) {
this.serverPort = serverPort;
return this;
}
@Override
public String toString() {
return "ClusterClientAssignConfig{" +
"serverHost='" + serverHost + '\'' +
", serverPort=" + serverPort +
'}';
}
}

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.cluster.client.config;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public class ClusterClientConfig {
private Integer requestTimeout;
public Integer getRequestTimeout() {
return requestTimeout;
}
public ClusterClientConfig setRequestTimeout(Integer requestTimeout) {
this.requestTimeout = requestTimeout;
return this;
}
@Override
public String toString() {
return "ClusterClientConfig{" +
"requestTimeout=" + requestTimeout +
'}';
}
}

View File

@@ -0,0 +1,214 @@
/*
* 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.config;
import java.util.ArrayList;
import java.util.List;
import com.alibaba.csp.sentinel.cluster.ClusterConstants;
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.AssertUtil;
import com.alibaba.csp.sentinel.util.StringUtil;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public final class ClusterClientConfigManager {
/**
* Client config properties.
*/
private static volatile String serverHost = null;
private static volatile int serverPort = ClusterConstants.DEFAULT_CLUSTER_SERVER_PORT;
private static volatile int requestTimeout = ClusterConstants.DEFAULT_REQUEST_TIMEOUT;
private static volatile int connectTimeout = ClusterConstants.DEFAULT_CONNECT_TIMEOUT_MILLIS;
private static final PropertyListener<ClusterClientConfig> CONFIG_PROPERTY_LISTENER
= new ClientConfigPropertyListener();
private static final PropertyListener<ClusterClientAssignConfig> ASSIGN_PROPERTY_LISTENER
= new ClientAssignPropertyListener();
private static SentinelProperty<ClusterClientConfig> clientConfigProperty = new DynamicSentinelProperty<>();
private static SentinelProperty<ClusterClientAssignConfig> clientAssignProperty = new DynamicSentinelProperty<>();
private static final List<ServerChangeObserver> SERVER_CHANGE_OBSERVERS = new ArrayList<>();
static {
bindPropertyListener();
}
private static void bindPropertyListener() {
removePropertyListener();
clientAssignProperty.addListener(ASSIGN_PROPERTY_LISTENER);
clientConfigProperty.addListener(CONFIG_PROPERTY_LISTENER);
}
private static void removePropertyListener() {
clientAssignProperty.removeListener(ASSIGN_PROPERTY_LISTENER);
clientConfigProperty.removeListener(CONFIG_PROPERTY_LISTENER);
}
public static void registerServerAssignProperty(SentinelProperty<ClusterClientAssignConfig> property) {
AssertUtil.notNull(property, "property cannot be null");
synchronized (ASSIGN_PROPERTY_LISTENER) {
RecordLog.info("[ClusterClientConfigManager] Registering new server assignment property to cluster "
+ "client config manager");
clientAssignProperty.removeListener(ASSIGN_PROPERTY_LISTENER);
property.addListener(ASSIGN_PROPERTY_LISTENER);
clientAssignProperty = property;
}
}
public static void registerClientConfigProperty(SentinelProperty<ClusterClientConfig> property) {
AssertUtil.notNull(property, "property cannot be null");
synchronized (CONFIG_PROPERTY_LISTENER) {
RecordLog.info("[ClusterClientConfigManager] Registering new global client config property to "
+ "cluster client config manager");
clientConfigProperty.removeListener(CONFIG_PROPERTY_LISTENER);
property.addListener(CONFIG_PROPERTY_LISTENER);
clientConfigProperty = property;
}
}
public static void addServerChangeObserver(ServerChangeObserver observer) {
AssertUtil.notNull(observer, "observer cannot be null");
SERVER_CHANGE_OBSERVERS.add(observer);
}
/**
* Apply new {@link ClusterClientConfig}, while the former config will be replaced.
*
* @param config new config to apply
*/
public static void applyNewConfig(ClusterClientConfig config) {
clientConfigProperty.updateValue(config);
}
public static void applyNewAssignConfig(ClusterClientAssignConfig clusterClientAssignConfig) {
clientAssignProperty.updateValue(clusterClientAssignConfig);
}
private static class ClientAssignPropertyListener implements PropertyListener<ClusterClientAssignConfig> {
@Override
public void configLoad(ClusterClientAssignConfig config) {
if (config == null) {
RecordLog.warn("[ClusterClientConfigManager] Empty initial client assignment config");
return;
}
applyConfig(config);
}
@Override
public void configUpdate(ClusterClientAssignConfig config) {
applyConfig(config);
}
private synchronized void applyConfig(ClusterClientAssignConfig config) {
if (!isValidAssignConfig(config)) {
RecordLog.warn(
"[ClusterClientConfigManager] Invalid cluster client assign config, ignoring: " + config);
return;
}
if (serverPort == config.getServerPort() && config.getServerHost().equals(serverHost)) {
return;
}
RecordLog.info("[ClusterClientConfigManager] Assign to new target token server: {}", config);
updateServerAssignment(config);
}
}
private static class ClientConfigPropertyListener implements PropertyListener<ClusterClientConfig> {
@Override
public void configLoad(ClusterClientConfig config) {
if (config == null) {
RecordLog.warn("[ClusterClientConfigManager] Empty initial client config");
return;
}
applyConfig(config);
}
@Override
public void configUpdate(ClusterClientConfig config) {
applyConfig(config);
}
private synchronized void applyConfig(ClusterClientConfig config) {
if (!isValidClientConfig(config)) {
RecordLog.warn(
"[ClusterClientConfigManager] Invalid cluster client config, ignoring: {}", config);
return;
}
RecordLog.info("[ClusterClientConfigManager] Updating to new client config: {}", config);
updateClientConfigChange(config);
}
}
private static void updateClientConfigChange(ClusterClientConfig config) {
if (config.getRequestTimeout() != requestTimeout) {
requestTimeout = config.getRequestTimeout();
}
}
private static void updateServerAssignment(/*@Valid*/ ClusterClientAssignConfig config) {
String host = config.getServerHost();
int port = config.getServerPort();
for (ServerChangeObserver observer : SERVER_CHANGE_OBSERVERS) {
observer.onRemoteServerChange(config);
}
serverHost = host;
serverPort = port;
}
public static boolean isValidAssignConfig(ClusterClientAssignConfig config) {
return config != null && StringUtil.isNotBlank(config.getServerHost())
&& config.getServerPort() > 0
&& config.getServerPort() <= 65535;
}
public static boolean isValidClientConfig(ClusterClientConfig config) {
return config != null && config.getRequestTimeout() > 0;
}
public static String getServerHost() {
return serverHost;
}
public static int getServerPort() {
return serverPort;
}
public static int getRequestTimeout() {
return requestTimeout;
}
public static int getConnectTimeout() {
return connectTimeout;
}
private ClusterClientConfigManager() {}
}

View File

@@ -0,0 +1,49 @@
/*
* 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.config;
import com.alibaba.csp.sentinel.config.SentinelConfig;
import com.alibaba.csp.sentinel.log.RecordLog;
/**
* <p>
* this class dedicated to reading startup configurations of cluster client
* </p>
*
* @author lianglin
* @since 1.7.0
*/
public class ClusterClientStartUpConfig {
private static final String MAX_PARAM_BYTE_SIZE = "csp.sentinel.cluster.max.param.byte.size";
/**
* Get the max bytes params can be serialized
*
* @return the max bytes, may be null
*/
public static Integer getMaxParamByteSize() {
String maxParamByteSize = SentinelConfig.getConfig(MAX_PARAM_BYTE_SIZE);
try {
return maxParamByteSize == null ? null : Integer.valueOf(maxParamByteSize);
} catch (Exception ex) {
RecordLog.warn("[ClusterClientStartUpConfig] Failed to parse maxParamByteSize: " + maxParamByteSize);
return null;
}
}
}

View File

@@ -0,0 +1,30 @@
/*
* 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.config;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public interface ServerChangeObserver {
/**
* Callback on remote server address change.
*
* @param assignConfig new cluster assignment config
*/
void onRemoteServerChange(ClusterClientAssignConfig assignConfig);
}

View File

@@ -0,0 +1,119 @@
/*
* 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.handler;
import java.net.InetSocketAddress;
import java.util.concurrent.atomic.AtomicInteger;
import com.alibaba.csp.sentinel.cluster.ClusterConstants;
import com.alibaba.csp.sentinel.cluster.client.ClientConstants;
import com.alibaba.csp.sentinel.cluster.registry.ConfigSupplierRegistry;
import com.alibaba.csp.sentinel.cluster.request.ClusterRequest;
import com.alibaba.csp.sentinel.cluster.response.ClusterResponse;
import com.alibaba.csp.sentinel.log.RecordLog;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
* Netty client handler for Sentinel token client.
*
* @author Eric Zhao
* @since 1.4.0
*/
public class TokenClientHandler extends ChannelInboundHandlerAdapter {
private final AtomicInteger currentState;
private final Runnable disconnectCallback;
public TokenClientHandler(AtomicInteger currentState, Runnable disconnectCallback) {
this.currentState = currentState;
this.disconnectCallback = disconnectCallback;
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
currentState.set(ClientConstants.CLIENT_STATUS_STARTED);
fireClientPing(ctx);
RecordLog.info("[TokenClientHandler] Client handler active, remote address: {}", getRemoteAddress(ctx));
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof ClusterResponse) {
ClusterResponse<?> response = (ClusterResponse) msg;
if (response.getType() == ClusterConstants.MSG_TYPE_PING) {
handlePingResponse(ctx, response);
return;
}
TokenClientPromiseHolder.completePromise(response.getId(), response);
}
}
private void fireClientPing(ChannelHandlerContext ctx) {
// Data body: namespace of the client.
ClusterRequest<String> ping = new ClusterRequest<String>().setId(0)
.setType(ClusterConstants.MSG_TYPE_PING)
.setData(ConfigSupplierRegistry.getNamespaceSupplier().get());
ctx.writeAndFlush(ping);
}
private void handlePingResponse(ChannelHandlerContext ctx, ClusterResponse response) {
if (response.getStatus() == ClusterConstants.RESPONSE_STATUS_OK) {
int count = (int) response.getData();
RecordLog.info("[TokenClientHandler] Client ping OK (target server: {}, connected count: {})",
getRemoteAddress(ctx), count);
} else {
RecordLog.warn("[TokenClientHandler] Client ping failed (target server: {})", getRemoteAddress(ctx));
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
RecordLog.warn("[TokenClientHandler] Client exception caught", cause);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
RecordLog.info("[TokenClientHandler] Client handler inactive, remote address: {}", getRemoteAddress(ctx));
}
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
RecordLog.info("[TokenClientHandler] Client channel unregistered, remote address: {}", getRemoteAddress(ctx));
currentState.set(ClientConstants.CLIENT_STATUS_OFF);
disconnectCallback.run();
}
private String getRemoteAddress(ChannelHandlerContext ctx) {
if (ctx.channel().remoteAddress() == null) {
return null;
}
InetSocketAddress inetAddress = (InetSocketAddress) ctx.channel().remoteAddress();
return inetAddress.getAddress().getHostAddress() + ":" + inetAddress.getPort();
}
public int getCurrentState() {
return currentState.get();
}
public boolean hasStarted() {
return getCurrentState() == ClientConstants.CLIENT_STATUS_STARTED;
}
}

View File

@@ -0,0 +1,64 @@
/*
* 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.handler;
import java.util.AbstractMap.SimpleEntry;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.alibaba.csp.sentinel.cluster.response.ClusterResponse;
import io.netty.channel.ChannelPromise;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public final class TokenClientPromiseHolder {
private static final Map<Integer, SimpleEntry<ChannelPromise, ClusterResponse>> PROMISE_MAP = new ConcurrentHashMap<>();
public static void putPromise(int xid, ChannelPromise promise) {
PROMISE_MAP.put(xid, new SimpleEntry<ChannelPromise, ClusterResponse>(promise, null));
}
public static SimpleEntry<ChannelPromise, ClusterResponse> getEntry(int xid) {
return PROMISE_MAP.get(xid);
}
public static void remove(int xid) {
PROMISE_MAP.remove(xid);
}
public static <T> boolean completePromise(int xid, ClusterResponse<T> response) {
if (!PROMISE_MAP.containsKey(xid)) {
return false;
}
SimpleEntry<ChannelPromise, ClusterResponse> entry = PROMISE_MAP.get(xid);
if (entry != null) {
ChannelPromise promise = entry.getKey();
if (promise.isDone() || promise.isCancelled()) {
return false;
}
entry.setValue(response);
promise.setSuccess();
return true;
}
return false;
}
private TokenClientPromiseHolder() {}
}

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.client.init;
import com.alibaba.csp.sentinel.cluster.client.ClientConstants;
import com.alibaba.csp.sentinel.cluster.client.codec.data.FlowRequestDataWriter;
import com.alibaba.csp.sentinel.cluster.client.codec.data.FlowResponseDataDecoder;
import com.alibaba.csp.sentinel.cluster.client.codec.data.ParamFlowRequestDataWriter;
import com.alibaba.csp.sentinel.cluster.client.codec.data.PingRequestDataWriter;
import com.alibaba.csp.sentinel.cluster.client.codec.data.PingResponseDataDecoder;
import com.alibaba.csp.sentinel.cluster.client.codec.registry.RequestDataWriterRegistry;
import com.alibaba.csp.sentinel.cluster.client.codec.registry.ResponseDataDecodeRegistry;
import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientStartUpConfig;
import com.alibaba.csp.sentinel.init.InitFunc;
import com.alibaba.csp.sentinel.init.InitOrder;
/**
* @author Eric Zhao
* @since 1.4.0
*/
@InitOrder(0)
public class DefaultClusterClientInitFunc implements InitFunc {
@Override
public void init() throws Exception {
initDefaultEntityWriters();
initDefaultEntityDecoders();
}
private void initDefaultEntityWriters() {
RequestDataWriterRegistry.addWriter(ClientConstants.TYPE_PING, new PingRequestDataWriter());
RequestDataWriterRegistry.addWriter(ClientConstants.TYPE_FLOW, new FlowRequestDataWriter());
Integer maxParamByteSize = ClusterClientStartUpConfig.getMaxParamByteSize();
if (maxParamByteSize == null) {
RequestDataWriterRegistry.addWriter(ClientConstants.TYPE_PARAM_FLOW, new ParamFlowRequestDataWriter());
} else {
RequestDataWriterRegistry.addWriter(ClientConstants.TYPE_PARAM_FLOW, new ParamFlowRequestDataWriter(maxParamByteSize));
}
}
private void initDefaultEntityDecoders() {
ResponseDataDecodeRegistry.addDecoder(ClientConstants.TYPE_PING, new PingResponseDataDecoder());
ResponseDataDecodeRegistry.addDecoder(ClientConstants.TYPE_FLOW, new FlowResponseDataDecoder());
ResponseDataDecodeRegistry.addDecoder(ClientConstants.TYPE_PARAM_FLOW, new FlowResponseDataDecoder());
}
}

View File

@@ -0,0 +1,89 @@
/*
* 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.command.entity;
import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientAssignConfig;
import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfig;
/**
* @author Eric Zhao
* @since 1.4.1
*/
public class ClusterClientStateEntity {
private String serverHost;
private Integer serverPort;
private Integer clientState;
private Integer requestTimeout;
public String getServerHost() {
return serverHost;
}
public ClusterClientStateEntity setServerHost(String serverHost) {
this.serverHost = serverHost;
return this;
}
public Integer getServerPort() {
return serverPort;
}
public ClusterClientStateEntity setServerPort(Integer serverPort) {
this.serverPort = serverPort;
return this;
}
public Integer getRequestTimeout() {
return requestTimeout;
}
public ClusterClientStateEntity setRequestTimeout(Integer requestTimeout) {
this.requestTimeout = requestTimeout;
return this;
}
public Integer getClientState() {
return clientState;
}
public ClusterClientStateEntity setClientState(Integer clientState) {
this.clientState = clientState;
return this;
}
public ClusterClientConfig toClientConfig() {
return new ClusterClientConfig().setRequestTimeout(requestTimeout);
}
public ClusterClientAssignConfig toAssignConfig() {
return new ClusterClientAssignConfig()
.setServerHost(serverHost)
.setServerPort(serverPort);
}
@Override
public String toString() {
return "ClusterClientStateEntity{" +
"serverHost='" + serverHost + '\'' +
", serverPort=" + serverPort +
", clientState=" + clientState +
", requestTimeout=" + requestTimeout +
'}';
}
}

View File

@@ -0,0 +1,49 @@
/*
* 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.command.handler;
import com.alibaba.csp.sentinel.cluster.client.ClientConstants;
import com.alibaba.csp.sentinel.cluster.client.TokenClientProvider;
import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfigManager;
import com.alibaba.csp.sentinel.command.CommandHandler;
import com.alibaba.csp.sentinel.command.CommandRequest;
import com.alibaba.csp.sentinel.command.CommandResponse;
import com.alibaba.csp.sentinel.command.annotation.CommandMapping;
import com.alibaba.csp.sentinel.command.entity.ClusterClientStateEntity;
import com.alibaba.fastjson.JSON;
/**
* @author Eric Zhao
* @since 1.4.0
*/
@CommandMapping(name = "cluster/client/fetchConfig", desc = "get cluster client config")
public class FetchClusterClientConfigHandler implements CommandHandler<String> {
@Override
public CommandResponse<String> handle(CommandRequest request) {
ClusterClientStateEntity stateVO = new ClusterClientStateEntity()
.setServerHost(ClusterClientConfigManager.getServerHost())
.setServerPort(ClusterClientConfigManager.getServerPort())
.setRequestTimeout(ClusterClientConfigManager.getRequestTimeout());
if (TokenClientProvider.isClientSpiAvailable()) {
stateVO.setClientState(TokenClientProvider.getClient().getState());
} else {
stateVO.setClientState(ClientConstants.CLIENT_STATUS_OFF);
}
return CommandResponse.ofSuccess(JSON.toJSONString(stateVO));
}
}

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.command.handler;
import java.net.URLDecoder;
import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfig;
import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfigManager;
import com.alibaba.csp.sentinel.command.CommandConstants;
import com.alibaba.csp.sentinel.command.CommandHandler;
import com.alibaba.csp.sentinel.command.CommandRequest;
import com.alibaba.csp.sentinel.command.CommandResponse;
import com.alibaba.csp.sentinel.command.annotation.CommandMapping;
import com.alibaba.csp.sentinel.command.entity.ClusterClientStateEntity;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.fastjson.JSON;
/**
* @author Eric Zhao
* @since 1.4.0
*/
@CommandMapping(name = "cluster/client/modifyConfig", desc = "modify cluster client config")
public class ModifyClusterClientConfigHandler implements CommandHandler<String> {
@Override
public CommandResponse<String> handle(CommandRequest request) {
String data = request.getParam("data");
if (StringUtil.isBlank(data)) {
return CommandResponse.ofFailure(new IllegalArgumentException("empty data"));
}
try {
data = URLDecoder.decode(data, "utf-8");
RecordLog.info("[ModifyClusterClientConfigHandler] Receiving cluster client config: {}", data);
ClusterClientStateEntity entity = JSON.parseObject(data, ClusterClientStateEntity.class);
ClusterClientConfigManager.applyNewConfig(entity.toClientConfig());
ClusterClientConfigManager.applyNewAssignConfig(entity.toAssignConfig());
return CommandResponse.ofSuccess(CommandConstants.MSG_SUCCESS);
} catch (Exception e) {
RecordLog.warn("[ModifyClusterClientConfigHandler] Decode client cluster config error", e);
return CommandResponse.ofFailure(e, "decode client cluster config error");
}
}
}

View File

@@ -0,0 +1 @@
com.alibaba.csp.sentinel.cluster.client.DefaultClusterTokenClient

View File

@@ -0,0 +1 @@
com.alibaba.csp.sentinel.cluster.client.codec.DefaultRequestEntityWriter

View File

@@ -0,0 +1 @@
com.alibaba.csp.sentinel.cluster.client.codec.DefaultResponseEntityDecoder

View File

@@ -0,0 +1,2 @@
com.alibaba.csp.sentinel.command.handler.ModifyClusterClientConfigHandler
com.alibaba.csp.sentinel.command.handler.FetchClusterClientConfigHandler

View File

@@ -0,0 +1 @@
com.alibaba.csp.sentinel.cluster.client.init.DefaultClusterClientInitFunc

View File

@@ -0,0 +1,36 @@
/*
* 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.codec.data;
import com.alibaba.csp.sentinel.cluster.response.data.FlowTokenResponseData;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.junit.Assert;
import org.junit.Test;
public class FlowResponseDataDecoderTest {
@Test
public void testDecode() {
ByteBuf buf = Unpooled.buffer();
FlowResponseDataDecoder decoder = new FlowResponseDataDecoder();
FlowTokenResponseData data = new FlowTokenResponseData();
data.setRemainingCount(12);
data.setWaitInMs(13);
buf.writeInt(12);
buf.writeInt(13);
Assert.assertEquals(decoder.decode(buf), data);
}
}

View File

@@ -0,0 +1,77 @@
package com.alibaba.csp.sentinel.cluster.client.codec.data;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.*;
/**
* @author Eric Zhao
*/
public class ParamFlowRequestDataWriterTest {
@Test
public void testCalculateParamTransportSize() {
ParamFlowRequestDataWriter writer = new ParamFlowRequestDataWriter();
// POJO (non-primitive type) should not be regarded as a valid parameter.
assertEquals(0, writer.calculateParamTransportSize(new SomePojo().setParam1("abc")));
assertEquals(4 + 1, writer.calculateParamTransportSize(1));
assertEquals(1 + 1, writer.calculateParamTransportSize((byte) 1));
assertEquals(1 + 1, writer.calculateParamTransportSize(false));
assertEquals(8 + 1, writer.calculateParamTransportSize(2L));
assertEquals(8 + 1, writer.calculateParamTransportSize(4.0d));
final String paramStr = "Sentinel";
assertEquals(1 + 4 + paramStr.getBytes().length, writer.calculateParamTransportSize(paramStr));
}
@Test
public void testResolveValidParams() {
final int maxSize = 15;
ParamFlowRequestDataWriter writer = new ParamFlowRequestDataWriter(maxSize);
ArrayList<Object> params = new ArrayList<Object>() {{
add(1);
add(64);
add(3);
}};
List<Object> validParams = writer.resolveValidParams(params);
assertTrue(validParams.contains(1) && validParams.contains(64) && validParams.contains(3));
//when over maxSize, the exceed number should not be contained
params.add(5);
assertFalse(writer.resolveValidParams(params).contains(5));
//POJO (non-primitive type) should not be regarded as a valid parameter
assertTrue(writer.resolveValidParams(new ArrayList<Object>() {{
add(new SomePojo());
}}).size() == 0);
}
private static class SomePojo {
private String param1;
public String getParam1() {
return param1;
}
public SomePojo setParam1(String param1) {
this.param1 = param1;
return this;
}
@Override
public String toString() {
return "SomePojo{" +
"param1='" + param1 + '\'' +
'}';
}
}
}

View File

@@ -0,0 +1,44 @@
/*
* 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.codec.data;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Eric Zhao
*/
public class PingResponseDataDecoderTest {
@Test
public void testDecodePingResponseData() {
ByteBuf buf = Unpooled.buffer();
PingResponseDataDecoder decoder = new PingResponseDataDecoder();
int big = Integer.MAX_VALUE;
buf.writeInt(big);
assertThat(decoder.decode(buf)).isEqualTo(big);
byte small = 12;
buf.writeByte(small);
assertThat(decoder.decode(buf)).isEqualTo(small);
buf.release();
}
}

View File

@@ -0,0 +1,22 @@
<?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">
<parent>
<artifactId>sentinel-cluster</artifactId>
<groupId>com.alibaba.csp</groupId>
<version>1.8.3</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sentinel-cluster-common-default</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,48 @@
/*
* 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 ClusterConstants {
public static final int MSG_TYPE_PING = 0;
public static final int MSG_TYPE_FLOW = 1;
public static final int MSG_TYPE_PARAM_FLOW = 2;
public static final int MSG_TYPE_CONCURRENT_FLOW_ACQUIRE = 3;
public static final int MSG_TYPE_CONCURRENT_FLOW_RELEASE = 4;
public static final int RESPONSE_STATUS_BAD = -1;
public static final int RESPONSE_STATUS_OK = 0;
public static final int PARAM_TYPE_INTEGER = 0;
public static final int PARAM_TYPE_LONG = 1;
public static final int PARAM_TYPE_BYTE = 2;
public static final int PARAM_TYPE_DOUBLE = 3;
public static final int PARAM_TYPE_FLOAT = 4;
public static final int PARAM_TYPE_SHORT = 5;
public static final int PARAM_TYPE_BOOLEAN = 6;
public static final int PARAM_TYPE_STRING = 7;
public static final int DEFAULT_CLUSTER_SERVER_PORT = 18730;
public static final int DEFAULT_REQUEST_TIMEOUT = 20;
public static final int DEFAULT_CONNECT_TIMEOUT_MILLIS = 10 * 1000;
private ClusterConstants() {}
}

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.cluster;
/**
* @author jialiang.ljl
* @since 1.4.0
*/
public final class ClusterErrorMessages {
public static final String BAD_REQUEST = "bad request";
public static final String UNEXPECTED_STATUS = "unexpected status";
public static final String TOO_MANY_REQUESTS = "too many requests (client side)";
public static final String REQUEST_TIME_OUT = "request time out";
public static final String CLIENT_NOT_READY = "client not ready";
public static final String NO_RULES_IN_SERVER = "no rules in token server";
private ClusterErrorMessages() {}
}

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.cluster;
import com.alibaba.csp.sentinel.cluster.request.ClusterRequest;
import com.alibaba.csp.sentinel.cluster.response.ClusterResponse;
/**
* Synchronous transport client for distributed flow control.
*
* @author Eric Zhao
* @since 1.4.0
*/
public interface ClusterTransportClient {
/**
* Start the client.
*
* @throws Exception some error occurred (e.g. initialization failed)
*/
void start() throws Exception;
/**
* Stop the client.
*
* @throws Exception some error occurred (e.g. shutdown failed)
*/
void stop() throws Exception;
/**
* Send request to remote server and get response.
*
* @param request Sentinel cluster request
* @return response from remote server
* @throws Exception some error occurs
*/
ClusterResponse sendRequest(ClusterRequest request) throws Exception;
/**
* Check whether the client has been started and ready for sending requests.
*
* @return true if the client is ready to send requests, otherwise false
*/
boolean isReady();
}

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.cluster.annotation;
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;
/**
* Request type annotation for handlers, codes, etc.
*
* @author Eric Zhao
* @since 1.4.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
public @interface RequestType {
/**
* Type of the request to handle.
*
* @return type of the request
*/
int value();
}

View File

@@ -0,0 +1,33 @@
/*
* 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.codec;
/**
* @param <S> source stream type
* @param <T> target entity type
* @author Eric Zhao
* @since 1.4.0
*/
public interface EntityDecoder<S, T> {
/**
* Decode target object from source stream.
*
* @param source source stream
* @return decoded target object
*/
T decode(S source);
}

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.cluster.codec;
/**
* A universal interface for publishing entities to a target stream.
*
* @param <E> entity type
* @param <T> target stream type
* @author Eric Zhao
* @since 1.4.0
*/
public interface EntityWriter<E, T> {
/**
* Write the provided entity to target stream.
*
* @param entity entity to publish
* @param target the target stream
*/
void writeTo(E entity, T target);
}

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.codec.request;
import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder;
import com.alibaba.csp.sentinel.cluster.request.Request;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public interface RequestEntityDecoder<S, T extends Request> extends EntityDecoder<S, T> {
}

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.cluster.codec.request;
import com.alibaba.csp.sentinel.cluster.codec.EntityWriter;
import com.alibaba.csp.sentinel.cluster.request.Request;
/**
* A universal {@link EntityWriter} interface for publishing {@link Request} to a target stream.
*
* @author Eric Zhao
* @since 1.4.0
*/
public interface RequestEntityWriter<E extends Request, T> extends EntityWriter<E, T> {
}

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.codec.response;
import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder;
import com.alibaba.csp.sentinel.cluster.response.Response;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public interface ResponseEntityDecoder<S, T extends Response> extends EntityDecoder<S, T> {
}

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.cluster.codec.response;
import com.alibaba.csp.sentinel.cluster.codec.EntityWriter;
import com.alibaba.csp.sentinel.cluster.response.Response;
/**
* A universal {@link EntityWriter} interface for publishing {@link Response} to a target stream.
*
* @author Eric Zhao
* @since 1.4.0
*/
public interface ResponseEntityWriter<E extends Response, T> extends EntityWriter<E, T> {
}

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.cluster.exception;
/**
* @author jialiang.ljl
* @since 1.4.0
*/
public class SentinelClusterException extends Exception {
public SentinelClusterException(String errorMsg) {
super(errorMsg);
}
@Override
public Throwable fillInStackTrace() {
return this;
}
}

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.cluster.registry;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.util.AppNameUtil;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.alibaba.csp.sentinel.util.function.Supplier;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public final class ConfigSupplierRegistry {
/**
* The default namespace supplier provides appName as namespace.
*/
private static final Supplier<String> DEFAULT_APP_NAME_SUPPLIER = new Supplier<String>() {
@Override
public String get() {
return AppNameUtil.getAppName();
}
};
/**
* Registered namespace supplier.
*/
private static Supplier<String> namespaceSupplier = DEFAULT_APP_NAME_SUPPLIER;
/**
* Get the registered namespace supplier.
*
* @return the registered namespace supplier
*/
public static Supplier<String> getNamespaceSupplier() {
return namespaceSupplier;
}
public static void setNamespaceSupplier(Supplier<String> namespaceSupplier) {
AssertUtil.notNull(namespaceSupplier, "namespaceSupplier cannot be null");
ConfigSupplierRegistry.namespaceSupplier = namespaceSupplier;
RecordLog.info("[ConfigSupplierRegistry] New namespace supplier provided, current supplied: {}",
namespaceSupplier.get());
}
private ConfigSupplierRegistry() {}
}

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.cluster.request;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public class ClusterRequest<T> implements Request {
private int id;
private int type;
private T data;
public ClusterRequest() {}
public ClusterRequest(int id, int type, T data) {
this.id = id;
this.type = type;
this.data = data;
}
public ClusterRequest(int type, T data) {
this.type = type;
this.data = data;
}
@Override
public int getId() {
return id;
}
public ClusterRequest<T> setId(int id) {
this.id = id;
return this;
}
@Override
public int getType() {
return type;
}
public ClusterRequest<T> setType(int type) {
this.type = type;
return this;
}
public T getData() {
return data;
}
public ClusterRequest<T> setData(T data) {
this.data = data;
return this;
}
@Override
public String toString() {
return "ClusterRequest{" +
"id=" + id +
", type=" + type +
", data=" + data +
'}';
}
}

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.request;
/**
* Cluster transport request interface.
*
* @author Eric Zhao
* @since 1.4.0
*/
public interface Request {
/**
* Get request type.
*
* @return request type
*/
int getType();
/**
* Get request ID.
*
* @return unique request ID
*/
int getId();
}

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.request.data;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public class FlowRequestData {
private long flowId;
private int count;
private boolean priority;
public long getFlowId() {
return flowId;
}
public FlowRequestData setFlowId(long flowId) {
this.flowId = flowId;
return this;
}
public int getCount() {
return count;
}
public FlowRequestData setCount(int count) {
this.count = count;
return this;
}
public boolean isPriority() {
return priority;
}
public FlowRequestData setPriority(boolean priority) {
this.priority = priority;
return this;
}
@Override
public String toString() {
return "FlowRequestData{" +
"flowId=" + flowId +
", count=" + count +
", priority=" + priority +
'}';
}
}

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.cluster.request.data;
import java.util.Collection;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public class ParamFlowRequestData {
private long flowId;
private int count;
private Collection<Object> params;
public long getFlowId() {
return flowId;
}
public ParamFlowRequestData setFlowId(long flowId) {
this.flowId = flowId;
return this;
}
public int getCount() {
return count;
}
public ParamFlowRequestData setCount(int count) {
this.count = count;
return this;
}
public Collection<Object> getParams() {
return params;
}
public ParamFlowRequestData setParams(Collection<Object> params) {
this.params = params;
return this;
}
@Override
public String toString() {
return "ParamFlowRequestData{" +
"flowId=" + flowId +
", count=" + count +
", params=" + params +
'}';
}
}

View File

@@ -0,0 +1,87 @@
/*
* 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.response;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public class ClusterResponse<T> implements Response {
private int id;
private int type;
private int status;
private T data;
public ClusterResponse() {}
public ClusterResponse(int id, int type, int status, T data) {
this.id = id;
this.type = type;
this.status = status;
this.data = data;
}
@Override
public int getId() {
return id;
}
public ClusterResponse<T> setId(int id) {
this.id = id;
return this;
}
@Override
public int getType() {
return type;
}
public ClusterResponse<T> setType(int type) {
this.type = type;
return this;
}
@Override
public int getStatus() {
return status;
}
public ClusterResponse<T> setStatus(int status) {
this.status = status;
return this;
}
public T getData() {
return data;
}
public ClusterResponse<T> setData(T data) {
this.data = data;
return this;
}
@Override
public String toString() {
return "ClusterResponse{" +
"id=" + id +
", type=" + type +
", status=" + status +
", data=" + data +
'}';
}
}

View File

@@ -0,0 +1,46 @@
/*
* 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.response;
/**
* Cluster transport response interface.
*
* @author Eric Zhao
* @since 1.4.0
*/
public interface Response {
/**
* Get response ID.
*
* @return response ID
*/
int getId();
/**
* Get response type.
*
* @return response type
*/
int getType();
/**
* Get response status.
*
* @return response status
*/
int getStatus();
}

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.cluster.response.data;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public class FlowTokenResponseData {
private int remainingCount;
private int waitInMs;
public int getRemainingCount() {
return remainingCount;
}
public FlowTokenResponseData setRemainingCount(int remainingCount) {
this.remainingCount = remainingCount;
return this;
}
public int getWaitInMs() {
return waitInMs;
}
public FlowTokenResponseData setWaitInMs(int waitInMs) {
this.waitInMs = waitInMs;
return this;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof FlowTokenResponseData)) {
return false;
}
FlowTokenResponseData that = (FlowTokenResponseData) o;
return this.remainingCount == that.remainingCount && this.waitInMs == that.waitInMs;
}
@Override
public int hashCode() {
int result = remainingCount;
result = 31 * result + waitInMs;
return result;
}
@Override
public String toString() {
return "FlowTokenResponseData{" +
"remainingCount=" + remainingCount +
", waitInMs=" + waitInMs +
'}';
}
}

View File

@@ -0,0 +1,70 @@
<?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">
<parent>
<artifactId>sentinel-cluster</artifactId>
<groupId>com.alibaba.csp</groupId>
<version>1.8.3</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sentinel-cluster-server-default</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-common</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-cluster-common-default</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-parameter-flow-control</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-handler</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</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>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,121 @@
/*
* 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.flow;
import com.alibaba.csp.sentinel.cluster.TokenResultStatus;
import com.alibaba.csp.sentinel.cluster.TokenResult;
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager;
import com.alibaba.csp.sentinel.cluster.flow.statistic.ClusterMetricStatistics;
import com.alibaba.csp.sentinel.cluster.flow.statistic.limit.GlobalRequestLimiter;
import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager;
import com.alibaba.csp.sentinel.cluster.flow.statistic.data.ClusterFlowEvent;
import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterMetric;
import com.alibaba.csp.sentinel.cluster.server.log.ClusterServerStatLogUtil;
import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
/**
* Flow checker for cluster flow rules.
*
* @author Eric Zhao
* @since 1.4.0
*/
final class ClusterFlowChecker {
private static double calcGlobalThreshold(FlowRule rule) {
double count = rule.getCount();
switch (rule.getClusterConfig().getThresholdType()) {
case ClusterRuleConstant.FLOW_THRESHOLD_GLOBAL:
return count;
case ClusterRuleConstant.FLOW_THRESHOLD_AVG_LOCAL:
default:
int connectedCount = ClusterFlowRuleManager.getConnectedCount(rule.getClusterConfig().getFlowId());
return count * connectedCount;
}
}
static boolean allowProceed(long flowId) {
String namespace = ClusterFlowRuleManager.getNamespace(flowId);
return GlobalRequestLimiter.tryPass(namespace);
}
static TokenResult acquireClusterToken(/*@Valid*/ FlowRule rule, int acquireCount, boolean prioritized) {
Long id = rule.getClusterConfig().getFlowId();
if (!allowProceed(id)) {
return new TokenResult(TokenResultStatus.TOO_MANY_REQUEST);
}
ClusterMetric metric = ClusterMetricStatistics.getMetric(id);
if (metric == null) {
return new TokenResult(TokenResultStatus.FAIL);
}
double latestQps = metric.getAvg(ClusterFlowEvent.PASS);
double globalThreshold = calcGlobalThreshold(rule) * ClusterServerConfigManager.getExceedCount();
double nextRemaining = globalThreshold - latestQps - acquireCount;
if (nextRemaining >= 0) {
// TODO: checking logic and metric operation should be separated.
metric.add(ClusterFlowEvent.PASS, acquireCount);
metric.add(ClusterFlowEvent.PASS_REQUEST, 1);
if (prioritized) {
// Add prioritized pass.
metric.add(ClusterFlowEvent.OCCUPIED_PASS, acquireCount);
}
// Remaining count is cut down to a smaller integer.
return new TokenResult(TokenResultStatus.OK)
.setRemaining((int) nextRemaining)
.setWaitInMs(0);
} else {
if (prioritized) {
// Try to occupy incoming buckets.
double occupyAvg = metric.getAvg(ClusterFlowEvent.WAITING);
if (occupyAvg <= ClusterServerConfigManager.getMaxOccupyRatio() * globalThreshold) {
int waitInMs = metric.tryOccupyNext(ClusterFlowEvent.PASS, acquireCount, globalThreshold);
// waitInMs > 0 indicates pre-occupy incoming buckets successfully.
if (waitInMs > 0) {
ClusterServerStatLogUtil.log("flow|waiting|" + id);
return new TokenResult(TokenResultStatus.SHOULD_WAIT)
.setRemaining(0)
.setWaitInMs(waitInMs);
}
// Or else occupy failed, should be blocked.
}
}
// Blocked.
metric.add(ClusterFlowEvent.BLOCK, acquireCount);
metric.add(ClusterFlowEvent.BLOCK_REQUEST, 1);
ClusterServerStatLogUtil.log("flow|block|" + id, acquireCount);
ClusterServerStatLogUtil.log("flow|block_request|" + id, 1);
if (prioritized) {
// Add prioritized block.
metric.add(ClusterFlowEvent.OCCUPIED_BLOCK, acquireCount);
ClusterServerStatLogUtil.log("flow|occupied_block|" + id, 1);
}
return blockedResult();
}
}
private static TokenResult blockedResult() {
return new TokenResult(TokenResultStatus.BLOCKED)
.setRemaining(0)
.setWaitInMs(0);
}
private ClusterFlowChecker() {}
}

View File

@@ -0,0 +1,123 @@
/*
* 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.flow;
import java.util.Collection;
import com.alibaba.csp.sentinel.cluster.TokenResult;
import com.alibaba.csp.sentinel.cluster.TokenResultStatus;
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterParamFlowRuleManager;
import com.alibaba.csp.sentinel.cluster.flow.statistic.ClusterParamMetricStatistics;
import com.alibaba.csp.sentinel.cluster.flow.statistic.limit.GlobalRequestLimiter;
import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterParamMetric;
import com.alibaba.csp.sentinel.cluster.server.log.ClusterServerStatLogUtil;
import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
/**
* @author jialiang.linjl
* @author Eric Zhao
* @since 1.4.0
*/
public final class ClusterParamFlowChecker {
static boolean allowProceed(long flowId) {
String namespace = ClusterParamFlowRuleManager.getNamespace(flowId);
return GlobalRequestLimiter.tryPass(namespace);
}
static TokenResult acquireClusterToken(ParamFlowRule rule, int count, Collection<Object> values) {
Long id = rule.getClusterConfig().getFlowId();
if (!allowProceed(id)) {
return new TokenResult(TokenResultStatus.TOO_MANY_REQUEST);
}
ClusterParamMetric metric = ClusterParamMetricStatistics.getMetric(id);
if (metric == null) {
// Unexpected state, return FAIL.
return new TokenResult(TokenResultStatus.FAIL);
}
if (values == null || values.isEmpty()) {
// Empty parameter list will always pass.
return new TokenResult(TokenResultStatus.OK);
}
double remaining = -1;
boolean hasPassed = true;
Object blockObject = null;
for (Object value : values) {
double latestQps = metric.getAvg(value);
double threshold = calcGlobalThreshold(rule, value);
double nextRemaining = threshold - latestQps - count;
remaining = nextRemaining;
if (nextRemaining < 0) {
hasPassed = false;
blockObject = value;
break;
}
}
if (hasPassed) {
for (Object value : values) {
metric.addValue(value, count);
}
ClusterServerStatLogUtil.log(String.format("param|pass|%d", id));
} else {
ClusterServerStatLogUtil.log(String.format("param|block|%d|%s", id, blockObject));
}
if (values.size() > 1) {
// Remaining field is unsupported for multi-values.
remaining = -1;
}
return hasPassed ? newPassResponse((int)remaining): newBlockResponse();
}
private static TokenResult newPassResponse(int remaining) {
return new TokenResult(TokenResultStatus.OK)
.setRemaining(remaining)
.setWaitInMs(0);
}
private static TokenResult newBlockResponse() {
return new TokenResult(TokenResultStatus.BLOCKED)
.setRemaining(0)
.setWaitInMs(0);
}
private static double calcGlobalThreshold(ParamFlowRule rule, Object value) {
double count = getRawThreshold(rule, value);
switch (rule.getClusterConfig().getThresholdType()) {
case ClusterRuleConstant.FLOW_THRESHOLD_GLOBAL:
return count;
case ClusterRuleConstant.FLOW_THRESHOLD_AVG_LOCAL:
default:
int connectedCount = ClusterParamFlowRuleManager.getConnectedCount(rule.getClusterConfig().getFlowId());
return count * connectedCount;
}
}
private static double getRawThreshold(ParamFlowRule rule, Object value) {
Integer itemCount = rule.retrieveExclusiveItemCount(value);
if (itemCount == null) {
return rule.getCount();
} else {
return itemCount;
}
}
private ClusterParamFlowChecker() {}
}

View File

@@ -0,0 +1,102 @@
/*
* 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.flow;
import com.alibaba.csp.sentinel.cluster.TokenResult;
import com.alibaba.csp.sentinel.cluster.TokenResultStatus;
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager;
import com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.CurrentConcurrencyManager;
import com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.TokenCacheNode;
import com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.TokenCacheNodeManager;
import com.alibaba.csp.sentinel.cluster.server.log.ClusterServerStatLogUtil;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author yunfeiyanggzq
*/
final public class ConcurrentClusterFlowChecker {
public static double calcGlobalThreshold(FlowRule rule) {
double count = rule.getCount();
switch (rule.getClusterConfig().getThresholdType()) {
case ClusterRuleConstant.FLOW_THRESHOLD_GLOBAL:
return count;
case ClusterRuleConstant.FLOW_THRESHOLD_AVG_LOCAL:
default:
int connectedCount = ClusterFlowRuleManager.getConnectedCount(rule.getClusterConfig().getFlowId());
return count * connectedCount;
}
}
public static TokenResult acquireConcurrentToken(/*@Valid*/ String clientAddress, FlowRule rule, int acquireCount) {
long flowId = rule.getClusterConfig().getFlowId();
AtomicInteger nowCalls = CurrentConcurrencyManager.get(flowId);
if (nowCalls == null) {
RecordLog.warn("[ConcurrentClusterFlowChecker] Fail to get nowCalls by flowId<{}>", flowId);
return new TokenResult(TokenResultStatus.FAIL);
}
// check before enter the lock to improve the efficiency
if (nowCalls.get() + acquireCount > calcGlobalThreshold(rule)) {
ClusterServerStatLogUtil.log("concurrent|block|" + flowId, acquireCount);
return new TokenResult(TokenResultStatus.BLOCKED);
}
// ensure the atomicity of operations
// lock different nowCalls to improve the efficiency
synchronized (nowCalls) {
// check again whether the request can pass.
if (nowCalls.get() + acquireCount > calcGlobalThreshold(rule)) {
ClusterServerStatLogUtil.log("concurrent|block|" + flowId, acquireCount);
return new TokenResult(TokenResultStatus.BLOCKED);
} else {
nowCalls.getAndAdd(acquireCount);
}
}
ClusterServerStatLogUtil.log("concurrent|pass|" + flowId, acquireCount);
TokenCacheNode node = TokenCacheNode.generateTokenCacheNode(rule, acquireCount, clientAddress);
TokenCacheNodeManager.putTokenCacheNode(node.getTokenId(), node);
TokenResult tokenResult = new TokenResult(TokenResultStatus.OK);
tokenResult.setTokenId(node.getTokenId());
return tokenResult;
}
public static TokenResult releaseConcurrentToken(/*@Valid*/ long tokenId) {
TokenCacheNode node = TokenCacheNodeManager.getTokenCacheNode(tokenId);
if (node == null) {
RecordLog.info("[ConcurrentClusterFlowChecker] Token<{}> is already released", tokenId);
return new TokenResult(TokenResultStatus.ALREADY_RELEASE);
}
FlowRule rule = ClusterFlowRuleManager.getFlowRuleById(node.getFlowId());
if (rule == null) {
RecordLog.info("[ConcurrentClusterFlowChecker] Fail to get rule by flowId<{}>", node.getFlowId());
return new TokenResult(TokenResultStatus.NO_RULE_EXISTS);
}
if (TokenCacheNodeManager.removeTokenCacheNode(tokenId) == null) {
RecordLog.info("[ConcurrentClusterFlowChecker] Token<{}> is already released for flowId<{}>", tokenId, node.getFlowId());
return new TokenResult(TokenResultStatus.ALREADY_RELEASE);
}
int acquireCount = node.getAcquireCount();
AtomicInteger nowCalls = CurrentConcurrencyManager.get(node.getFlowId());
nowCalls.getAndAdd(-1 * acquireCount);
ClusterServerStatLogUtil.log("concurrent|release|" + rule.getClusterConfig().getFlowId(), acquireCount);
return new TokenResult(TokenResultStatus.RELEASE_OK);
}
}

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.flow;
import com.alibaba.csp.sentinel.cluster.TokenResult;
import com.alibaba.csp.sentinel.cluster.TokenResultStatus;
import com.alibaba.csp.sentinel.cluster.TokenService;
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager;
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterParamFlowRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
import com.alibaba.csp.sentinel.spi.Spi;
import java.util.Collection;
/**
* Default implementation for cluster {@link TokenService}.
*
* @author Eric Zhao
* @since 1.4.0
*/
@Spi(isDefault = true)
public class DefaultTokenService implements TokenService {
@Override
public TokenResult requestToken(Long ruleId, int acquireCount, boolean prioritized) {
if (notValidRequest(ruleId, acquireCount)) {
return badRequest();
}
// The rule should be valid.
FlowRule rule = ClusterFlowRuleManager.getFlowRuleById(ruleId);
if (rule == null) {
return new TokenResult(TokenResultStatus.NO_RULE_EXISTS);
}
return ClusterFlowChecker.acquireClusterToken(rule, acquireCount, prioritized);
}
@Override
public TokenResult requestParamToken(Long ruleId, int acquireCount, Collection<Object> params) {
if (notValidRequest(ruleId, acquireCount) || params == null || params.isEmpty()) {
return badRequest();
}
// The rule should be valid.
ParamFlowRule rule = ClusterParamFlowRuleManager.getParamRuleById(ruleId);
if (rule == null) {
return new TokenResult(TokenResultStatus.NO_RULE_EXISTS);
}
return ClusterParamFlowChecker.acquireClusterToken(rule, acquireCount, params);
}
@Override
public TokenResult requestConcurrentToken(String clientAddress, Long ruleId, int acquireCount) {
if (notValidRequest(clientAddress, ruleId, acquireCount)) {
return badRequest();
}
// The rule should be valid.
FlowRule rule = ClusterFlowRuleManager.getFlowRuleById(ruleId);
if (rule == null) {
return new TokenResult(TokenResultStatus.NO_RULE_EXISTS);
}
return ConcurrentClusterFlowChecker.acquireConcurrentToken(clientAddress, rule, acquireCount);
}
@Override
public void releaseConcurrentToken(Long tokenId) {
if (tokenId == null) {
return;
}
ConcurrentClusterFlowChecker.releaseConcurrentToken(tokenId);
}
private boolean notValidRequest(Long id, int count) {
return id == null || id <= 0 || count <= 0;
}
private boolean notValidRequest(String address, Long id, int count) {
return address == null || "".equals(address) || id == null || id <= 0 || count <= 0;
}
private TokenResult badRequest() {
return new TokenResult(TokenResultStatus.BAD_REQUEST);
}
}

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.cluster.flow.rule;
import com.alibaba.csp.sentinel.cluster.flow.statistic.ClusterMetricStatistics;
import com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.CurrentConcurrencyManager;
import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterMetric;
import com.alibaba.csp.sentinel.cluster.server.ServerConstants;
import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionManager;
import com.alibaba.csp.sentinel.cluster.server.util.ClusterRuleUtil;
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.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.ClusterFlowConfig;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleUtil;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.csp.sentinel.util.function.Function;
import com.alibaba.csp.sentinel.util.function.Predicate;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* Manager for cluster flow rules.
*
* @author Eric Zhao
* @since 1.4.0
*/
public final class ClusterFlowRuleManager {
/**
* The default cluster flow rule property supplier that creates a new dynamic property
* for a specific namespace to do rule management manually.
*/
public static final Function<String, SentinelProperty<List<FlowRule>>> DEFAULT_PROPERTY_SUPPLIER =
new Function<String, SentinelProperty<List<FlowRule>>>() {
@Override
public SentinelProperty<List<FlowRule>> apply(String namespace) {
return new DynamicSentinelProperty<>();
}
};
/**
* (flowId, clusterRule)
*/
private static final Map<Long, FlowRule> FLOW_RULES = new ConcurrentHashMap<>();
/**
* (namespace, [flowId...])
*/
private static final Map<String, Set<Long>> NAMESPACE_FLOW_ID_MAP = new ConcurrentHashMap<>();
/**
* <p>This map (flowId, namespace) is used for getting connected count
* when checking a specific rule in {@code ruleId}:</p>
*
* <pre>
* ruleId -> namespace -> connection group -> connected count
* </pre>
*/
private static final Map<Long, String> FLOW_NAMESPACE_MAP = new ConcurrentHashMap<>();
/**
* (namespace, property-listener wrapper)
*/
private static final Map<String, NamespaceFlowProperty<FlowRule>> PROPERTY_MAP = new ConcurrentHashMap<>();
/**
* Cluster flow rule property supplier for a specific namespace.
*/
private static volatile Function<String, SentinelProperty<List<FlowRule>>> propertySupplier
= DEFAULT_PROPERTY_SUPPLIER;
private static final Object UPDATE_LOCK = new Object();
static {
initDefaultProperty();
}
private static void initDefaultProperty() {
// The server should always support default namespace,
// so register a default property for default namespace.
SentinelProperty<List<FlowRule>> defaultProperty = new DynamicSentinelProperty<>();
String defaultNamespace = ServerConstants.DEFAULT_NAMESPACE;
registerPropertyInternal(defaultNamespace, defaultProperty);
}
public static void setPropertySupplier(Function<String, SentinelProperty<List<FlowRule>>> propertySupplier) {
AssertUtil.notNull(propertySupplier, "flow rule property supplier cannot be null");
ClusterFlowRuleManager.propertySupplier = propertySupplier;
}
/**
* Listen to the {@link SentinelProperty} for cluster {@link FlowRule}s.
* The property is the source of cluster {@link FlowRule}s for a specific namespace.
*
* @param namespace namespace to register
*/
public static void register2Property(String namespace) {
AssertUtil.notEmpty(namespace, "namespace cannot be empty");
if (propertySupplier == null) {
RecordLog.warn(
"[ClusterFlowRuleManager] Cluster flow property supplier is absent, cannot register property");
return;
}
SentinelProperty<List<FlowRule>> property = propertySupplier.apply(namespace);
if (property == null) {
RecordLog.warn(
"[ClusterFlowRuleManager] Wrong created property from cluster flow property supplier, ignoring");
return;
}
synchronized (UPDATE_LOCK) {
RecordLog.info("[ClusterFlowRuleManager] Registering new property to cluster flow rule manager"
+ " for namespace <{}>", namespace);
registerPropertyInternal(namespace, property);
}
}
/**
* Listen to the {@link SentinelProperty} for cluster {@link FlowRule}s if current property for namespace is absent.
* The property is the source of cluster {@link FlowRule}s for a specific namespace.
*
* @param namespace namespace to register
*/
public static void registerPropertyIfAbsent(String namespace) {
AssertUtil.notEmpty(namespace, "namespace cannot be empty");
if (!PROPERTY_MAP.containsKey(namespace)) {
synchronized (UPDATE_LOCK) {
if (!PROPERTY_MAP.containsKey(namespace)) {
register2Property(namespace);
}
}
}
}
private static void registerPropertyInternal(/*@NonNull*/ String namespace, /*@Valid*/
SentinelProperty<List<FlowRule>> property) {
NamespaceFlowProperty<FlowRule> oldProperty = PROPERTY_MAP.get(namespace);
if (oldProperty != null) {
oldProperty.getProperty().removeListener(oldProperty.getListener());
}
PropertyListener<List<FlowRule>> listener = new FlowRulePropertyListener(namespace);
property.addListener(listener);
PROPERTY_MAP.put(namespace, new NamespaceFlowProperty<>(namespace, property, listener));
Set<Long> flowIdSet = NAMESPACE_FLOW_ID_MAP.get(namespace);
if (flowIdSet == null) {
resetNamespaceFlowIdMapFor(namespace);
}
}
/**
* Remove cluster flow rule property for a specific namespace.
*
* @param namespace valid namespace
*/
public static void removeProperty(String namespace) {
AssertUtil.notEmpty(namespace, "namespace cannot be empty");
synchronized (UPDATE_LOCK) {
NamespaceFlowProperty<FlowRule> property = PROPERTY_MAP.get(namespace);
if (property != null) {
property.getProperty().removeListener(property.getListener());
PROPERTY_MAP.remove(namespace);
}
RecordLog.info("[ClusterFlowRuleManager] Removing property from cluster flow rule manager"
+ " for namespace <{}>", namespace);
}
}
private static void removePropertyListeners() {
for (NamespaceFlowProperty<FlowRule> property : PROPERTY_MAP.values()) {
property.getProperty().removeListener(property.getListener());
}
}
private static void restorePropertyListeners() {
for (NamespaceFlowProperty<FlowRule> p : PROPERTY_MAP.values()) {
p.getProperty().removeListener(p.getListener());
p.getProperty().addListener(p.getListener());
}
}
/**
* Get flow rule by rule ID.
*
* @param id rule ID
* @return flow rule
*/
public static FlowRule getFlowRuleById(Long id) {
if (!ClusterRuleUtil.validId(id)) {
return null;
}
return FLOW_RULES.get(id);
}
public static Set<Long> getFlowIdSet(String namespace) {
if (StringUtil.isEmpty(namespace)) {
return new HashSet<>();
}
Set<Long> set = NAMESPACE_FLOW_ID_MAP.get(namespace);
if (set == null) {
return new HashSet<>();
}
return new HashSet<>(set);
}
public static List<FlowRule> getAllFlowRules() {
return new ArrayList<>(FLOW_RULES.values());
}
/**
* Get all cluster flow rules within a specific namespace.
*
* @param namespace valid namespace
* @return cluster flow rules within the provided namespace
*/
public static List<FlowRule> getFlowRules(String namespace) {
if (StringUtil.isEmpty(namespace)) {
return new ArrayList<>();
}
List<FlowRule> rules = new ArrayList<>();
Set<Long> flowIdSet = NAMESPACE_FLOW_ID_MAP.get(namespace);
if (flowIdSet == null || flowIdSet.isEmpty()) {
return rules;
}
for (Long flowId : flowIdSet) {
FlowRule rule = FLOW_RULES.get(flowId);
if (rule != null) {
rules.add(rule);
}
}
return rules;
}
/**
* Load flow rules for a specific namespace. The former rules of the namespace will be replaced.
*
* @param namespace a valid namespace
* @param rules rule list
*/
public static void loadRules(String namespace, List<FlowRule> rules) {
AssertUtil.notEmpty(namespace, "namespace cannot be empty");
NamespaceFlowProperty<FlowRule> property = PROPERTY_MAP.get(namespace);
if (property != null) {
property.getProperty().updateValue(rules);
}
}
private static void resetNamespaceFlowIdMapFor(/*@Valid*/ String namespace) {
NAMESPACE_FLOW_ID_MAP.put(namespace, new HashSet<Long>());
}
/**
* Clear all rules of the provided namespace and reset map.
*
* @param namespace valid namespace
*/
private static void clearAndResetRulesFor(/*@Valid*/ String namespace) {
Set<Long> flowIdSet = NAMESPACE_FLOW_ID_MAP.get(namespace);
if (flowIdSet != null && !flowIdSet.isEmpty()) {
for (Long flowId : flowIdSet) {
FLOW_RULES.remove(flowId);
FLOW_NAMESPACE_MAP.remove(flowId);
if (CurrentConcurrencyManager.containsFlowId(flowId)) {
CurrentConcurrencyManager.remove(flowId);
}
}
flowIdSet.clear();
} else {
resetNamespaceFlowIdMapFor(namespace);
}
}
private static void clearAndResetRulesConditional(/*@Valid*/ String namespace, Predicate<Long> predicate) {
Set<Long> oldIdSet = NAMESPACE_FLOW_ID_MAP.get(namespace);
if (oldIdSet != null && !oldIdSet.isEmpty()) {
for (Long flowId : oldIdSet) {
if (predicate.test(flowId)) {
FLOW_RULES.remove(flowId);
FLOW_NAMESPACE_MAP.remove(flowId);
ClusterMetricStatistics.removeMetric(flowId);
if (CurrentConcurrencyManager.containsFlowId(flowId)) {
CurrentConcurrencyManager.remove(flowId);
}
}
}
oldIdSet.clear();
}
}
/**
* Get connected count for associated namespace of given {@code flowId}.
*
* @param flowId unique flow ID
* @return connected count
*/
public static int getConnectedCount(long flowId) {
if (flowId <= 0) {
return 0;
}
String namespace = FLOW_NAMESPACE_MAP.get(flowId);
if (namespace == null) {
return 0;
}
return ConnectionManager.getConnectedCount(namespace);
}
public static String getNamespace(long flowId) {
return FLOW_NAMESPACE_MAP.get(flowId);
}
private static void applyClusterFlowRule(List<FlowRule> list, /*@Valid*/ String namespace) {
if (list == null || list.isEmpty()) {
clearAndResetRulesFor(namespace);
return;
}
final ConcurrentHashMap<Long, FlowRule> ruleMap = new ConcurrentHashMap<>();
Set<Long> flowIdSet = new HashSet<>();
for (FlowRule rule : list) {
if (!rule.isClusterMode()) {
continue;
}
if (!FlowRuleUtil.isValidRule(rule)) {
RecordLog.warn(
"[ClusterFlowRuleManager] Ignoring invalid flow rule when loading new flow rules: " + rule);
continue;
}
if (StringUtil.isBlank(rule.getLimitApp())) {
rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT);
}
// Flow id should not be null after filtered.
ClusterFlowConfig clusterConfig = rule.getClusterConfig();
Long flowId = clusterConfig.getFlowId();
if (flowId == null) {
continue;
}
ruleMap.put(flowId, rule);
FLOW_NAMESPACE_MAP.put(flowId, namespace);
flowIdSet.add(flowId);
if (!CurrentConcurrencyManager.containsFlowId(flowId)) {
CurrentConcurrencyManager.put(flowId, 0);
}
// Prepare cluster metric from valid flow ID.
ClusterMetricStatistics.putMetricIfAbsent(flowId,
new ClusterMetric(clusterConfig.getSampleCount(), clusterConfig.getWindowIntervalMs()));
}
// Cleanup unused cluster metrics.
clearAndResetRulesConditional(namespace, new Predicate<Long>() {
@Override
public boolean test(Long flowId) {
return !ruleMap.containsKey(flowId);
}
});
FLOW_RULES.putAll(ruleMap);
NAMESPACE_FLOW_ID_MAP.put(namespace, flowIdSet);
}
private static final class FlowRulePropertyListener implements PropertyListener<List<FlowRule>> {
private final String namespace;
public FlowRulePropertyListener(String namespace) {
this.namespace = namespace;
}
@Override
public synchronized void configUpdate(List<FlowRule> conf) {
applyClusterFlowRule(conf, namespace);
RecordLog.info("[ClusterFlowRuleManager] Cluster flow rules received for namespace <{}>: {}",
namespace, FLOW_RULES);
}
@Override
public synchronized void configLoad(List<FlowRule> conf) {
applyClusterFlowRule(conf, namespace);
RecordLog.info("[ClusterFlowRuleManager] Cluster flow rules loaded for namespace <{}>: {}",
namespace, FLOW_RULES);
}
}
private ClusterFlowRuleManager() {
}
}

View File

@@ -0,0 +1,371 @@
/*
* 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.flow.rule;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import com.alibaba.csp.sentinel.cluster.flow.statistic.ClusterParamMetricStatistics;
import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterParamMetric;
import com.alibaba.csp.sentinel.cluster.server.ServerConstants;
import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionManager;
import com.alibaba.csp.sentinel.cluster.server.util.ClusterRuleUtil;
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.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowClusterConfig;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleUtil;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.csp.sentinel.util.function.Function;
import com.alibaba.csp.sentinel.util.function.Predicate;
/**
* Manager for cluster parameter flow rules.
*
* @author Eric Zhao
* @since 1.4.0
*/
public final class ClusterParamFlowRuleManager {
/**
* The default cluster parameter flow rule property supplier that creates a new
* dynamic property for a specific namespace to manually do rule management.
*/
public static final Function<String, SentinelProperty<List<ParamFlowRule>>> DEFAULT_PROPERTY_SUPPLIER =
new Function<String, SentinelProperty<List<ParamFlowRule>>>() {
@Override
public SentinelProperty<List<ParamFlowRule>> apply(String namespace) {
return new DynamicSentinelProperty<>();
}
};
/**
* (id, clusterParamRule)
*/
private static final Map<Long, ParamFlowRule> PARAM_RULES = new ConcurrentHashMap<>();
/**
* (namespace, [flowId...])
*/
private static final Map<String, Set<Long>> NAMESPACE_FLOW_ID_MAP = new ConcurrentHashMap<>();
/**
* (flowId, namespace)
*/
private static final Map<Long, String> FLOW_NAMESPACE_MAP = new ConcurrentHashMap<>();
/**
* (namespace, property-listener wrapper)
*/
private static final Map<String, NamespaceFlowProperty<ParamFlowRule>> PROPERTY_MAP = new ConcurrentHashMap<>();
/**
* Cluster parameter flow rule property supplier for a specific namespace.
*/
private static volatile Function<String, SentinelProperty<List<ParamFlowRule>>> propertySupplier
= DEFAULT_PROPERTY_SUPPLIER;
private static final Object UPDATE_LOCK = new Object();
static {
initDefaultProperty();
}
private static void initDefaultProperty() {
SentinelProperty<List<ParamFlowRule>> defaultProperty = new DynamicSentinelProperty<>();
String defaultNamespace = ServerConstants.DEFAULT_NAMESPACE;
registerPropertyInternal(defaultNamespace, defaultProperty);
}
public static void setPropertySupplier(
Function<String, SentinelProperty<List<ParamFlowRule>>> propertySupplier) {
ClusterParamFlowRuleManager.propertySupplier = propertySupplier;
}
public static String getNamespace(long flowId) {
return FLOW_NAMESPACE_MAP.get(flowId);
}
/**
* Listen to the {@link SentinelProperty} for cluster {@link ParamFlowRule}s.
* The property is the source of cluster {@link ParamFlowRule}s for a specific namespace.
*
* @param namespace namespace to register
*/
public static void register2Property(String namespace) {
AssertUtil.notEmpty(namespace, "namespace cannot be empty");
if (propertySupplier == null) {
RecordLog.warn(
"[ClusterParamFlowRuleManager] Cluster param rule property supplier is absent, cannot register "
+ "property");
return;
}
SentinelProperty<List<ParamFlowRule>> property = propertySupplier.apply(namespace);
if (property == null) {
RecordLog.warn(
"[ClusterParamFlowRuleManager] Wrong created property from cluster param rule property supplier, "
+ "ignoring");
return;
}
synchronized (UPDATE_LOCK) {
RecordLog.info("[ClusterParamFlowRuleManager] Registering new property to cluster param rule manager"
+ " for namespace <{}>", namespace);
registerPropertyInternal(namespace, property);
}
}
public static void registerPropertyIfAbsent(String namespace) {
AssertUtil.notEmpty(namespace, "namespace cannot be empty");
if (!PROPERTY_MAP.containsKey(namespace)) {
synchronized (UPDATE_LOCK) {
if (!PROPERTY_MAP.containsKey(namespace)) {
register2Property(namespace);
}
}
}
}
private static void registerPropertyInternal(/*@NonNull*/ String namespace, /*@Valid*/
SentinelProperty<List<ParamFlowRule>> property) {
NamespaceFlowProperty<ParamFlowRule> oldProperty = PROPERTY_MAP.get(namespace);
if (oldProperty != null) {
oldProperty.getProperty().removeListener(oldProperty.getListener());
}
PropertyListener<List<ParamFlowRule>> listener = new ParamRulePropertyListener(namespace);
property.addListener(listener);
PROPERTY_MAP.put(namespace, new NamespaceFlowProperty<>(namespace, property, listener));
Set<Long> flowIdSet = NAMESPACE_FLOW_ID_MAP.get(namespace);
if (flowIdSet == null) {
resetNamespaceFlowIdMapFor(namespace);
}
}
public static void removeProperty(String namespace) {
AssertUtil.notEmpty(namespace, "namespace cannot be empty");
synchronized (UPDATE_LOCK) {
NamespaceFlowProperty<ParamFlowRule> property = PROPERTY_MAP.get(namespace);
if (property != null) {
property.getProperty().removeListener(property.getListener());
PROPERTY_MAP.remove(namespace);
}
RecordLog.info("[ClusterParamFlowRuleManager] Removing property from cluster flow rule manager"
+ " for namespace <{}>", namespace);
}
}
private static void removePropertyListeners() {
for (NamespaceFlowProperty<ParamFlowRule> property : PROPERTY_MAP.values()) {
property.getProperty().removeListener(property.getListener());
}
}
private static void restorePropertyListeners() {
for (NamespaceFlowProperty<ParamFlowRule> p : PROPERTY_MAP.values()) {
p.getProperty().removeListener(p.getListener());
p.getProperty().addListener(p.getListener());
}
}
private static void resetNamespaceFlowIdMapFor(/*@Valid*/ String namespace) {
NAMESPACE_FLOW_ID_MAP.put(namespace, new HashSet<Long>());
}
private static void clearAndResetRulesFor(/*@Valid*/ String namespace) {
Set<Long> flowIdSet = NAMESPACE_FLOW_ID_MAP.get(namespace);
if (flowIdSet != null && !flowIdSet.isEmpty()) {
for (Long flowId : flowIdSet) {
PARAM_RULES.remove(flowId);
FLOW_NAMESPACE_MAP.remove(flowId);
}
flowIdSet.clear();
} else {
resetNamespaceFlowIdMapFor(namespace);
}
}
private static void clearAndResetRulesConditional(/*@Valid*/ String namespace, Predicate<Long> predicate) {
Set<Long> oldIdSet = NAMESPACE_FLOW_ID_MAP.get(namespace);
if (oldIdSet != null && !oldIdSet.isEmpty()) {
for (Long flowId : oldIdSet) {
if (predicate.test(flowId)) {
PARAM_RULES.remove(flowId);
FLOW_NAMESPACE_MAP.remove(flowId);
ClusterParamMetricStatistics.removeMetric(flowId);
}
}
oldIdSet.clear();
}
}
public static ParamFlowRule getParamRuleById(Long id) {
if (!ClusterRuleUtil.validId(id)) {
return null;
}
return PARAM_RULES.get(id);
}
public static Set<Long> getFlowIdSet(String namespace) {
if (StringUtil.isEmpty(namespace)) {
return new HashSet<>();
}
Set<Long> set = NAMESPACE_FLOW_ID_MAP.get(namespace);
if (set == null) {
return new HashSet<>();
}
return new HashSet<>(set);
}
public static List<ParamFlowRule> getAllParamRules() {
return new ArrayList<>(PARAM_RULES.values());
}
/**
* Get all cluster parameter flow rules within a specific namespace.
*
* @param namespace a valid namespace
* @return cluster parameter flow rules within the provided namespace
*/
public static List<ParamFlowRule> getParamRules(String namespace) {
if (StringUtil.isEmpty(namespace)) {
return new ArrayList<>();
}
List<ParamFlowRule> rules = new ArrayList<>();
Set<Long> flowIdSet = NAMESPACE_FLOW_ID_MAP.get(namespace);
if (flowIdSet == null || flowIdSet.isEmpty()) {
return rules;
}
for (Long flowId : flowIdSet) {
ParamFlowRule rule = PARAM_RULES.get(flowId);
if (rule != null) {
rules.add(rule);
}
}
return rules;
}
/**
* Load parameter flow rules for a specific namespace. The former rules of the namespace will be replaced.
*
* @param namespace a valid namespace
* @param rules rule list
*/
public static void loadRules(String namespace, List<ParamFlowRule> rules) {
AssertUtil.notEmpty(namespace, "namespace cannot be empty");
NamespaceFlowProperty<ParamFlowRule> property = PROPERTY_MAP.get(namespace);
if (property != null) {
property.getProperty().updateValue(rules);
}
}
/**
* Get connected count for associated namespace of given {@code flowId}.
*
* @param flowId existing rule ID
* @return connected count
*/
public static int getConnectedCount(long flowId) {
if (flowId <= 0) {
return 0;
}
String namespace = FLOW_NAMESPACE_MAP.get(flowId);
if (namespace == null) {
return 0;
}
return ConnectionManager.getConnectedCount(namespace);
}
private static class ParamRulePropertyListener implements PropertyListener<List<ParamFlowRule>> {
private final String namespace;
public ParamRulePropertyListener(String namespace) {
this.namespace = namespace;
}
@Override
public void configLoad(List<ParamFlowRule> conf) {
applyClusterParamRules(conf, namespace);
RecordLog.info("[ClusterParamFlowRuleManager] Cluster parameter rules loaded for namespace <{}>: {}",
namespace, PARAM_RULES);
}
@Override
public void configUpdate(List<ParamFlowRule> conf) {
applyClusterParamRules(conf, namespace);
RecordLog.info("[ClusterParamFlowRuleManager] Cluster parameter rules received for namespace <{}>: {}",
namespace, PARAM_RULES);
}
}
private static void applyClusterParamRules(List<ParamFlowRule> list, /*@Valid*/ String namespace) {
if (list == null || list.isEmpty()) {
clearAndResetRulesFor(namespace);
return;
}
final ConcurrentHashMap<Long, ParamFlowRule> ruleMap = new ConcurrentHashMap<>();
Set<Long> flowIdSet = new HashSet<>();
for (ParamFlowRule rule : list) {
if (!rule.isClusterMode()) {
continue;
}
if (!ParamFlowRuleUtil.isValidRule(rule)) {
RecordLog.warn(
"[ClusterParamFlowRuleManager] Ignoring invalid param flow rule when loading new flow rules: "
+ rule);
continue;
}
if (StringUtil.isBlank(rule.getLimitApp())) {
rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT);
}
ParamFlowRuleUtil.fillExceptionFlowItems(rule);
ParamFlowClusterConfig clusterConfig = rule.getClusterConfig();
// Flow id should not be null after filtered.
Long flowId = clusterConfig.getFlowId();
if (flowId == null) {
continue;
}
ruleMap.put(flowId, rule);
FLOW_NAMESPACE_MAP.put(flowId, namespace);
flowIdSet.add(flowId);
// Prepare cluster parameter metric from valid rule ID.
ClusterParamMetricStatistics.putMetricIfAbsent(flowId,
new ClusterParamMetric(clusterConfig.getSampleCount(), clusterConfig.getWindowIntervalMs()));
}
// Cleanup unused cluster parameter metrics.
clearAndResetRulesConditional(namespace, new Predicate<Long>() {
@Override
public boolean test(Long flowId) {
return !ruleMap.containsKey(flowId);
}
});
PARAM_RULES.putAll(ruleMap);
NAMESPACE_FLOW_ID_MAP.put(namespace, flowIdSet);
}
private ClusterParamFlowRuleManager() {}
}

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.flow.rule;
import java.util.List;
import com.alibaba.csp.sentinel.property.PropertyListener;
import com.alibaba.csp.sentinel.property.SentinelProperty;
/**
* A property wrapper for list of rules of a given namespace.
* This is useful for auto-management of the property and listener.
*
* @param <T> type of the rule
* @author Eric Zhao
* @since 1.4.0
*/
class NamespaceFlowProperty<T> {
private final String namespace;
private final SentinelProperty<List<T>> property;
private final PropertyListener<List<T>> listener;
public NamespaceFlowProperty(String namespace,
SentinelProperty<List<T>> property,
PropertyListener<List<T>> listener) {
this.namespace = namespace;
this.property = property;
this.listener = listener;
}
public SentinelProperty<List<T>> getProperty() {
return property;
}
public String getNamespace() {
return namespace;
}
public PropertyListener<List<T>> getListener() {
return listener;
}
}

View File

@@ -0,0 +1,112 @@
/*
* 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.flow.statistic;
import java.util.Map;
/**
* @author Eric Zhao
* @since 1.4.1
*/
public class ClusterMetricNode {
private long timestamp;
private String resourceName;
private long flowId;
private double passQps;
private double blockQps;
private long rt;
private Map<Object, Double> topParams;
public long getTimestamp() {
return timestamp;
}
public ClusterMetricNode setTimestamp(long timestamp) {
this.timestamp = timestamp;
return this;
}
public String getResourceName() {
return resourceName;
}
public ClusterMetricNode setResourceName(String resourceName) {
this.resourceName = resourceName;
return this;
}
public long getFlowId() {
return flowId;
}
public ClusterMetricNode setFlowId(long flowId) {
this.flowId = flowId;
return this;
}
public double getPassQps() {
return passQps;
}
public ClusterMetricNode setPassQps(double passQps) {
this.passQps = passQps;
return this;
}
public double getBlockQps() {
return blockQps;
}
public ClusterMetricNode setBlockQps(double blockQps) {
this.blockQps = blockQps;
return this;
}
public long getRt() {
return rt;
}
public ClusterMetricNode setRt(long rt) {
this.rt = rt;
return this;
}
public Map<Object, Double> getTopParams() {
return topParams;
}
public ClusterMetricNode setTopParams(Map<Object, Double> topParams) {
this.topParams = topParams;
return this;
}
@Override
public String toString() {
return "ClusterMetricNode{" +
"timestamp=" + timestamp +
", resourceName='" + resourceName + '\'' +
", flowId=" + flowId +
", passQps=" + passQps +
", blockQps=" + blockQps +
", rt=" + rt +
", topParams=" + topParams +
'}';
}
}

View File

@@ -0,0 +1,106 @@
/*
* 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.flow.statistic;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager;
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterParamFlowRuleManager;
import com.alibaba.csp.sentinel.cluster.flow.statistic.data.ClusterFlowEvent;
import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterMetric;
import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterParamMetric;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
import com.alibaba.csp.sentinel.util.TimeUtil;
/**
* @author Eric Zhao
* @since 1.4.1
*/
public class ClusterMetricNodeGenerator {
public static Map<String, List<ClusterMetricNode>> generateCurrentNodeMap(String namespace) {
Map<String, List<ClusterMetricNode>> map = new HashMap<>();
Set<Long> flowIds = ClusterFlowRuleManager.getFlowIdSet(namespace);
Set<Long> paramFlowIds = ClusterParamFlowRuleManager.getFlowIdSet(namespace);
for (Long id : flowIds) {
ClusterMetricNode node = flowToMetricNode(id);
if (node == null) {
continue;
}
putToMap(map, node);
}
for (Long id : paramFlowIds) {
ClusterMetricNode node = paramToMetricNode(id);
if (node == null) {
continue;
}
putToMap(map, node);
}
return map;
}
private static void putToMap(Map<String, List<ClusterMetricNode>> map, ClusterMetricNode node) {
List<ClusterMetricNode> nodeList = map.get(node.getResourceName());
if (nodeList == null) {
nodeList = new ArrayList<>();
map.put(node.getResourceName(), nodeList);
}
nodeList.add(node);
}
public static ClusterMetricNode flowToMetricNode(long flowId) {
FlowRule rule = ClusterFlowRuleManager.getFlowRuleById(flowId);
if (rule == null) {
return null;
}
ClusterMetric metric = ClusterMetricStatistics.getMetric(flowId);
if (metric == null) {
return new ClusterMetricNode().setFlowId(flowId)
.setResourceName(rule.getResource());
}
return new ClusterMetricNode()
.setFlowId(flowId)
.setResourceName(rule.getResource())
.setBlockQps(metric.getAvg(ClusterFlowEvent.BLOCK))
.setPassQps(metric.getAvg(ClusterFlowEvent.PASS))
.setTimestamp(TimeUtil.currentTimeMillis());
}
public static ClusterMetricNode paramToMetricNode(long flowId) {
ParamFlowRule rule = ClusterParamFlowRuleManager.getParamRuleById(flowId);
if (rule == null) {
return null;
}
ClusterParamMetric metric = ClusterParamMetricStatistics.getMetric(flowId);
if (metric == null) {
return new ClusterMetricNode().setFlowId(flowId)
.setResourceName(rule.getResource())
.setTimestamp(TimeUtil.currentTimeMillis())
.setTopParams(new HashMap<Object, Double>(0));
}
return new ClusterMetricNode()
.setFlowId(flowId)
.setResourceName(rule.getResource())
.setTimestamp(TimeUtil.currentTimeMillis())
.setTopParams(metric.getTopValues(5));
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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.flow.statistic;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterMetric;
import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager;
import com.alibaba.csp.sentinel.util.AssertUtil;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public final class ClusterMetricStatistics {
private static final Map<Long, ClusterMetric> METRIC_MAP = new ConcurrentHashMap<>();
public static void clear() {
METRIC_MAP.clear();
}
public static void putMetric(long id, ClusterMetric metric) {
AssertUtil.notNull(metric, "Cluster metric cannot be null");
METRIC_MAP.put(id, metric);
}
public static boolean putMetricIfAbsent(long id, ClusterMetric metric) {
AssertUtil.notNull(metric, "Cluster metric cannot be null");
if (METRIC_MAP.containsKey(id)) {
return false;
}
METRIC_MAP.put(id, metric);
return true;
}
public static void removeMetric(long id) {
METRIC_MAP.remove(id);
}
public static ClusterMetric getMetric(long id) {
return METRIC_MAP.get(id);
}
public static void resetFlowMetrics() {
Set<Long> keySet = METRIC_MAP.keySet();
for (Long id : keySet) {
METRIC_MAP.put(id, new ClusterMetric(ClusterServerConfigManager.getSampleCount(),
ClusterServerConfigManager.getIntervalMs()));
}
}
private ClusterMetricStatistics() {}
}

View File

@@ -0,0 +1,69 @@
/*
* 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.flow.statistic;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterParamMetric;
import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager;
import com.alibaba.csp.sentinel.util.AssertUtil;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public final class ClusterParamMetricStatistics {
private static final Map<Long, ClusterParamMetric> METRIC_MAP = new ConcurrentHashMap<>();
public static void clear() {
METRIC_MAP.clear();
}
public static void putMetric(long id, ClusterParamMetric metric) {
AssertUtil.notNull(metric, "metric cannot be null");
METRIC_MAP.put(id, metric);
}
public static boolean putMetricIfAbsent(long id, ClusterParamMetric metric) {
AssertUtil.notNull(metric, "metric cannot be null");
if (METRIC_MAP.containsKey(id)) {
return false;
}
METRIC_MAP.put(id, metric);
return true;
}
public static void removeMetric(long id) {
METRIC_MAP.remove(id);
}
public static ClusterParamMetric getMetric(long id) {
return METRIC_MAP.get(id);
}
public static void resetFlowMetrics() {
Set<Long> keySet = METRIC_MAP.keySet();
for (Long id : keySet) {
METRIC_MAP.put(id, new ClusterParamMetric(ClusterServerConfigManager.getSampleCount(),
ClusterServerConfigManager.getIntervalMs()));
}
}
private ClusterParamMetricStatistics() {}
}

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.
*/
package com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent;
import com.alibaba.csp.sentinel.cluster.flow.ConcurrentClusterFlowChecker;
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager;
import com.alibaba.csp.sentinel.cluster.server.log.ClusterServerStatLogUtil;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import java.util.Set;
/**
* @author yunfeiyanggzq
*/
public class ClusterConcurrentCheckerLogListener implements Runnable {
@Override
public void run() {
try {
collectInformation();
} catch (Exception e) {
RecordLog.warn("[ClusterConcurrentCheckerLogListener] Failed to record concurrent flow control regularly", e);
}
}
private void collectInformation() {
Set<Long> keySet = CurrentConcurrencyManager.getConcurrencyMapKeySet();
for (long flowId : keySet) {
FlowRule rule = ClusterFlowRuleManager.getFlowRuleById(flowId);
if (rule == null || CurrentConcurrencyManager.get(flowId).get() == 0) {
continue;
}
double concurrencyLevel = ConcurrentClusterFlowChecker.calcGlobalThreshold(rule);
String resource = rule.getResource();
ClusterServerStatLogUtil.log(String.format("concurrent|resource:%s|flowId:%dl|concurrencyLevel:%fl|currentConcurrency", resource, flowId,concurrencyLevel),CurrentConcurrencyManager.get(flowId).get());
}
if (TokenCacheNodeManager.getSize() != 0){
ClusterServerStatLogUtil.log("flow|totalTokenSize", TokenCacheNodeManager.getSize());
}
}
}

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.flow.statistic.concurrent;
import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* We use a ConcurrentHashMap<long, AtomicInteger> type structure to store nowCalls corresponding to
* rules, where the key is flowId and the value is nowCalls. Because nowCalls may be accessed and
* modified by multiple threads, we consider to design it as an AtomicInteger class . Each newly
* created rule will add a nowCalls object to this map. If the concurrency corresponding to a rule changes,
* we will update the corresponding nowCalls in real time. Each request to obtain a token will increase the nowCalls;
* and the request to release the token will reduce the nowCalls.
*
* @author yunfeiyanggzq
*/
public final class CurrentConcurrencyManager {
/**
* use ConcurrentHashMap to store the nowCalls of rules.
*/
private static final ConcurrentHashMap<Long, AtomicInteger> NOW_CALLS_MAP = new ConcurrentHashMap<Long, AtomicInteger>();
@SuppressWarnings("PMD.ThreadPoolCreationRule")
private static final ScheduledExecutorService SCHEDULER = Executors.newScheduledThreadPool(1,
new NamedThreadFactory("sentinel-cluster-concurrency-record-task", true));
static {
ClusterConcurrentCheckerLogListener logTask = new ClusterConcurrentCheckerLogListener();
SCHEDULER.scheduleAtFixedRate(logTask, 0, 1, TimeUnit.SECONDS);
}
/**
* add current concurrency.
*/
public static void addConcurrency(Long flowId, Integer acquireCount) {
AtomicInteger nowCalls = NOW_CALLS_MAP.get(flowId);
if (nowCalls == null) {
return;
}
nowCalls.getAndAdd(acquireCount);
}
/**
* get the current concurrency.
*/
public static AtomicInteger get(Long flowId) {
return NOW_CALLS_MAP.get(flowId);
}
/**
* delete the current concurrency.
*/
public static void remove(Long flowId) {
NOW_CALLS_MAP.remove(flowId);
}
/**
* put the current concurrency.
*/
public static void put(Long flowId, Integer nowCalls) {
NOW_CALLS_MAP.put(flowId, new AtomicInteger(nowCalls));
}
/**
* check flow id.
*/
public static boolean containsFlowId(Long flowId) {
return NOW_CALLS_MAP.containsKey(flowId);
}
/**
* get NOW_CALLS_MAP.
*/
public static Set<Long> getConcurrencyMapKeySet() {
return NOW_CALLS_MAP.keySet();
}
}

View File

@@ -0,0 +1,131 @@
/*
* 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.flow.statistic.concurrent;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import java.util.UUID;
/**
* We use TokenCacheNodeManager to store the tokenId, whose the underlying storage structure
* is ConcurrentLinkedHashMap, Its storage node is TokenCacheNode. In order to operate the nowCalls value when
* the expired tokenId is deleted regularly, we need to store the flowId in TokenCacheNode.
*
* @author yunfeiyanggzq
*/
public class TokenCacheNode {
/**
* the TokenId of the token
*/
private Long tokenId;
/**
* the client goes offline detection time
*/
private Long clientTimeout;
/**
* the resource called over time detection time
*/
private Long resourceTimeout;
/**
* the flow rule id corresponding to the token
*/
private Long flowId;
/**
* the number this token occupied
*/
private int acquireCount;
/**
* the address of the client holds the token.
*/
private String clientAddress;
public TokenCacheNode() {
}
public static TokenCacheNode generateTokenCacheNode(FlowRule rule, int acquireCount, String clientAddress) {
TokenCacheNode node = new TokenCacheNode();
// getMostSignificantBits() returns the most significant 64 bits of this UUID's 128 bit value.
// The probability of collision is extremely low.
node.setTokenId(UUID.randomUUID().getMostSignificantBits());
node.setFlowId(rule.getClusterConfig().getFlowId());
node.setClientTimeout(rule.getClusterConfig().getClientOfflineTime());
node.setResourceTimeout(rule.getClusterConfig().getResourceTimeout());
node.setAcquireCount(acquireCount);
node.setClientAddress(clientAddress);
return node;
}
public Long getTokenId() {
return tokenId;
}
public void setTokenId(Long tokenId) {
this.tokenId = tokenId;
}
public Long getClientTimeout() {
return clientTimeout;
}
public void setClientTimeout(Long clientTimeout) {
this.clientTimeout = clientTimeout + System.currentTimeMillis();
}
public Long getResourceTimeout() {
return this.resourceTimeout;
}
public void setResourceTimeout(Long resourceTimeout) {
this.resourceTimeout = resourceTimeout + System.currentTimeMillis();
}
public Long getFlowId() {
return flowId;
}
public void setFlowId(Long flowId) {
this.flowId = flowId;
}
public int getAcquireCount() {
return acquireCount;
}
public void setAcquireCount(int acquireCount) {
this.acquireCount = acquireCount;
}
public String getClientAddress() {
return clientAddress;
}
public void setClientAddress(String clientAddress) {
this.clientAddress = clientAddress;
}
@Override
public String toString() {
return "TokenCacheNode{" +
"tokenId=" + tokenId +
", clientTimeout=" + clientTimeout +
", resourceTimeout=" + resourceTimeout +
", flowId=" + flowId +
", acquireCount=" + acquireCount +
", clientAddress='" + clientAddress + '\'' +
'}';
}
}

View File

@@ -0,0 +1,82 @@
/*
* 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.flow.statistic.concurrent;
import com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.expire.RegularExpireStrategy;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
import com.googlecode.concurrentlinkedhashmap.Weighers;
import java.util.Set;
/**
* @author yunfeiyanggzq
*/
public class TokenCacheNodeManager {
private static ConcurrentLinkedHashMap<Long, TokenCacheNode> TOKEN_CACHE_NODE_MAP;
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
private static final int DEFAULT_CAPACITY = Integer.MAX_VALUE;
static {
prepare(DEFAULT_CONCURRENCY_LEVEL, DEFAULT_CAPACITY);
}
public static void prepare(int concurrencyLevel, int maximumWeightedCapacity) {
AssertUtil.isTrue(concurrencyLevel > 0, "concurrencyLevel must be positive");
AssertUtil.isTrue(maximumWeightedCapacity > 0, "maximumWeightedCapacity must be positive");
TOKEN_CACHE_NODE_MAP = new ConcurrentLinkedHashMap.Builder<Long, TokenCacheNode>()
.concurrencyLevel(concurrencyLevel)
.maximumWeightedCapacity(maximumWeightedCapacity)
.weigher(Weighers.singleton())
.build();
// Start the task of regularly clearing expired keys
RegularExpireStrategy strategy = new RegularExpireStrategy(TOKEN_CACHE_NODE_MAP);
strategy.startClearTaskRegularly();
}
public static TokenCacheNode getTokenCacheNode(long tokenId) {
//use getQuietly to prevent disorder
return TOKEN_CACHE_NODE_MAP.getQuietly(tokenId);
}
public static void putTokenCacheNode(long tokenId, TokenCacheNode cacheNode) {
TOKEN_CACHE_NODE_MAP.put(tokenId, cacheNode);
}
public static boolean isContainsTokenId(long tokenId) {
return TOKEN_CACHE_NODE_MAP.containsKey(tokenId);
}
public static TokenCacheNode removeTokenCacheNode(long tokenId) {
return TOKEN_CACHE_NODE_MAP.remove(tokenId);
}
public static int getSize() {
return TOKEN_CACHE_NODE_MAP.size();
}
public static Set<Long> getCacheKeySet() {
return TOKEN_CACHE_NODE_MAP.keySet();
}
public static boolean validToken(TokenCacheNode cacheNode) {
return cacheNode.getTokenId() != null && cacheNode.getFlowId() != null && cacheNode.getClientTimeout() >= 0 && cacheNode.getResourceTimeout() >= 0;
}
}

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.cluster.flow.statistic.concurrent.expire;
/**
* @author yunfeiyagnggzq
*/
public interface ExpireStrategy {
/**
* clean expired token regularly.
*/
void startClearTaskRegularly();
}

View File

@@ -0,0 +1,137 @@
/*
* 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.flow.statistic.concurrent.expire;
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager;
import com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.CurrentConcurrencyManager;
import com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.TokenCacheNode;
import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionManager;
import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* We need to consider the situation that the token client goes offline
* or the resource call times out. It can be detected by sourceTimeout
* and clientTimeout. The resource calls timeout detection is triggered
* on the token client. If the resource is called over time, the token
* client will request the token server to release token or refresh the
* token. The client offline detection is triggered on the token server.
* If the offline detection time is exceeded, token server will trigger
* the detection token clients status. If the token client is offline,
* token server will delete the corresponding tokenId. If it is not offline,
* token server will continue to save it.
*
* @author yunfeiyanggzq
**/
public class RegularExpireStrategy implements ExpireStrategy {
/**
* The max number of token deleted each time,
* the number of expired key-value pairs deleted each time does not exceed this number
*/
private long executeCount = 1000;
/**
* Length of time for task execution
*/
private long executeDuration = 800;
/**
* Frequency of task execution
*/
private long executeRate = 1000;
/**
* the local cache of tokenId
*/
private ConcurrentLinkedHashMap<Long, TokenCacheNode> localCache;
@SuppressWarnings("PMD.ThreadPoolCreationRule")
private static ScheduledExecutorService executor = Executors.newScheduledThreadPool(1,
new NamedThreadFactory("regular clear expired token thread", true));
public RegularExpireStrategy(ConcurrentLinkedHashMap<Long, TokenCacheNode> localCache) {
AssertUtil.isTrue(localCache != null, " local cache can't be null");
this.localCache = localCache;
}
@Override
public void startClearTaskRegularly() {
executor.scheduleAtFixedRate(new ClearExpiredTokenTask(), 0, executeRate, TimeUnit.MILLISECONDS);
}
private class ClearExpiredTokenTask implements Runnable {
@Override
public void run() {
try {
clearToken();
} catch (Throwable e) {
e.printStackTrace();
RecordLog.warn("[RegularExpireStrategy] undefined throwable during clear token: ", e);
}
}
}
private void clearToken() {
long start = System.currentTimeMillis();
List<Long> keyList = new ArrayList<>(localCache.keySet());
for (int i = 0; i < executeCount && i < keyList.size(); i++) {
// time out execution exit
if (System.currentTimeMillis() - start > executeDuration) {
RecordLog.info("[RegularExpireStrategy] End the process of expired token detection because of execute time is more than executeDuration: {}", executeDuration);
break;
}
Long key = keyList.get(i);
TokenCacheNode node = localCache.get(key);
if (node == null) {
continue;
}
// remove the token whose client is offline and saved for more than clientTimeout
if (!ConnectionManager.isClientOnline(node.getClientAddress()) && node.getClientTimeout() - System.currentTimeMillis() < 0) {
removeToken(key, node);
RecordLog.info("[RegularExpireStrategy] Delete the expired token<{}> because of client offline for ruleId<{}>", node.getTokenId(), node.getFlowId());
continue;
}
// If we find that token's save time is more than 2 times of the client's call resource timeout time,
// the token will be determined to timeout.
long resourceTimeout = ClusterFlowRuleManager.getFlowRuleById(node.getFlowId()).getClusterConfig().getResourceTimeout();
if (System.currentTimeMillis() - node.getResourceTimeout() > resourceTimeout) {
removeToken(key, node);
RecordLog.info("[RegularExpireStrategy] Delete the expired token<{}> because of resource timeout for ruleId<{}>", node.getTokenId(), node.getFlowId());
}
}
}
private void removeToken(long tokenId, TokenCacheNode node) {
if (localCache.remove(tokenId) == null) {
RecordLog.info("[RegularExpireStrategy] Token<{}> is already released for ruleId<{}>", tokenId, node.getFlowId());
return;
}
AtomicInteger nowCalls = CurrentConcurrencyManager.get(node.getFlowId());
if (nowCalls == null) {
return;
}
nowCalls.getAndAdd(node.getAcquireCount() * -1);
}
}

View File

@@ -0,0 +1,52 @@
/*
* 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.flow.statistic.data;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public enum ClusterFlowEvent {
/**
* Normal pass.
*/
PASS,
/**
* Normal block.
*/
BLOCK,
/**
* Token request (from client) passed.
*/
PASS_REQUEST,
/**
* Token request (from client) blocked.
*/
BLOCK_REQUEST,
/**
* Pass (pre-occupy incoming buckets).
*/
OCCUPIED_PASS,
/**
* Block (pre-occupy incoming buckets failed).
*/
OCCUPIED_BLOCK,
/**
* Waiting due to flow shaping or for next bucket tick.
*/
WAITING
}

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.cluster.flow.statistic.data;
import java.util.concurrent.atomic.LongAdder;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public class ClusterMetricBucket {
private final LongAdder[] counters;
public ClusterMetricBucket() {
ClusterFlowEvent[] events = ClusterFlowEvent.values();
this.counters = new LongAdder[events.length];
for (ClusterFlowEvent event : events) {
counters[event.ordinal()] = new LongAdder();
}
}
public void reset() {
for (ClusterFlowEvent event : ClusterFlowEvent.values()) {
counters[event.ordinal()].reset();
}
}
public long get(ClusterFlowEvent event) {
return counters[event.ordinal()].sum();
}
public ClusterMetricBucket add(ClusterFlowEvent event, long count) {
counters[event.ordinal()].add(count);
return this;
}
}

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.cluster.flow.statistic.limit;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager;
import com.alibaba.csp.sentinel.util.AssertUtil;
/**
* @author Eric Zhao
* @since 1.4.1
*/
public final class GlobalRequestLimiter {
private static final Map<String, RequestLimiter> GLOBAL_QPS_LIMITER_MAP = new ConcurrentHashMap<>();
public static void initIfAbsent(String namespace) {
AssertUtil.notEmpty(namespace, "namespace cannot be empty");
if (!GLOBAL_QPS_LIMITER_MAP.containsKey(namespace)) {
GLOBAL_QPS_LIMITER_MAP.put(namespace, new RequestLimiter(ClusterServerConfigManager.getMaxAllowedQps(namespace)));
}
}
public static RequestLimiter getRequestLimiter(String namespace) {
if (namespace == null) {
return null;
}
return GLOBAL_QPS_LIMITER_MAP.get(namespace);
}
public static boolean tryPass(String namespace) {
if (namespace == null) {
return false;
}
RequestLimiter limiter = GLOBAL_QPS_LIMITER_MAP.get(namespace);
if (limiter == null) {
return true;
}
return limiter.tryPass();
}
public static double getCurrentQps(String namespace) {
RequestLimiter limiter = getRequestLimiter(namespace);
if (limiter == null) {
return 0;
}
return limiter.getQps();
}
public static double getMaxAllowedQps(String namespace) {
RequestLimiter limiter = getRequestLimiter(namespace);
if (limiter == null) {
return 0;
}
return limiter.getQpsAllowed();
}
public static void applyMaxQpsChange(double maxAllowedQps) {
AssertUtil.isTrue(maxAllowedQps >= 0, "max allowed QPS should > 0");
for (RequestLimiter limiter : GLOBAL_QPS_LIMITER_MAP.values()) {
if (limiter != null) {
limiter.setQpsAllowed(maxAllowedQps);
}
}
}
private GlobalRequestLimiter() {}
}

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.cluster.flow.statistic.limit;
import java.util.List;
import java.util.concurrent.atomic.LongAdder;
import com.alibaba.csp.sentinel.slots.statistic.base.LeapArray;
import com.alibaba.csp.sentinel.slots.statistic.base.UnaryLeapArray;
import com.alibaba.csp.sentinel.util.AssertUtil;
/**
* @author Eric Zhao
* @since 1.4.1
*/
public class RequestLimiter {
private double qpsAllowed;
private final LeapArray<LongAdder> data;
public RequestLimiter(double qpsAllowed) {
this(new UnaryLeapArray(10, 1000), qpsAllowed);
}
RequestLimiter(LeapArray<LongAdder> data, double qpsAllowed) {
AssertUtil.isTrue(qpsAllowed >= 0, "max allowed QPS should > 0");
this.data = data;
this.qpsAllowed = qpsAllowed;
}
public void increment() {
data.currentWindow().value().increment();
}
public void add(int x) {
data.currentWindow().value().add(x);
}
public long getSum() {
data.currentWindow();
long success = 0;
List<LongAdder> list = data.values();
for (LongAdder window : list) {
success += window.sum();
}
return success;
}
public double getQps() {
return getSum() / data.getIntervalInSecond();
}
public double getQpsAllowed() {
return qpsAllowed;
}
public boolean canPass() {
return getQps() + 1 <= qpsAllowed;
}
public RequestLimiter setQpsAllowed(double qpsAllowed) {
this.qpsAllowed = qpsAllowed;
return this;
}
public boolean tryPass() {
if (canPass()) {
add(1);
return true;
}
return false;
}
}

View File

@@ -0,0 +1,99 @@
/*
* 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.flow.statistic.metric;
import java.util.List;
import com.alibaba.csp.sentinel.cluster.flow.statistic.data.ClusterFlowEvent;
import com.alibaba.csp.sentinel.cluster.flow.statistic.data.ClusterMetricBucket;
import com.alibaba.csp.sentinel.util.AssertUtil;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public class ClusterMetric {
private final ClusterMetricLeapArray metric;
public ClusterMetric(int sampleCount, int intervalInMs) {
AssertUtil.isTrue(sampleCount > 0, "sampleCount should be positive");
AssertUtil.isTrue(intervalInMs > 0, "interval should be positive");
AssertUtil.isTrue(intervalInMs % sampleCount == 0, "time span needs to be evenly divided");
this.metric = new ClusterMetricLeapArray(sampleCount, intervalInMs);
}
public void add(ClusterFlowEvent event, long count) {
metric.currentWindow().value().add(event, count);
}
public long getCurrentCount(ClusterFlowEvent event) {
return metric.currentWindow().value().get(event);
}
/**
* Get total sum for provided event in {@code intervalInSec}.
*
* @param event event to calculate
* @return total sum for event
*/
public long getSum(ClusterFlowEvent event) {
metric.currentWindow();
long sum = 0;
List<ClusterMetricBucket> buckets = metric.values();
for (ClusterMetricBucket bucket : buckets) {
sum += bucket.get(event);
}
return sum;
}
/**
* Get average count for provided event per second.
*
* @param event event to calculate
* @return average count per second for event
*/
public double getAvg(ClusterFlowEvent event) {
return getSum(event) / metric.getIntervalInSecond();
}
/**
* Try to pre-occupy upcoming buckets.
*
* @return time to wait for next bucket (in ms); 0 if cannot occupy next buckets
*/
public int tryOccupyNext(ClusterFlowEvent event, int acquireCount, double threshold) {
double latestQps = getAvg(ClusterFlowEvent.PASS);
if (!canOccupy(event, acquireCount, latestQps, threshold)) {
return 0;
}
metric.addOccupyPass(acquireCount);
add(ClusterFlowEvent.WAITING, acquireCount);
return 1000 / metric.getSampleCount();
}
private boolean canOccupy(ClusterFlowEvent event, int acquireCount, double latestQps, double threshold) {
long headPass = metric.getFirstCountOfWindow(event);
long occupiedCount = metric.getOccupiedCount(event);
// bucket to occupy (= incoming bucket)
// ↓
// | head bucket | | | | current bucket |
// +-------------+----+----+----+----------- ----+
// (headPass)
return latestQps + (acquireCount + occupiedCount) - headPass <= threshold;
}
}

View File

@@ -0,0 +1,93 @@
/*
* 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.flow.statistic.metric;
import java.util.concurrent.atomic.LongAdder;
import com.alibaba.csp.sentinel.cluster.flow.statistic.data.ClusterFlowEvent;
import com.alibaba.csp.sentinel.cluster.flow.statistic.data.ClusterMetricBucket;
import com.alibaba.csp.sentinel.slots.statistic.base.LeapArray;
import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public class ClusterMetricLeapArray extends LeapArray<ClusterMetricBucket> {
private final LongAdder[] occupyCounter;
private boolean hasOccupied = false;
public ClusterMetricLeapArray(int sampleCount, int intervalInMs) {
super(sampleCount, intervalInMs);
ClusterFlowEvent[] events = ClusterFlowEvent.values();
this.occupyCounter = new LongAdder[events.length];
for (ClusterFlowEvent event : events) {
occupyCounter[event.ordinal()] = new LongAdder();
}
}
@Override
public ClusterMetricBucket newEmptyBucket(long timeMillis) {
return new ClusterMetricBucket();
}
@Override
protected WindowWrap<ClusterMetricBucket> resetWindowTo(WindowWrap<ClusterMetricBucket> w, long startTime) {
w.resetTo(startTime);
w.value().reset();
transferOccupyToBucket(w.value());
return w;
}
private void transferOccupyToBucket(/*@Valid*/ ClusterMetricBucket bucket) {
if (hasOccupied) {
transferOccupiedCount(bucket, ClusterFlowEvent.PASS, ClusterFlowEvent.OCCUPIED_PASS);
transferOccupiedThenReset(bucket, ClusterFlowEvent.PASS);
transferOccupiedThenReset(bucket, ClusterFlowEvent.PASS_REQUEST);
hasOccupied = false;
}
}
private void transferOccupiedCount(ClusterMetricBucket bucket, ClusterFlowEvent source, ClusterFlowEvent target) {
bucket.add(target, occupyCounter[source.ordinal()].sum());
}
private void transferOccupiedThenReset(ClusterMetricBucket bucket, ClusterFlowEvent event) {
bucket.add(event, occupyCounter[event.ordinal()].sumThenReset());
}
public void addOccupyPass(int count) {
occupyCounter[ClusterFlowEvent.PASS.ordinal()].add(count);
occupyCounter[ClusterFlowEvent.PASS_REQUEST.ordinal()].add(1);
this.hasOccupied = true;
}
public long getOccupiedCount(ClusterFlowEvent event) {
return occupyCounter[event.ordinal()].sum();
}
public long getFirstCountOfWindow(ClusterFlowEvent event) {
if (event == null) {
return 0;
}
WindowWrap<ClusterMetricBucket> windowWrap = getValidHead();
if (windowWrap == null) {
return 0;
}
return windowWrap.value().get(event);
}
}

View File

@@ -0,0 +1,134 @@
/*
* 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.flow.statistic.metric;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.atomic.LongAdder;
import com.alibaba.csp.sentinel.slots.statistic.cache.CacheMap;
import com.alibaba.csp.sentinel.util.AssertUtil;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public class ClusterParamMetric {
public static final int DEFAULT_CLUSTER_MAX_CAPACITY = 4000;
private final ClusterParameterLeapArray<LongAdder> metric;
public ClusterParamMetric(int sampleCount, int intervalInMs) {
this(sampleCount, intervalInMs, DEFAULT_CLUSTER_MAX_CAPACITY);
}
public ClusterParamMetric(int sampleCount, int intervalInMs, int maxCapacity) {
AssertUtil.isTrue(sampleCount > 0, "sampleCount should be positive");
AssertUtil.isTrue(intervalInMs > 0, "interval should be positive");
AssertUtil.isTrue(intervalInMs % sampleCount == 0, "time span needs to be evenly divided");
this.metric = new ClusterParameterLeapArray<>(sampleCount, intervalInMs, maxCapacity);
}
public long getSum(Object value) {
if (value == null) {
return 0;
}
metric.currentWindow();
long sum = 0;
List<CacheMap<Object, LongAdder>> buckets = metric.values();
for (CacheMap<Object, LongAdder> bucket : buckets) {
long count = getCount(bucket.get(value));
sum += count;
}
return sum;
}
private long getCount(/*@Nullable*/ LongAdder adder) {
return adder == null ? 0 : adder.sum();
}
public void addValue(Object value, int count) {
if (value == null) {
return;
}
CacheMap<Object, LongAdder> data = metric.currentWindow().value();
LongAdder newCounter = new LongAdder();
LongAdder currentCounter = data.putIfAbsent(value, newCounter);
if (currentCounter != null) {
currentCounter.add(count);
} else {
newCounter.add(count);
}
}
public double getAvg(Object value) {
return getSum(value) / metric.getIntervalInSecond();
}
public Map<Object, Double> getTopValues(int number) {
AssertUtil.isTrue(number > 0, "number must be positive");
metric.currentWindow();
List<CacheMap<Object, LongAdder>> buckets = metric.values();
Map<Object, Long> result = new HashMap<>(buckets.size());
for (CacheMap<Object, LongAdder> b : buckets) {
Set<Object> subSet = b.keySet(true);
for (Object o : subSet) {
Long count = result.get(o);
if (count == null) {
count = getCount(b.get(o));
} else {
count += getCount(b.get(o));
}
result.put(o, count);
}
}
// After merge, get the top set one.
Set<Entry<Object, Long>> set = result.entrySet();
List<Entry<Object, Long>> list = new ArrayList<>(set);
Collections.sort(list, new Comparator<Entry<Object, Long>>() {
@Override
public int compare(Entry<Object, Long> a,
Entry<Object, Long> b) {
return (int) (b.getValue() == null ? 0 : b.getValue()) - (int) (a.getValue() == null ? 0 : a.getValue());
}
});
Map<Object, Double> doubleResult = new HashMap<Object, Double>();
int size = list.size() > number ? number : list.size();
for (int i = 0; i < size; i++) {
Map.Entry<Object, Long> x = list.get(i);
if (x.getValue() == 0) {
break;
}
doubleResult.put(x.getKey(), ((double) x.getValue()) / metric.getIntervalInSecond());
}
return doubleResult;
}
}

View File

@@ -0,0 +1,51 @@
/*
* 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.flow.statistic.metric;
import com.alibaba.csp.sentinel.slots.statistic.base.LeapArray;
import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap;
import com.alibaba.csp.sentinel.slots.statistic.cache.CacheMap;
import com.alibaba.csp.sentinel.slots.statistic.cache.ConcurrentLinkedHashMapWrapper;
import com.alibaba.csp.sentinel.util.AssertUtil;
/**
* @param <C> counter type
* @author Eric Zhao
* @since 1.4.0
*/
public class ClusterParameterLeapArray<C> extends LeapArray<CacheMap<Object, C>> {
private final int maxCapacity;
public ClusterParameterLeapArray(int sampleCount, int intervalInMs, int maxCapacity) {
super(sampleCount, intervalInMs);
AssertUtil.isTrue(maxCapacity > 0, "maxCapacity of LRU map should be positive");
this.maxCapacity = maxCapacity;
}
@Override
public CacheMap<Object, C> newEmptyBucket(long timeMillis) {
return new ConcurrentLinkedHashMapWrapper<>(maxCapacity);
}
@Override
protected WindowWrap<CacheMap<Object, C>> resetWindowTo(WindowWrap<CacheMap<Object, C>> w, long startTime) {
w.resetTo(startTime);
w.value().clear();
return w;
}
}

View File

@@ -0,0 +1,70 @@
/*
* 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 java.util.Collection;
import com.alibaba.csp.sentinel.cluster.TokenResult;
import com.alibaba.csp.sentinel.cluster.TokenResultStatus;
import com.alibaba.csp.sentinel.cluster.TokenService;
/**
* Default embedded token server in Sentinel which wraps the {@link SentinelDefaultTokenServer}
* and the {@link TokenService} from SPI provider.
*
* @author Eric Zhao
* @since 1.4.0
*/
public class DefaultEmbeddedTokenServer implements EmbeddedClusterTokenServer {
private final TokenService tokenService = TokenServiceProvider.getService();
private final ClusterTokenServer server = new SentinelDefaultTokenServer(true);
@Override
public void start() throws Exception {
server.start();
}
@Override
public void stop() throws Exception {
server.stop();
}
@Override
public TokenResult requestToken(Long ruleId, int acquireCount, boolean prioritized) {
if (tokenService != null) {
return tokenService.requestToken(ruleId, acquireCount, prioritized);
}
return new TokenResult(TokenResultStatus.FAIL);
}
@Override
public TokenResult requestParamToken(Long ruleId, int acquireCount, Collection<Object> params) {
if (tokenService != null) {
return tokenService.requestParamToken(ruleId, acquireCount, params);
}
return new TokenResult(TokenResultStatus.FAIL);
}
@Override
public TokenResult requestConcurrentToken(String clientAddress, Long ruleId, int acquireCount) {
return null;
}
@Override
public void releaseConcurrentToken(Long tokenId) {
}
}

View File

@@ -0,0 +1,186 @@
/*
* 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 java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import com.alibaba.csp.sentinel.cluster.server.codec.netty.NettyRequestDecoder;
import com.alibaba.csp.sentinel.cluster.server.codec.netty.NettyResponseEncoder;
import com.alibaba.csp.sentinel.cluster.server.connection.Connection;
import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionPool;
import com.alibaba.csp.sentinel.cluster.server.handler.TokenServerHandler;
import com.alibaba.csp.sentinel.log.RecordLog;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.concurrent.GenericFutureListener;
import io.netty.util.internal.SystemPropertyUtil;
import static com.alibaba.csp.sentinel.cluster.server.ServerConstants.*;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public class NettyTransportServer implements ClusterTokenServer {
private static final int DEFAULT_EVENT_LOOP_THREADS = Math.max(1,
SystemPropertyUtil.getInt("io.netty.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2));
private static final int MAX_RETRY_TIMES = 3;
private static final int RETRY_SLEEP_MS = 2000;
private final int port;
private NioEventLoopGroup bossGroup;
private NioEventLoopGroup workerGroup;
private final ConnectionPool connectionPool = new ConnectionPool();
private final AtomicInteger currentState = new AtomicInteger(SERVER_STATUS_OFF);
private final AtomicInteger failedTimes = new AtomicInteger(0);
public NettyTransportServer(int port) {
this.port = port;
}
@Override
public void start() {
if (!currentState.compareAndSet(SERVER_STATUS_OFF, SERVER_STATUS_STARTING)) {
return;
}
ServerBootstrap b = new ServerBootstrap();
this.bossGroup = new NioEventLoopGroup(1);
this.workerGroup = new NioEventLoopGroup(DEFAULT_EVENT_LOOP_THREADS);
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(new LengthFieldBasedFrameDecoder(1024, 0, 2, 0, 2));
p.addLast(new NettyRequestDecoder());
p.addLast(new LengthFieldPrepender(2));
p.addLast(new NettyResponseEncoder());
p.addLast(new TokenServerHandler(connectionPool));
}
})
.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
.childOption(ChannelOption.SO_SNDBUF, 32 * 1024)
.childOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
.childOption(ChannelOption.SO_TIMEOUT, 10)
.childOption(ChannelOption.TCP_NODELAY, true)
.childOption(ChannelOption.SO_RCVBUF, 32 * 1024);
b.bind(port).addListener(new GenericFutureListener<ChannelFuture>() {
@Override
public void operationComplete(ChannelFuture future) {
if (future.cause() != null) {
RecordLog.info("[NettyTransportServer] Token server start failed (port=" + port + "), failedTimes: " + failedTimes.get(),
future.cause());
currentState.compareAndSet(SERVER_STATUS_STARTING, SERVER_STATUS_OFF);
int failCount = failedTimes.incrementAndGet();
if (failCount > MAX_RETRY_TIMES) {
return;
}
try {
Thread.sleep(failCount * RETRY_SLEEP_MS);
start();
} catch (Throwable e) {
RecordLog.info("[NettyTransportServer] Failed to start token server when retrying", e);
}
} else {
RecordLog.info("[NettyTransportServer] Token server started success at port {}", port);
currentState.compareAndSet(SERVER_STATUS_STARTING, SERVER_STATUS_STARTED);
}
}
});
}
@Override
public void stop() {
// If still initializing, wait for ready.
while (currentState.get() == SERVER_STATUS_STARTING) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// Ignore.
}
}
if (currentState.compareAndSet(SERVER_STATUS_STARTED, SERVER_STATUS_OFF)) {
try {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
connectionPool.shutdownAll();
failedTimes.set(0);
RecordLog.info("[NettyTransportServer] Sentinel token server stopped");
} catch (Exception ex) {
RecordLog.warn("[NettyTransportServer] Failed to stop token server (port=" + port + ")", ex);
}
}
}
public void refreshRunningServer() {
connectionPool.refreshIdleTask();
}
public void closeConnection(String clientIp, int clientPort) throws Exception {
Connection connection = connectionPool.getConnection(clientIp, clientPort);
connection.close();
}
public void closeAll() throws Exception {
List<Connection> connections = connectionPool.listAllConnection();
for (Connection connection : connections) {
connection.close();
}
}
public List<String> listAllClient() {
List<String> clients = new ArrayList<String>();
List<Connection> connections = connectionPool.listAllConnection();
for (Connection conn : connections) {
clients.add(conn.getConnectionKey());
}
return clients;
}
public int getCurrentState() {
return currentState.get();
}
public int clientCount() {
return connectionPool.count();
}
}

View File

@@ -0,0 +1,153 @@
/*
* 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 java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import com.alibaba.csp.sentinel.cluster.ClusterStateManager;
import com.alibaba.csp.sentinel.cluster.registry.ConfigSupplierRegistry;
import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager;
import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig;
import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfigObserver;
import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionManager;
import com.alibaba.csp.sentinel.init.InitExecutor;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.util.HostNameUtil;
import com.alibaba.csp.sentinel.util.StringUtil;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public class SentinelDefaultTokenServer implements ClusterTokenServer {
private final boolean embedded;
private ClusterTokenServer server;
private int port;
private final AtomicBoolean shouldStart = new AtomicBoolean(false);
static {
InitExecutor.doInit();
}
public SentinelDefaultTokenServer() {
this(false);
}
public SentinelDefaultTokenServer(boolean embedded) {
this.embedded = embedded;
ClusterServerConfigManager.addTransportConfigChangeObserver(new ServerTransportConfigObserver() {
@Override
public void onTransportConfigChange(ServerTransportConfig config) {
changeServerConfig(config);
}
});
initNewServer();
}
private void initNewServer() {
if (server != null) {
return;
}
int port = ClusterServerConfigManager.getPort();
if (port > 0) {
this.server = new NettyTransportServer(port);
this.port = port;
}
}
private synchronized void changeServerConfig(ServerTransportConfig config) {
if (config == null || config.getPort() <= 0) {
return;
}
int newPort = config.getPort();
if (newPort == port) {
return;
}
try {
if (server != null) {
stopServer();
}
this.server = new NettyTransportServer(newPort);
this.port = newPort;
startServerIfScheduled();
} catch (Exception ex) {
RecordLog.warn("[SentinelDefaultTokenServer] Failed to apply modification to token server", ex);
}
}
private void startServerIfScheduled() throws Exception {
if (shouldStart.get()) {
if (server != null) {
server.start();
ClusterStateManager.markToServer();
if (embedded) {
RecordLog.info("[SentinelDefaultTokenServer] Running in embedded mode");
handleEmbeddedStart();
}
}
}
}
private void stopServer() throws Exception {
if (server != null) {
server.stop();
if (embedded) {
handleEmbeddedStop();
}
}
}
private void handleEmbeddedStop() {
String namespace = ConfigSupplierRegistry.getNamespaceSupplier().get();
if (StringUtil.isNotEmpty(namespace)) {
ConnectionManager.removeConnection(namespace, HostNameUtil.getIp());
}
}
private void handleEmbeddedStart() {
String namespace = ConfigSupplierRegistry.getNamespaceSupplier().get();
if (StringUtil.isNotEmpty(namespace)) {
// Mark server global mode as embedded.
ClusterServerConfigManager.setEmbedded(true);
if (!ClusterServerConfigManager.getNamespaceSet().contains(namespace)) {
Set<String> namespaceSet = new HashSet<>(ClusterServerConfigManager.getNamespaceSet());
namespaceSet.add(namespace);
ClusterServerConfigManager.loadServerNamespaceSet(namespaceSet);
}
// Register self to connection group.
ConnectionManager.addConnection(namespace, HostNameUtil.getIp());
}
}
@Override
public void start() throws Exception {
if (shouldStart.compareAndSet(false, true)) {
startServerIfScheduled();
}
}
@Override
public void stop() throws Exception {
if (shouldStart.compareAndSet(true, false)) {
stopServer();
}
}
}

View File

@@ -0,0 +1,31 @@
/*
* 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;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public final class ServerConstants {
public static final int SERVER_STATUS_OFF = 0;
public static final int SERVER_STATUS_STARTING = 1;
public static final int SERVER_STATUS_STARTED = 2;
public static final String DEFAULT_NAMESPACE = "default";
private ServerConstants() {}
}

View File

@@ -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.server;
import com.alibaba.csp.sentinel.cluster.TokenService;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.spi.SpiLoader;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public final class TokenServiceProvider {
private static TokenService service = null;
static {
resolveTokenServiceSpi();
}
public static TokenService getService() {
return service;
}
private static void resolveTokenServiceSpi() {
service = SpiLoader.of(TokenService.class).loadFirstInstanceOrDefault();
if (service != null) {
RecordLog.info("[TokenServiceProvider] Global token service resolved: "
+ service.getClass().getCanonicalName());
} else {
RecordLog.warn("[TokenServiceProvider] Unable to resolve TokenService: no SPI found");
}
}
}

View File

@@ -0,0 +1,64 @@
/*
* 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.codec;
import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder;
import com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityDecoder;
import com.alibaba.csp.sentinel.cluster.request.ClusterRequest;
import com.alibaba.csp.sentinel.cluster.server.codec.registry.RequestDataDecodeRegistry;
import com.alibaba.csp.sentinel.log.RecordLog;
import io.netty.buffer.ByteBuf;
/**
* <p>Default entity decoder for any {@link ClusterRequest} entity.</p>
*
* <p>Decode format:</p>
* <pre>
* +--------+---------+---------+
* | xid(4) | type(1) | data... |
* +--------+---------+---------+
* </pre>
*
* @author Eric Zhao
* @since 1.4.0
*/
public class DefaultRequestEntityDecoder implements RequestEntityDecoder<ByteBuf, ClusterRequest> {
@Override
public ClusterRequest decode(ByteBuf source) {
if (source.readableBytes() >= 5) {
int xid = source.readInt();
int type = source.readByte();
EntityDecoder<ByteBuf, ?> dataDecoder = RequestDataDecodeRegistry.getDecoder(type);
if (dataDecoder == null) {
RecordLog.warn("Unknown type of request data decoder: {}", type);
return null;
}
Object data;
if (source.readableBytes() == 0) {
data = null;
} else {
data = dataDecoder.decode(source);
}
return new ClusterRequest<>(xid, type, data);
}
return null;
}
}

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.codec;
import com.alibaba.csp.sentinel.cluster.ClusterConstants;
import com.alibaba.csp.sentinel.cluster.codec.EntityWriter;
import com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityWriter;
import com.alibaba.csp.sentinel.cluster.response.ClusterResponse;
import com.alibaba.csp.sentinel.cluster.response.Response;
import com.alibaba.csp.sentinel.cluster.server.codec.registry.ResponseDataWriterRegistry;
import com.alibaba.csp.sentinel.log.RecordLog;
import io.netty.buffer.ByteBuf;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public class DefaultResponseEntityWriter implements ResponseEntityWriter<ClusterResponse, ByteBuf> {
@Override
public void writeTo(ClusterResponse response, ByteBuf out) {
int type = response.getType();
EntityWriter<Object, ByteBuf> responseDataWriter = ResponseDataWriterRegistry.getWriter(type);
if (responseDataWriter == null) {
writeHead(response.setStatus(ClusterConstants.RESPONSE_STATUS_BAD), out);
RecordLog.warn("[NettyResponseEncoder] Cannot find matching writer for type <{}>", response.getType());
return;
}
writeHead(response, out);
responseDataWriter.writeTo(response.getData(), out);
}
private void writeHead(Response response, ByteBuf out) {
out.writeInt(response.getId());
out.writeByte(response.getType());
out.writeByte(response.getStatus());
}
}

View File

@@ -0,0 +1,64 @@
/*
* 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.codec;
import com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityDecoder;
import com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityWriter;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.spi.SpiLoader;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public final class ServerEntityCodecProvider {
private static RequestEntityDecoder requestEntityDecoder = null;
private static ResponseEntityWriter responseEntityWriter = null;
static {
resolveInstance();
}
private static void resolveInstance() {
ResponseEntityWriter writer = SpiLoader.of(ResponseEntityWriter.class).loadFirstInstance();
if (writer == null) {
RecordLog.warn("[ServerEntityCodecProvider] No existing response entity writer, resolve failed");
} else {
responseEntityWriter = writer;
RecordLog.info("[ServerEntityCodecProvider] Response entity writer resolved: {}",
responseEntityWriter.getClass().getCanonicalName());
}
RequestEntityDecoder decoder = SpiLoader.of(RequestEntityDecoder.class).loadFirstInstance();
if (decoder == null) {
RecordLog.warn("[ServerEntityCodecProvider] No existing request entity decoder, resolve failed");
} else {
requestEntityDecoder = decoder;
RecordLog.info("[ServerEntityCodecProvider] Request entity decoder resolved: {}",
requestEntityDecoder.getClass().getCanonicalName());
}
}
public static RequestEntityDecoder getRequestEntityDecoder() {
return requestEntityDecoder;
}
public static ResponseEntityWriter getResponseEntityWriter() {
return responseEntityWriter;
}
private ServerEntityCodecProvider() {}
}

View File

@@ -0,0 +1,49 @@
/*
* 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.codec.data;
import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder;
import com.alibaba.csp.sentinel.cluster.request.data.FlowRequestData;
import io.netty.buffer.ByteBuf;
/**
* <p>
* Decoder for {@link FlowRequestData} from {@code ByteBuf} stream. The layout:
* </p>
* <pre>
* | flow ID (8) | count (4) | priority flag (1) |
* </pre>
*
* @author Eric Zhao
* @since 1.4.0
*/
public class FlowRequestDataDecoder implements EntityDecoder<ByteBuf, FlowRequestData> {
@Override
public FlowRequestData decode(ByteBuf source) {
if (source.readableBytes() >= 12) {
FlowRequestData requestData = new FlowRequestData()
.setFlowId(source.readLong())
.setCount(source.readInt());
if (source.readableBytes() >= 1) {
requestData.setPriority(source.readBoolean());
}
return requestData;
}
return null;
}
}

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.cluster.server.codec.data;
import com.alibaba.csp.sentinel.cluster.codec.EntityWriter;
import com.alibaba.csp.sentinel.cluster.response.data.FlowTokenResponseData;
import io.netty.buffer.ByteBuf;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public class FlowResponseDataWriter implements EntityWriter<FlowTokenResponseData, ByteBuf> {
@Override
public void writeTo(FlowTokenResponseData entity, ByteBuf out) {
out.writeInt(entity.getRemainingCount());
out.writeInt(entity.getWaitInMs());
}
}

View File

@@ -0,0 +1,91 @@
/*
* 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.codec.data;
import java.util.ArrayList;
import java.util.List;
import com.alibaba.csp.sentinel.cluster.ClusterConstants;
import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder;
import com.alibaba.csp.sentinel.cluster.request.data.ParamFlowRequestData;
import io.netty.buffer.ByteBuf;
/**
* @author jialiang.linjl
* @author Eric Zhao
* @since 1.4.0
*/
public class ParamFlowRequestDataDecoder implements EntityDecoder<ByteBuf, ParamFlowRequestData> {
@Override
public ParamFlowRequestData decode(ByteBuf source) {
if (source.readableBytes() >= 16) {
ParamFlowRequestData requestData = new ParamFlowRequestData()
.setFlowId(source.readLong())
.setCount(source.readInt());
int amount = source.readInt();
if (amount > 0) {
List<Object> params = new ArrayList<>(amount);
for (int i = 0; i < amount; i++) {
decodeParam(source, params);
}
requestData.setParams(params);
return requestData;
}
}
return null;
}
private boolean decodeParam(ByteBuf source, List<Object> params) {
byte paramType = source.readByte();
switch (paramType) {
case ClusterConstants.PARAM_TYPE_INTEGER:
params.add(source.readInt());
return true;
case ClusterConstants.PARAM_TYPE_STRING:
int length = source.readInt();
byte[] bytes = new byte[length];
source.readBytes(bytes);
// TODO: take care of charset?
params.add(new String(bytes));
return true;
case ClusterConstants.PARAM_TYPE_BOOLEAN:
params.add(source.readBoolean());
return true;
case ClusterConstants.PARAM_TYPE_DOUBLE:
params.add(source.readDouble());
return true;
case ClusterConstants.PARAM_TYPE_LONG:
params.add(source.readLong());
return true;
case ClusterConstants.PARAM_TYPE_FLOAT:
params.add(source.readFloat());
return true;
case ClusterConstants.PARAM_TYPE_BYTE:
params.add(source.readByte());
return true;
case ClusterConstants.PARAM_TYPE_SHORT:
params.add(source.readShort());
return true;
default:
return false;
}
}
}

View File

@@ -0,0 +1,40 @@
/*
* 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.codec.data;
import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder;
import io.netty.buffer.ByteBuf;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public class PingRequestDataDecoder implements EntityDecoder<ByteBuf, String> {
@Override
public String decode(ByteBuf source) {
if (source.readableBytes() >= 4) {
int length = source.readInt();
if (length > 0 && source.readableBytes() > 0) {
byte[] bytes = new byte[length];
source.readBytes(bytes);
return new String(bytes);
}
}
return null;
}
}

View File

@@ -0,0 +1,36 @@
/*
* 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.codec.data;
import com.alibaba.csp.sentinel.cluster.codec.EntityWriter;
import com.alibaba.csp.sentinel.util.StringUtil;
import io.netty.buffer.ByteBuf;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public class PingResponseDataWriter implements EntityWriter<Integer, ByteBuf> {
@Override
public void writeTo(Integer entity, ByteBuf target) {
if (entity == null || target == null) {
return;
}
target.writeInt(entity);
}
}

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.cluster.server.codec.netty;
import java.util.List;
import com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityDecoder;
import com.alibaba.csp.sentinel.cluster.request.Request;
import com.alibaba.csp.sentinel.cluster.server.codec.ServerEntityCodecProvider;
import com.alibaba.csp.sentinel.log.RecordLog;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public class NettyRequestDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
RequestEntityDecoder<ByteBuf, Request> requestDecoder = ServerEntityCodecProvider.getRequestEntityDecoder();
if (requestDecoder == null) {
RecordLog.warn("[NettyRequestDecoder] Cannot resolve the global request entity decoder, "
+ "dropping the request");
return;
}
// TODO: handle decode error here.
Request request = requestDecoder.decode(in);
if (request != null) {
out.add(request);
}
}
}

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.
*/
package com.alibaba.csp.sentinel.cluster.server.codec.netty;
import com.alibaba.csp.sentinel.cluster.ClusterConstants;
import com.alibaba.csp.sentinel.cluster.codec.EntityWriter;
import com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityWriter;
import com.alibaba.csp.sentinel.cluster.response.ClusterResponse;
import com.alibaba.csp.sentinel.cluster.response.Response;
import com.alibaba.csp.sentinel.cluster.server.codec.ServerEntityCodecProvider;
import com.alibaba.csp.sentinel.cluster.server.codec.registry.ResponseDataWriterRegistry;
import com.alibaba.csp.sentinel.log.RecordLog;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public class NettyResponseEncoder extends MessageToByteEncoder<ClusterResponse> {
@Override
protected void encode(ChannelHandlerContext ctx, ClusterResponse response, ByteBuf out) throws Exception {
ResponseEntityWriter<ClusterResponse, ByteBuf> responseEntityWriter = ServerEntityCodecProvider.getResponseEntityWriter();
if (responseEntityWriter == null) {
RecordLog.warn("[NettyResponseEncoder] Cannot resolve the global response entity writer, reply bad status");
writeBadStatusHead(response, out);
return;
}
responseEntityWriter.writeTo(response, out);
}
private void writeBadStatusHead(Response response, ByteBuf out) {
out.writeInt(response.getId());
out.writeByte(ClusterConstants.RESPONSE_STATUS_BAD);
out.writeByte(response.getStatus());
}
}

View File

@@ -0,0 +1,48 @@
/*
* 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.codec.registry;
import java.util.HashMap;
import java.util.Map;
import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder;
import io.netty.buffer.ByteBuf;
/**
* @author Eric Zhao
* @since 1.4.0
*/
public final class RequestDataDecodeRegistry {
private static final Map<Integer, EntityDecoder<ByteBuf, ?>> DECODER_MAP = new HashMap<>();
public static boolean addDecoder(int type, EntityDecoder<ByteBuf, ?> decoder) {
if (DECODER_MAP.containsKey(type)) {
return false;
}
DECODER_MAP.put(type, decoder);
return true;
}
public static EntityDecoder<ByteBuf, Object> getDecoder(int type) {
return (EntityDecoder<ByteBuf, Object>)DECODER_MAP.get(type);
}
public static boolean removeDecoder(int type) {
return DECODER_MAP.remove(type) != null;
}
}

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