diff --git a/.codecov.yml b/.codecov.yml index 33e7d838..b15b071e 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,4 +1,5 @@ ignore: - "sentinel-demo/.*" - "sentinel-dashboard/.*" - - "sentinel-benchmark/.*" \ No newline at end of file + - "sentinel-benchmark/.*" + - "sentinel-transport/.*" \ No newline at end of file diff --git a/sentinel-cluster/README.md b/sentinel-cluster/README.md new file mode 100644 index 00000000..d6e1c2fd --- /dev/null +++ b/sentinel-cluster/README.md @@ -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 \ No newline at end of file diff --git a/sentinel-cluster/sentinel-cluster-client-default/README.md b/sentinel-cluster/sentinel-cluster-client-default/README.md new file mode 100644 index 00000000..d09d8263 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/README.md @@ -0,0 +1 @@ +# Sentinel Cluster Client (Default) \ No newline at end of file diff --git a/sentinel-cluster/sentinel-cluster-client-default/pom.xml b/sentinel-cluster/sentinel-cluster-client-default/pom.xml index 42b89d43..de189630 100644 --- a/sentinel-cluster/sentinel-cluster-client-default/pom.xml +++ b/sentinel-cluster/sentinel-cluster-client-default/pom.xml @@ -17,6 +17,11 @@ com.alibaba.csp sentinel-core + + com.alibaba.csp + sentinel-transport-common + provided + com.alibaba.csp sentinel-cluster-common-default @@ -26,5 +31,16 @@ io.netty netty-all + + + junit + junit + test + + + org.mockito + mockito-core + test + \ No newline at end of file diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/ClientConstants.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/ClientConstants.java new file mode 100644 index 00000000..41b1dd01 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/ClientConstants.java @@ -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() {} +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/DefaultClusterTokenClient.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/DefaultClusterTokenClient.java new file mode 100644 index 00000000..58779cec --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/DefaultClusterTokenClient.java @@ -0,0 +1,198 @@ +/* + * 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.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.ClusterClientConfig; +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(ClusterClientConfig clusterClientConfig) { + changeServer(clusterClientConfig); + } + }); + initNewConnection(); + } + + private boolean serverEqual(TokenServerDescriptor descriptor, ClusterClientConfig 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*/ ClusterClientConfig 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); + 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 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 request = new ClusterRequest<>(ClusterConstants.MSG_TYPE_FLOW, data); + try { + return sendTokenRequest(request); + } catch (Exception ex) { + ClusterClientStatLogUtil.log(ex.getMessage()); + return new TokenResult(TokenResultStatus.FAIL); + } + } + + @Override + public TokenResult requestParamToken(Long flowId, int acquireCount, Collection params) { + if (notValidRequest(flowId, acquireCount) || params == null || params.isEmpty()) { + return badRequest(); + } + ParamFlowRequestData data = new ParamFlowRequestData().setCount(acquireCount) + .setFlowId(flowId).setParams(params); + ClusterRequest request = new ClusterRequest<>(ClusterConstants.MSG_TYPE_PARAM_FLOW, data); + try { + return sendTokenRequest(request); + } catch (Exception ex) { + ClusterClientStatLogUtil.log(ex.getMessage()); + return new TokenResult(TokenResultStatus.FAIL); + } + } + + 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); + } +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/NettyTransportClient.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/NettyTransportClient.java new file mode 100644 index 00000000..e70df0e0 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/NettyTransportClient.java @@ -0,0 +1,248 @@ +/* + * 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.TimeUnit; +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.ClusterClientConfig; +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.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 { + + public static final int RECONNECT_DELAY_MS = 1000; + + 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); + + public NettyTransportClient(ClusterClientConfig clientConfig) { + AssertUtil.notNull(clientConfig, "client config cannot be null"); + this.host = clientConfig.getServerHost(); + this.port = clientConfig.getServerPort(); + } + + 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.SO_TIMEOUT, 20) + .option(ChannelOption.TCP_NODELAY, true) + .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000) + .handler(new ChannelInitializer() { + @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() { + @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 (channel != null) { + channel.eventLoop().schedule(new Runnable() { + @Override + public void run() { + RecordLog.info("[NettyTransportClient] Reconnecting to server <" + host + ":" + port + ">"); + try { + start(); + } catch (Exception e) { + RecordLog.warn("[NettyTransportClient] Failed to reconnect to server", e); + } + } + }, RECONNECT_DELAY_MS * (failConnectedTime.get() + 1), TimeUnit.MILLISECONDS); + } + } + }; + + @Override + public void start() throws Exception { + connect(initClientBootstrap()); + } + + @Override + public void stop() throws Exception { + while (currentState.get() == ClientConstants.CLIENT_STATUS_PENDING) { + try { + Thread.sleep(500); + } catch (Exception ex) { + // Ignore. + } + } + + if (channel != null) { + channel.close(); + channel = null; + } + if (eventLoopGroup != null) { + eventLoopGroup.shutdownGracefully(); + } + 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 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() { + if (idGenerator.get() > MAX_ID) { + idGenerator.set(0); + } + return idGenerator.incrementAndGet(); + } + + /*public CompletableFuture 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 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 MAX_ID = 999_999_999; +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/ClientEntityCodecProvider.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/ClientEntityCodecProvider.java new file mode 100644 index 00000000..6cb0ebda --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/ClientEntityCodecProvider.java @@ -0,0 +1,62 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.client.codec; + +import com.alibaba.csp.sentinel.util.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.loadFirstInstance(RequestEntityWriter.class); + 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.loadFirstInstance(ResponseEntityDecoder.class); + 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() {} +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultRequestEntityWriter.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultRequestEntityWriter.java new file mode 100644 index 00000000..0ba9680f --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultRequestEntityWriter.java @@ -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 { + + @Override + public void writeTo(ClusterRequest request, ByteBuf target) { + int type = request.getType(); + EntityWriter requestDataWriter = RequestDataWriterRegistry.getWriter(type); + + if (requestDataWriter == null) { + RecordLog.warn("[DefaultRequestEntityWriter] Cannot find matching request writer for type <{0}>," + + " 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()); + } +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultResponseEntityDecoder.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultResponseEntityDecoder.java new file mode 100644 index 00000000..6cb3333c --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultResponseEntityDecoder.java @@ -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; + +/** + *

Default entity decoder for any {@link ClusterResponse} entity.

+ * + *

Decode format:

+ *
+ * +--------+---------+-----------+---------+
+ * | xid(4) | type(1) | status(1) | data... |
+ * +--------+---------+-----------+---------+
+ * 
+ * + * @author Eric Zhao + * @since 1.4.0 + */ +public class DefaultResponseEntityDecoder implements ResponseEntityDecoder { + + @Override + public ClusterResponse decode(ByteBuf source) { + if (source.readableBytes() >= 6) { + int xid = source.readInt(); + int type = source.readByte(); + int status = source.readByte(); + + EntityDecoder decoder = ResponseDataDecodeRegistry.getDecoder(type); + if (decoder == null) { + RecordLog.warn("Unknown type of response data decoder: {0}", 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; + } +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/FlowRequestDataWriter.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/FlowRequestDataWriter.java new file mode 100644 index 00000000..a134d279 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/FlowRequestDataWriter.java @@ -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(4 byte) | Count(4 byte) | PriorityFlag (1) | + * +-------------------+--------------+----------------+---------------+------------------+ + * + * @author Eric Zhao + * @since 1.4.0 + */ +public class FlowRequestDataWriter implements EntityWriter { + + @Override + public void writeTo(FlowRequestData entity, ByteBuf target) { + target.writeLong(entity.getFlowId()); + target.writeInt(entity.getCount()); + target.writeBoolean(entity.isPriority()); + } +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/FlowResponseDataDecoder.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/FlowResponseDataDecoder.java new file mode 100644 index 00000000..c15355fe --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/FlowResponseDataDecoder.java @@ -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 { + + @Override + public FlowTokenResponseData decode(ByteBuf source) { + FlowTokenResponseData data = new FlowTokenResponseData(); + + if (source.readableBytes() == 8) { + data.setRemainingCount(source.readInt()); + data.setWaitInMs(source.readInt()); + } + return data; + } +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/ParamFlowRequestDataWriter.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/ParamFlowRequestDataWriter.java new file mode 100644 index 00000000..831d214d --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/ParamFlowRequestDataWriter.java @@ -0,0 +1,129 @@ +/* + * 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 java.util.Collection; + +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 io.netty.buffer.ByteBuf; + +/** + * @author jialiang.linjl + * @author Eric Zhao + * @since 1.4.0 + */ +public class ParamFlowRequestDataWriter implements EntityWriter { + + @Override + public void writeTo(ParamFlowRequestData entity, ByteBuf target) { + target.writeLong(entity.getFlowId()); + target.writeInt(entity.getCount()); + + Collection params = entity.getParams(); + + // Write parameter amount. + int amount = calculateParamAmount(params); + target.writeInt(amount); + + // Serialize parameters with type flag. + for (Object param : entity.getParams()) { + encodeValue(param, target); + } + } + + 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); + } + + private int calculateParamAmount(/*@NonEmpty*/ Collection params) { + int size = 0; + int length = 0; + for (Object param : params) { + int s = calculateParamTransportSize(param); + if (size + s > PARAM_MAX_SIZE) { + break; + } + length++; + } + return length; + } + + private int calculateParamTransportSize(Object value) { + // 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 PARAM_MAX_SIZE = 1000; +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/PingRequestDataWriter.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/PingRequestDataWriter.java new file mode 100644 index 00000000..def73ced --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/PingRequestDataWriter.java @@ -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 { + + @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); + } +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/PingResponseDataDecoder.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/PingResponseDataDecoder.java new file mode 100644 index 00000000..e12395f0 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/PingResponseDataDecoder.java @@ -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.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 { + + @Override + public Integer decode(ByteBuf source) { + if (source.readableBytes() >= 1) { + return (int) source.readByte(); + } + return -1; + } +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyRequestEncoder.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyRequestEncoder.java new file mode 100644 index 00000000..f6141a3d --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyRequestEncoder.java @@ -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 { + + @Override + protected void encode(ChannelHandlerContext ctx, ClusterRequest request, ByteBuf out) throws Exception { + RequestEntityWriter requestEntityWriter = ClientEntityCodecProvider.getRequestEntityWriter(); + if (requestEntityWriter == null) { + RecordLog.warn("[NettyRequestEncoder] Cannot resolve the global request entity writer, dropping the request"); + return; + } + + requestEntityWriter.writeTo(request, out); + } +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyResponseDecoder.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyResponseDecoder.java new file mode 100644 index 00000000..5f314619 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyResponseDecoder.java @@ -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 out) throws Exception { + ResponseEntityDecoder 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); + } + } +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/registry/RequestDataWriterRegistry.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/registry/RequestDataWriterRegistry.java new file mode 100644 index 00000000..9c0a61b4 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/registry/RequestDataWriterRegistry.java @@ -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> WRITER_MAP = new HashMap<>(); + + public static boolean addWriter(int type, EntityWriter writer) { + if (WRITER_MAP.containsKey(type)) { + return false; + } + WRITER_MAP.put(type, (EntityWriter)writer); + return true; + } + + public static EntityWriter getWriter(int type) { + return WRITER_MAP.get(type); + } + + public static boolean remove(int type) { + return WRITER_MAP.remove(type) != null; + } +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/registry/ResponseDataDecodeRegistry.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/registry/ResponseDataDecodeRegistry.java new file mode 100644 index 00000000..543fa774 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/registry/ResponseDataDecodeRegistry.java @@ -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> DECODER_MAP = new HashMap<>(); + + public static boolean addDecoder(int type, EntityDecoder decoder) { + if (DECODER_MAP.containsKey(type)) { + return false; + } + DECODER_MAP.put(type, decoder); + return true; + } + + public static EntityDecoder getDecoder(int type) { + return (EntityDecoder)DECODER_MAP.get(type); + } + + public static boolean removeDecoder(int type) { + return DECODER_MAP.remove(type) != null; + } +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfig.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfig.java new file mode 100644 index 00000000..87f048e9 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfig.java @@ -0,0 +1,75 @@ +/* + * 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 String serverHost; + private int serverPort; + + private int requestTimeout; + private int connectTimeout; + + public String getServerHost() { + return serverHost; + } + + public ClusterClientConfig setServerHost(String serverHost) { + this.serverHost = serverHost; + return this; + } + + public int getServerPort() { + return serverPort; + } + + public ClusterClientConfig setServerPort(int serverPort) { + this.serverPort = serverPort; + return this; + } + + public int getRequestTimeout() { + return requestTimeout; + } + + public ClusterClientConfig setRequestTimeout(int requestTimeout) { + this.requestTimeout = requestTimeout; + return this; + } + + public int getConnectTimeout() { + return connectTimeout; + } + + public ClusterClientConfig setConnectTimeout(int connectTimeout) { + this.connectTimeout = connectTimeout; + return this; + } + + @Override + public String toString() { + return "ClusterClientConfig{" + + "serverHost='" + serverHost + '\'' + + ", serverPort=" + serverPort + + ", requestTimeout=" + requestTimeout + + ", connectTimeout=" + connectTimeout + + '}'; + } +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfigManager.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfigManager.java new file mode 100644 index 00000000..ca2af3da --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfigManager.java @@ -0,0 +1,140 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.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 final PropertyListener PROPERTY_LISTENER = new ClientConfigPropertyListener(); + private static SentinelProperty currentProperty = new DynamicSentinelProperty<>(); + + private static final List SERVER_CHANGE_OBSERVERS = new ArrayList<>(); + + static { + currentProperty.addListener(PROPERTY_LISTENER); + } + + public static void register2Property(SentinelProperty property) { + synchronized (PROPERTY_LISTENER) { + RecordLog.info("[ClusterClientConfigManager] Registering new property to cluster client config manager"); + currentProperty.removeListener(PROPERTY_LISTENER); + property.addListener(PROPERTY_LISTENER); + currentProperty = 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) { + currentProperty.updateValue(config); + } + + private static class ClientConfigPropertyListener implements PropertyListener { + + @Override + public void configUpdate(ClusterClientConfig config) { + applyConfig(config); + } + + @Override + public void configLoad(ClusterClientConfig config) { + if (config == null) { + RecordLog.warn("[ClusterClientConfigManager] Empty initial config"); + return; + } + applyConfig(config); + } + + private synchronized void applyConfig(ClusterClientConfig config) { + if (!isValidConfig(config)) { + RecordLog.warn( + "[ClusterClientConfigManager] Invalid cluster client config, ignoring: " + config); + return; + } + RecordLog.info("[ClusterClientConfigManager] Updating new config: " + config); + if (config.getRequestTimeout() != requestTimeout) { + requestTimeout = config.getRequestTimeout(); + } + updateServer(config); + } + } + + public static boolean isValidConfig(ClusterClientConfig config) { + return config != null && StringUtil.isNotBlank(config.getServerHost()) + && config.getServerPort() > 0 + && config.getServerPort() <= 65535 + && config.getRequestTimeout() > 0; + } + + public static void updateServer(ClusterClientConfig config) { + String host = config.getServerHost(); + int port = config.getServerPort(); + AssertUtil.assertNotBlank(host, "token server host cannot be empty"); + AssertUtil.isTrue(port > 0, "token server port should be valid (positive)"); + if (serverPort == port && host.equals(serverHost)) { + return; + } + for (ServerChangeObserver observer : SERVER_CHANGE_OBSERVERS) { + observer.onRemoteServerChange(config); + } + + serverHost = host; + serverPort = port; + } + + public static String getServerHost() { + return serverHost; + } + + public static int getServerPort() { + return serverPort; + } + + public static int getRequestTimeout() { + return requestTimeout; + } + + private ClusterClientConfigManager() {} +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ServerChangeObserver.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ServerChangeObserver.java new file mode 100644 index 00000000..30908476 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ServerChangeObserver.java @@ -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 clusterClientConfig new cluster client config + */ + void onRemoteServerChange(ClusterClientConfig clusterClientConfig); +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientHandler.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientHandler.java new file mode 100644 index 00000000..61b2e37c --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientHandler.java @@ -0,0 +1,109 @@ +/* + * 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.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: " + ctx.channel().remoteAddress()); + } + + @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 ping = new ClusterRequest().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: {0}, connected count: {1})", + ctx.channel().remoteAddress(), count); + return; + } + RecordLog.warn("[TokenClientHandler] Client ping failed (target server: {0})", ctx.channel().remoteAddress()); + } + + @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: " + ctx.channel().remoteAddress()); + } + + @Override + public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { + RecordLog.info("[TokenClientHandler] Client channel unregistered, remote address: " + ctx.channel().remoteAddress()); + currentState.set(ClientConstants.CLIENT_STATUS_OFF); + disconnectCallback.run(); + } + + public int getCurrentState() { + return currentState.get(); + } + + public boolean hasStarted() { + return getCurrentState() == ClientConstants.CLIENT_STATUS_STARTED; + } +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientPromiseHolder.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientPromiseHolder.java new file mode 100644 index 00000000..b0cf0e6c --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientPromiseHolder.java @@ -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> PROMISE_MAP = new ConcurrentHashMap<>(); + + public static void putPromise(int xid, ChannelPromise promise) { + PROMISE_MAP.put(xid, new SimpleEntry(promise, null)); + } + + public static SimpleEntry getEntry(int xid) { + return PROMISE_MAP.get(xid); + } + + public static void remove(int xid) { + PROMISE_MAP.remove(xid); + } + + public static boolean completePromise(int xid, ClusterResponse response) { + if (!PROMISE_MAP.containsKey(xid)) { + return false; + } + SimpleEntry 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() {} +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/init/DefaultClusterClientInitFunc.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/init/DefaultClusterClientInitFunc.java new file mode 100644 index 00000000..d5851bc0 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/init/DefaultClusterClientInitFunc.java @@ -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.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.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()); + RequestDataWriterRegistry.addWriter(ClientConstants.TYPE_PARAM_FLOW, new ParamFlowRequestDataWriter()); + } + + 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()); + } +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/command/handler/FetchClusterClientConfigHandler.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/command/handler/FetchClusterClientConfigHandler.java new file mode 100644 index 00000000..407d7311 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/command/handler/FetchClusterClientConfigHandler.java @@ -0,0 +1,42 @@ +/* + * 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.config.ClusterClientConfig; +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.fastjson.JSON; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +@CommandMapping(name = "cluster/client/fetchConfig") +public class FetchClusterClientConfigHandler implements CommandHandler { + + @Override + public CommandResponse handle(CommandRequest request) { + ClusterClientConfig config = new ClusterClientConfig() + .setServerHost(ClusterClientConfigManager.getServerHost()) + .setServerPort(ClusterClientConfigManager.getServerPort()) + .setRequestTimeout(ClusterClientConfigManager.getRequestTimeout()); + return CommandResponse.ofSuccess(JSON.toJSONString(config)); + } +} + diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/command/handler/ModifyClusterClientConfigHandler.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/command/handler/ModifyClusterClientConfigHandler.java new file mode 100644 index 00000000..73d00d5d --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/command/handler/ModifyClusterClientConfigHandler.java @@ -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.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.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.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") +public class ModifyClusterClientConfigHandler implements CommandHandler { + + @Override + public CommandResponse 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); + ClusterClientConfig clusterClientConfig = JSON.parseObject(data, ClusterClientConfig.class); + ClusterClientConfigManager.applyNewConfig(clusterClientConfig); + + return CommandResponse.ofSuccess("success"); + } catch (Exception e) { + RecordLog.warn("[ModifyClusterClientConfigHandler] Decode client cluster config error", e); + return CommandResponse.ofFailure(e, "decode client cluster config error"); + } + } +} + diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.client.ClusterTokenClient b/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.client.ClusterTokenClient new file mode 100644 index 00000000..3070db6a --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.client.ClusterTokenClient @@ -0,0 +1 @@ +com.alibaba.csp.sentinel.cluster.client.DefaultClusterTokenClient \ No newline at end of file diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityWriter b/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityWriter new file mode 100644 index 00000000..23698745 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityWriter @@ -0,0 +1 @@ +com.alibaba.csp.sentinel.cluster.client.codec.DefaultRequestEntityWriter \ No newline at end of file diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityDecoder b/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityDecoder new file mode 100644 index 00000000..cd43e7e2 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityDecoder @@ -0,0 +1 @@ +com.alibaba.csp.sentinel.cluster.client.codec.DefaultResponseEntityDecoder \ No newline at end of file diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler b/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler new file mode 100755 index 00000000..80d3582c --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler @@ -0,0 +1,2 @@ +com.alibaba.csp.sentinel.command.handler.ModifyClusterClientConfigHandler +com.alibaba.csp.sentinel.command.handler.FetchClusterClientConfigHandler \ No newline at end of file diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc b/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc new file mode 100755 index 00000000..b9b709cd --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc @@ -0,0 +1 @@ +com.alibaba.csp.sentinel.cluster.client.init.DefaultClusterClientInitFunc \ No newline at end of file diff --git a/sentinel-cluster/sentinel-cluster-common-default/pom.xml b/sentinel-cluster/sentinel-cluster-common-default/pom.xml index d3f14e08..970432e6 100644 --- a/sentinel-cluster/sentinel-cluster-common-default/pom.xml +++ b/sentinel-cluster/sentinel-cluster-common-default/pom.xml @@ -13,7 +13,10 @@ jar - + + com.alibaba.csp + sentinel-core + \ No newline at end of file diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterConstants.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterConstants.java new file mode 100644 index 00000000..0183ab72 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterConstants.java @@ -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; + +/** + * @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 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 = 8730; + public static final int DEFAULT_REQUEST_TIMEOUT = 20; + + private ClusterConstants() {} +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterErrorMessages.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterErrorMessages.java new file mode 100644 index 00000000..3e6ad416 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterErrorMessages.java @@ -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 (not running or initializing)"; + public static final String NO_RULES_IN_SERVER = "no rules in token server"; + + private ClusterErrorMessages() {} +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTransportClient.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTransportClient.java new file mode 100644 index 00000000..23ba9014 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTransportClient.java @@ -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(); +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/annotation/RequestType.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/annotation/RequestType.java new file mode 100644 index 00000000..74224b84 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/annotation/RequestType.java @@ -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(); +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/EntityDecoder.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/EntityDecoder.java new file mode 100644 index 00000000..1bb830ac --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/EntityDecoder.java @@ -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 source stream type + * @param target entity type + * @author Eric Zhao + * @since 1.4.0 + */ +public interface EntityDecoder { + + /** + * Decode target object from source stream. + * + * @param source source stream + * @return decoded target object + */ + T decode(S source); +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/EntityWriter.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/EntityWriter.java new file mode 100644 index 00000000..03dfcbaf --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/EntityWriter.java @@ -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 entity type + * @param target stream type + * @author Eric Zhao + * @since 1.4.0 + */ +public interface EntityWriter { + + /** + * Write the provided entity to target stream. + * + * @param entity entity to publish + * @param target the target stream + */ + void writeTo(E entity, T target); +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/request/RequestEntityDecoder.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/request/RequestEntityDecoder.java new file mode 100644 index 00000000..8b9fd33f --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/request/RequestEntityDecoder.java @@ -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 extends EntityDecoder { + +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/request/RequestEntityWriter.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/request/RequestEntityWriter.java new file mode 100644 index 00000000..814bbed1 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/request/RequestEntityWriter.java @@ -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 extends EntityWriter { + +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/response/ResponseEntityDecoder.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/response/ResponseEntityDecoder.java new file mode 100644 index 00000000..6bb03ae1 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/response/ResponseEntityDecoder.java @@ -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 extends EntityDecoder { + +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/response/ResponseEntityWriter.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/response/ResponseEntityWriter.java new file mode 100644 index 00000000..89e5625c --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/response/ResponseEntityWriter.java @@ -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 extends EntityWriter { + +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/exception/SentinelClusterException.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/exception/SentinelClusterException.java new file mode 100644 index 00000000..ddc73095 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/exception/SentinelClusterException.java @@ -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; + } +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/registry/ConfigSupplierRegistry.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/registry/ConfigSupplierRegistry.java new file mode 100644 index 00000000..63107d52 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/registry/ConfigSupplierRegistry.java @@ -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 DEFAULT_APP_NAME_SUPPLIER = new Supplier() { + @Override + public String get() { + return AppNameUtil.getAppName(); + } + }; + /** + * Registered namespace supplier. + */ + private static Supplier namespaceSupplier = DEFAULT_APP_NAME_SUPPLIER; + + /** + * Get the registered namespace supplier. + * + * @return the registered namespace supplier + */ + public static Supplier getNamespaceSupplier() { + return namespaceSupplier; + } + + public static void setNamespaceSupplier(Supplier namespaceSupplier) { + AssertUtil.notNull(namespaceSupplier, "namespaceSupplier cannot be null"); + ConfigSupplierRegistry.namespaceSupplier = namespaceSupplier; + RecordLog.info("[ConfigSupplierRegistry] New namespace supplier provided, current supplied: " + + namespaceSupplier.get()); + } + + private ConfigSupplierRegistry() {} +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/ClusterRequest.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/ClusterRequest.java new file mode 100644 index 00000000..2e513e41 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/ClusterRequest.java @@ -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 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 setId(int id) { + this.id = id; + return this; + } + + @Override + public int getType() { + return type; + } + + public ClusterRequest setType(int type) { + this.type = type; + return this; + } + + public T getData() { + return data; + } + + public ClusterRequest setData(T data) { + this.data = data; + return this; + } + + @Override + public String toString() { + return "ClusterRequest{" + + "id=" + id + + ", type=" + type + + ", data=" + data + + '}'; + } +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/Request.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/Request.java new file mode 100644 index 00000000..cda5b9c0 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/Request.java @@ -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(); +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/data/FlowRequestData.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/data/FlowRequestData.java new file mode 100644 index 00000000..ab401983 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/data/FlowRequestData.java @@ -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 + + '}'; + } +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/data/ParamFlowRequestData.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/data/ParamFlowRequestData.java new file mode 100644 index 00000000..c6ac6ec4 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/data/ParamFlowRequestData.java @@ -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 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 getParams() { + return params; + } + + public ParamFlowRequestData setParams(Collection params) { + this.params = params; + return this; + } + + @Override + public String toString() { + return "ParamFlowRequestData{" + + "flowId=" + flowId + + ", count=" + count + + ", params=" + params + + '}'; + } +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/ClusterResponse.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/ClusterResponse.java new file mode 100644 index 00000000..4f5ebf5b --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/ClusterResponse.java @@ -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 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 setId(int id) { + this.id = id; + return this; + } + + @Override + public int getType() { + return type; + } + + public ClusterResponse setType(int type) { + this.type = type; + return this; + } + + @Override + public int getStatus() { + return status; + } + + public ClusterResponse setStatus(int status) { + this.status = status; + return this; + } + + public T getData() { + return data; + } + + public ClusterResponse setData(T data) { + this.data = data; + return this; + } + + @Override + public String toString() { + return "ClusterResponse{" + + "id=" + id + + ", type=" + type + + ", status=" + status + + ", data=" + data + + '}'; + } +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/Response.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/Response.java new file mode 100644 index 00000000..037088d9 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/Response.java @@ -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(); +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/data/FlowTokenResponseData.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/data/FlowTokenResponseData.java new file mode 100644 index 00000000..2ca2842c --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/data/FlowTokenResponseData.java @@ -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.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 String toString() { + return "FlowTokenResponseData{" + + "remainingCount=" + remainingCount + + ", waitInMs=" + waitInMs + + '}'; + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/pom.xml b/sentinel-cluster/sentinel-cluster-server-default/pom.xml index b240ed51..cce6b03c 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/pom.xml +++ b/sentinel-cluster/sentinel-cluster-server-default/pom.xml @@ -35,5 +35,16 @@ io.netty netty-all + + + junit + junit + test + + + org.mockito + mockito-core + test + \ No newline at end of file diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowChecker.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowChecker.java new file mode 100644 index 00000000..f9fb0108 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowChecker.java @@ -0,0 +1,110 @@ +/* + * 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.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 TokenResult acquireClusterToken(/*@Valid*/ FlowRule rule, int acquireCount, boolean prioritized) { + Long id = rule.getClusterConfig().getFlowId(); + ClusterMetric metric = ClusterMetricStatistics.getMetric(id); + if (metric == null) { + return new TokenResult(TokenResultStatus.FAIL); + } + + double latestQps = metric.getAvg(ClusterFlowEvent.PASS_REQUEST); + 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() {} +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterParamFlowChecker.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterParamFlowChecker.java new file mode 100644 index 00000000..798ecf84 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterParamFlowChecker.java @@ -0,0 +1,108 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.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.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 TokenResult acquireClusterToken(ParamFlowRule rule, int count, Collection values) { + Long id = rule.getClusterConfig().getFlowId(); + ClusterParamMetric metric = ClusterParamMetricStatistics.getMetric(id); + if (metric == null) { + // Unexpected state, return FAIL. + return new TokenResult(TokenResultStatus.FAIL); + } + 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() {} +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/DefaultTokenService.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/DefaultTokenService.java new file mode 100644 index 00000000..2953a135 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/DefaultTokenService.java @@ -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.flow; + +import java.util.Collection; + +import com.alibaba.csp.sentinel.cluster.TokenResultStatus; +import com.alibaba.csp.sentinel.cluster.TokenResult; +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; + +/** + * Default implementation for cluster {@link TokenService}. + * + * @author Eric Zhao + * @since 1.4.0 + */ +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 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); + } + + private boolean notValidRequest(Long id, int count) { + return id == null || id <= 0 || count <= 0; + } + + private TokenResult badRequest() { + return new TokenResult(TokenResultStatus.BAD_REQUEST); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/ClusterFlowRuleManager.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/ClusterFlowRuleManager.java new file mode 100644 index 00000000..621ff6b5 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/ClusterFlowRuleManager.java @@ -0,0 +1,379 @@ +/* + * 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.ClusterMetricStatistics; +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.config.ClusterServerConfigManager; +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.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; + +/** + * 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>> DEFAULT_PROPERTY_SUPPLIER = + new Function>>() { + @Override + public SentinelProperty> apply(String namespace) { + return new DynamicSentinelProperty<>(); + } + }; + + /** + * (flowId, clusterRule) + */ + private static final Map FLOW_RULES = new ConcurrentHashMap<>(); + /** + * (namespace, [flowId...]) + */ + private static final Map> NAMESPACE_FLOW_ID_MAP = new ConcurrentHashMap<>(); + /** + *

This map (flowId, namespace) is used for getting connected count + * when checking a specific rule in {@code ruleId}:

+ * + *
+     * ruleId -> namespace -> connection group -> connected count
+     * 
+ */ + private static final Map FLOW_NAMESPACE_MAP = new ConcurrentHashMap<>(); + + /** + * (namespace, property-listener wrapper) + */ + private static final Map> PROPERTY_MAP = new ConcurrentHashMap<>(); + /** + * Cluster flow rule property supplier for a specific namespace. + */ + private static volatile Function>> 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> defaultProperty = new DynamicSentinelProperty<>(); + String defaultNamespace = ServerConstants.DEFAULT_NAMESPACE; + registerPropertyInternal(defaultNamespace, defaultProperty); + } + + public static void setPropertySupplier(Function>> propertySupplier) { + 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> 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 <{0}>", 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> property) { + NamespaceFlowProperty oldProperty = PROPERTY_MAP.get(namespace); + if (oldProperty != null) { + oldProperty.getProperty().removeListener(oldProperty.getListener()); + } + PropertyListener> listener = new FlowRulePropertyListener(namespace); + property.addListener(listener); + PROPERTY_MAP.put(namespace, new NamespaceFlowProperty<>(namespace, property, listener)); + Set 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 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 <{0}>", namespace); + } + } + + private static void removePropertyListeners() { + for (NamespaceFlowProperty property : PROPERTY_MAP.values()) { + property.getProperty().removeListener(property.getListener()); + } + } + + private static void restorePropertyListeners() { + for (NamespaceFlowProperty 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 List 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 getFlowRules(String namespace) { + if (StringUtil.isEmpty(namespace)) { + return new ArrayList<>(); + } + List rules = new ArrayList<>(); + Set 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 rules) { + AssertUtil.notEmpty(namespace, "namespace cannot be empty"); + NamespaceFlowProperty 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()); + } + + /** + * Clear all rules of the provided namespace and reset map. + * + * @param namespace valid namespace + */ + private static void clearAndResetRulesFor(/*@Valid*/ String namespace) { + Set 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); + } + flowIdSet.clear(); + } else { + resetNamespaceFlowIdMapFor(namespace); + } + } + + private static void clearAndResetRulesConditional(/*@Valid*/ String namespace, Predicate predicate) { + Set 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); + } + } + 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); + } + + private static void applyClusterFlowRule(List list, /*@Valid*/ String namespace) { + if (list == null || list.isEmpty()) { + clearAndResetRulesFor(namespace); + return; + } + final ConcurrentHashMap ruleMap = new ConcurrentHashMap<>(); + + Set 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. + Long flowId = rule.getClusterConfig().getFlowId(); + if (flowId == null) { + continue; + } + ruleMap.put(flowId, rule); + FLOW_NAMESPACE_MAP.put(flowId, namespace); + flowIdSet.add(flowId); + + // Prepare cluster metric from valid flow ID. + ClusterMetricStatistics.putMetricIfAbsent(flowId, + new ClusterMetric(ClusterServerConfigManager.getSampleCount(), + ClusterServerConfigManager.getIntervalMs())); + } + + // Cleanup unused cluster metrics. + clearAndResetRulesConditional(namespace, new Predicate() { + @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> { + + private final String namespace; + + public FlowRulePropertyListener(String namespace) { + this.namespace = namespace; + } + + @Override + public synchronized void configUpdate(List conf) { + applyClusterFlowRule(conf, namespace); + RecordLog.info("[ClusterFlowRuleManager] Cluster flow rules received for namespace <{0}>: {1}", + namespace, FLOW_RULES); + } + + @Override + public synchronized void configLoad(List conf) { + applyClusterFlowRule(conf, namespace); + RecordLog.info("[ClusterFlowRuleManager] Cluster flow rules loaded for namespace <{0}>: {1}", + namespace, FLOW_RULES); + } + } + + private ClusterFlowRuleManager() {} +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/ClusterParamFlowRuleManager.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/ClusterParamFlowRuleManager.java new file mode 100644 index 00000000..c81393d5 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/ClusterParamFlowRuleManager.java @@ -0,0 +1,356 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.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.config.ClusterServerConfigManager; +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.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>> DEFAULT_PROPERTY_SUPPLIER = + new Function>>() { + @Override + public SentinelProperty> apply(String namespace) { + return new DynamicSentinelProperty<>(); + } + }; + + /** + * (id, clusterParamRule) + */ + private static final Map PARAM_RULES = new ConcurrentHashMap<>(); + /** + * (namespace, [flowId...]) + */ + private static final Map> NAMESPACE_FLOW_ID_MAP = new ConcurrentHashMap<>(); + /** + * (flowId, namespace) + */ + private static final Map FLOW_NAMESPACE_MAP = new ConcurrentHashMap<>(); + + /** + * (namespace, property-listener wrapper) + */ + private static final Map> PROPERTY_MAP = new ConcurrentHashMap<>(); + /** + * Cluster parameter flow rule property supplier for a specific namespace. + */ + private static volatile Function>> propertySupplier + = DEFAULT_PROPERTY_SUPPLIER; + + private static final Object UPDATE_LOCK = new Object(); + + static { + initDefaultProperty(); + } + + private static void initDefaultProperty() { + SentinelProperty> defaultProperty = new DynamicSentinelProperty<>(); + String defaultNamespace = ServerConstants.DEFAULT_NAMESPACE; + registerPropertyInternal(defaultNamespace, defaultProperty); + } + + public static void setPropertySupplier( + Function>> propertySupplier) { + ClusterParamFlowRuleManager.propertySupplier = propertySupplier; + } + + /** + * 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> 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 <{0}>", 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> property) { + NamespaceFlowProperty oldProperty = PROPERTY_MAP.get(namespace); + if (oldProperty != null) { + oldProperty.getProperty().removeListener(oldProperty.getListener()); + } + PropertyListener> listener = new ParamRulePropertyListener(namespace); + property.addListener(listener); + PROPERTY_MAP.put(namespace, new NamespaceFlowProperty<>(namespace, property, listener)); + Set 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 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 <{0}>", namespace); + } + } + + private static void removePropertyListeners() { + for (NamespaceFlowProperty property : PROPERTY_MAP.values()) { + property.getProperty().removeListener(property.getListener()); + } + } + + private static void restorePropertyListeners() { + for (NamespaceFlowProperty 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()); + } + + private static void clearAndResetRulesFor(/*@Valid*/ String namespace) { + Set 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 predicate) { + Set 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 List 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 getParamRules(String namespace) { + if (StringUtil.isEmpty(namespace)) { + return new ArrayList<>(); + } + List rules = new ArrayList<>(); + Set 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 rules) { + AssertUtil.notEmpty(namespace, "namespace cannot be empty"); + NamespaceFlowProperty 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> { + + private final String namespace; + + public ParamRulePropertyListener(String namespace) { + this.namespace = namespace; + } + + @Override + public void configLoad(List conf) { + applyClusterParamRules(conf, namespace); + RecordLog.info("[ClusterParamFlowRuleManager] Cluster parameter rules loaded for namespace <{0}>: {1}", + namespace, PARAM_RULES); + } + + @Override + public void configUpdate(List conf) { + applyClusterParamRules(conf, namespace); + RecordLog.info("[ClusterParamFlowRuleManager] Cluster parameter rules received for namespace <{0}>: {1}", + namespace, PARAM_RULES); + } + } + + private static void applyClusterParamRules(List list, /*@Valid*/ String namespace) { + if (list == null || list.isEmpty()) { + clearAndResetRulesFor(namespace); + return; + } + final ConcurrentHashMap ruleMap = new ConcurrentHashMap<>(); + + Set 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); + + // Flow id should not be null after filtered. + Long flowId = rule.getClusterConfig().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(ClusterServerConfigManager.getSampleCount(), + ClusterServerConfigManager.getIntervalMs())); + } + + // Cleanup unused cluster parameter metrics. + clearAndResetRulesConditional(namespace, new Predicate() { + @Override + public boolean test(Long flowId) { + return !ruleMap.containsKey(flowId); + } + }); + + PARAM_RULES.putAll(ruleMap); + NAMESPACE_FLOW_ID_MAP.put(namespace, flowIdSet); + } + + private ClusterParamFlowRuleManager() {} +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/NamespaceFlowProperty.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/NamespaceFlowProperty.java new file mode 100644 index 00000000..37732b43 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/NamespaceFlowProperty.java @@ -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 type of the rule + * @author Eric Zhao + * @since 1.4.0 + */ +class NamespaceFlowProperty { + + private final String namespace; + private final SentinelProperty> property; + private final PropertyListener> listener; + + public NamespaceFlowProperty(String namespace, + SentinelProperty> property, + PropertyListener> listener) { + this.namespace = namespace; + this.property = property; + this.listener = listener; + } + + public SentinelProperty> getProperty() { + return property; + } + + public String getNamespace() { + return namespace; + } + + public PropertyListener> getListener() { + return listener; + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/ClusterMetricStatistics.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/ClusterMetricStatistics.java new file mode 100644 index 00000000..e574f7dc --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/ClusterMetricStatistics.java @@ -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 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 keySet = METRIC_MAP.keySet(); + for (Long id : keySet) { + METRIC_MAP.put(id, new ClusterMetric(ClusterServerConfigManager.getSampleCount(), + ClusterServerConfigManager.getIntervalMs())); + } + } + + private ClusterMetricStatistics() {} +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/ClusterParamMetricStatistics.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/ClusterParamMetricStatistics.java new file mode 100644 index 00000000..73632493 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/ClusterParamMetricStatistics.java @@ -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 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 keySet = METRIC_MAP.keySet(); + for (Long id : keySet) { + METRIC_MAP.put(id, new ClusterParamMetric(ClusterServerConfigManager.getSampleCount(), + ClusterServerConfigManager.getIntervalMs())); + } + } + + private ClusterParamMetricStatistics() {} +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/data/ClusterFlowEvent.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/data/ClusterFlowEvent.java new file mode 100644 index 00000000..8b79094f --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/data/ClusterFlowEvent.java @@ -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 +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/data/ClusterMetricBucket.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/data/ClusterMetricBucket.java new file mode 100644 index 00000000..a27dac5a --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/data/ClusterMetricBucket.java @@ -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 com.alibaba.csp.sentinel.slots.statistic.base.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; + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetric.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetric.java new file mode 100644 index 00000000..eea3c459 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetric.java @@ -0,0 +1,100 @@ +/* + * 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"); + int windowLengthInMs = intervalInMs / sampleCount; + this.metric = new ClusterMetricLeapArray(windowLengthInMs, 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 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; + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetricLeapArray.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetricLeapArray.java new file mode 100644 index 00000000..40d2d752 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetricLeapArray.java @@ -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.metric; + +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.LongAdder; +import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public class ClusterMetricLeapArray extends LeapArray { + + private final LongAdder[] occupyCounter; + private boolean hasOccupied = false; + + /** + * The total bucket count is: {@link #sampleCount} = intervalInMs / windowLengthInMs. + * + * @param windowLengthInMs a single window bucket's time length in milliseconds. + * @param intervalInMs the total time span of this {@link LeapArray} in milliseconds. + */ + public ClusterMetricLeapArray(int windowLengthInMs, int intervalInMs) { + super(windowLengthInMs, intervalInMs / 1000); + ClusterFlowEvent[] events = ClusterFlowEvent.values(); + this.occupyCounter = new LongAdder[events.length]; + for (ClusterFlowEvent event : events) { + occupyCounter[event.ordinal()] = new LongAdder(); + } + } + + @Override + public ClusterMetricBucket newEmptyBucket() { + return new ClusterMetricBucket(); + } + + @Override + protected WindowWrap resetWindowTo(WindowWrap 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 windowWrap = getValidHead(); + if (windowWrap == null) { + return 0; + } + return windowWrap.value().get(event); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParamMetric.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParamMetric.java new file mode 100644 index 00000000..4daf7e41 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParamMetric.java @@ -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.metric; + +import java.util.List; + +import com.alibaba.csp.sentinel.slots.statistic.base.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 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"); + int windowLengthInMs = intervalInMs / sampleCount; + this.metric = new ClusterParameterLeapArray<>(windowLengthInMs, intervalInMs, maxCapacity); + } + + public long getSum(Object value) { + if (value == null) { + return 0; + } + + metric.currentWindow(); + long sum = 0; + + List> buckets = metric.values(); + for (CacheMap 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 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(); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParameterLeapArray.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParameterLeapArray.java new file mode 100644 index 00000000..dbb603b4 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParameterLeapArray.java @@ -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.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 counter type + * @author Eric Zhao + * @since 1.4.0 + */ +public class ClusterParameterLeapArray extends LeapArray> { + + private final int maxCapacity; + + public ClusterParameterLeapArray(int windowLengthInMs, int intervalInMs, int maxCapacity) { + super(windowLengthInMs, intervalInMs / 1000); + AssertUtil.isTrue(maxCapacity > 0, "maxCapacity of LRU map should be positive"); + this.maxCapacity = maxCapacity; + } + + @Override + public CacheMap newEmptyBucket() { + return new ConcurrentLinkedHashMapWrapper<>(maxCapacity); + } + + @Override + protected WindowWrap> resetWindowTo(WindowWrap> w, long startTime) { + w.resetTo(startTime); + w.value().clear(); + return w; + } + + +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/DefaultEmbeddedTokenServer.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/DefaultEmbeddedTokenServer.java new file mode 100644 index 00000000..fd95cbf2 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/DefaultEmbeddedTokenServer.java @@ -0,0 +1,61 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.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 params) { + if (tokenService != null) { + return tokenService.requestParamToken(ruleId, acquireCount, params); + } + return new TokenResult(TokenResultStatus.FAIL); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/NettyTransportServer.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/NettyTransportServer.java new file mode 100644 index 00000000..aded579a --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/NettyTransportServer.java @@ -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 = 1000; + + 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() { + @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() { + @Override + public void operationComplete(ChannelFuture future) { + if (future.cause() != null) { + RecordLog.info("[NettyTransportServer] Token server start failed (port=" + port + ")", + 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 connections = connectionPool.listAllConnection(); + for (Connection connection : connections) { + connection.close(); + } + } + + public List listAllClient() { + List clients = new ArrayList(); + List 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(); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/SentinelDefaultTokenServer.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/SentinelDefaultTokenServer.java new file mode 100644 index 00000000..7aa478fe --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/SentinelDefaultTokenServer.java @@ -0,0 +1,140 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.server; + +import java.util.concurrent.atomic.AtomicBoolean; + +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(); + 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)) { + 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(); + } + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/ServerConstants.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/ServerConstants.java new file mode 100644 index 00000000..6569cef8 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/ServerConstants.java @@ -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() {} +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/TokenServiceProvider.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/TokenServiceProvider.java new file mode 100644 index 00000000..969e5400 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/TokenServiceProvider.java @@ -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.server; + +import java.util.ArrayList; +import java.util.List; +import java.util.ServiceLoader; + +import com.alibaba.csp.sentinel.cluster.TokenService; +import com.alibaba.csp.sentinel.cluster.flow.DefaultTokenService; +import com.alibaba.csp.sentinel.log.RecordLog; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public final class TokenServiceProvider { + + private static TokenService service = null; + + private static final ServiceLoader LOADER = ServiceLoader.load(TokenService.class); + + static { + resolveTokenServiceSpi(); + } + + public static TokenService getService() { + return service; + } + + private static void resolveTokenServiceSpi() { + boolean hasOther = false; + List list = new ArrayList(); + for (TokenService service : LOADER) { + if (service.getClass() != DefaultTokenService.class) { + hasOther = true; + list.add(service); + } + } + + if (hasOther) { + // Pick the first. + service = list.get(0); + } else { + // No custom token service, using default. + service = new DefaultTokenService(); + } + + RecordLog.info("[TokenServiceProvider] Global token service resolved: " + + service.getClass().getCanonicalName()); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/DefaultRequestEntityDecoder.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/DefaultRequestEntityDecoder.java new file mode 100644 index 00000000..bdd42e64 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/DefaultRequestEntityDecoder.java @@ -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; + +/** + *

Default entity decoder for any {@link ClusterRequest} entity.

+ * + *

Decode format:

+ *
+ * +--------+---------+---------+
+ * | xid(4) | type(1) | data... |
+ * +--------+---------+---------+
+ * 
+ * + * @author Eric Zhao + * @since 1.4.0 + */ +public class DefaultRequestEntityDecoder implements RequestEntityDecoder { + + @Override + public ClusterRequest decode(ByteBuf source) { + if (source.readableBytes() >= 5) { + int xid = source.readInt(); + int type = source.readByte(); + + EntityDecoder dataDecoder = RequestDataDecodeRegistry.getDecoder(type); + if (dataDecoder == null) { + RecordLog.warn("Unknown type of request data decoder: {0}", type); + return null; + } + + Object data; + if (source.readableBytes() == 0) { + data = null; + } else { + data = dataDecoder.decode(source); + } + + return new ClusterRequest<>(xid, type, data); + } + return null; + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/DefaultResponseEntityWriter.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/DefaultResponseEntityWriter.java new file mode 100644 index 00000000..e56faa09 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/DefaultResponseEntityWriter.java @@ -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 { + + @Override + public void writeTo(ClusterResponse response, ByteBuf out) { + int type = response.getType(); + EntityWriter responseDataWriter = ResponseDataWriterRegistry.getWriter(type); + + if (responseDataWriter == null) { + writeHead(response.setStatus(ClusterConstants.RESPONSE_STATUS_BAD), out); + RecordLog.warn("[NettyResponseEncoder] Cannot find matching writer for type <{0}>", 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()); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/ServerEntityCodecProvider.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/ServerEntityCodecProvider.java new file mode 100644 index 00000000..ada283ab --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/ServerEntityCodecProvider.java @@ -0,0 +1,66 @@ +/* + * 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.util.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.loadFirstInstance(ResponseEntityWriter.class); + 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.loadFirstInstance(RequestEntityDecoder.class); + 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() {} +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/FlowRequestDataDecoder.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/FlowRequestDataDecoder.java new file mode 100644 index 00000000..803d08b2 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/FlowRequestDataDecoder.java @@ -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; + +/** + *

+ * Decoder for {@link FlowRequestData} from {@code ByteBuf} stream. The layout: + *

+ *
+ * | flow ID (4) | count (4) | priority flag (1) |
+ * 
+ * + * @author Eric Zhao + * @since 1.4.0 + */ +public class FlowRequestDataDecoder implements EntityDecoder { + + @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; + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/FlowResponseDataWriter.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/FlowResponseDataWriter.java new file mode 100644 index 00000000..0f240eef --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/FlowResponseDataWriter.java @@ -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 { + + @Override + public void writeTo(FlowTokenResponseData entity, ByteBuf out) { + out.writeInt(entity.getRemainingCount()); + out.writeInt(entity.getWaitInMs()); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/ParamFlowRequestDataDecoder.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/ParamFlowRequestDataDecoder.java new file mode 100644 index 00000000..fd4eab6f --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/ParamFlowRequestDataDecoder.java @@ -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 { + + @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 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 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; + } + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/PingRequestDataDecoder.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/PingRequestDataDecoder.java new file mode 100644 index 00000000..cef33119 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/PingRequestDataDecoder.java @@ -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 { + + @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; + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/PingResponseDataWriter.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/PingResponseDataWriter.java new file mode 100644 index 00000000..26e31ecd --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/PingResponseDataWriter.java @@ -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 { + + @Override + public void writeTo(Integer entity, ByteBuf target) { + if (entity == null || target == null) { + return; + } + target.writeByte(entity); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/netty/NettyRequestDecoder.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/netty/NettyRequestDecoder.java new file mode 100644 index 00000000..f5c37b34 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/netty/NettyRequestDecoder.java @@ -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 out) throws Exception { + RequestEntityDecoder 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); + } + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/netty/NettyResponseEncoder.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/netty/NettyResponseEncoder.java new file mode 100644 index 00000000..ef7e2bef --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/netty/NettyResponseEncoder.java @@ -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 { + + @Override + protected void encode(ChannelHandlerContext ctx, ClusterResponse response, ByteBuf out) throws Exception { + ResponseEntityWriter 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()); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/registry/RequestDataDecodeRegistry.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/registry/RequestDataDecodeRegistry.java new file mode 100644 index 00000000..df8070d6 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/registry/RequestDataDecodeRegistry.java @@ -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> DECODER_MAP = new HashMap<>(); + + public static boolean addDecoder(int type, EntityDecoder decoder) { + if (DECODER_MAP.containsKey(type)) { + return false; + } + DECODER_MAP.put(type, decoder); + return true; + } + + public static EntityDecoder getDecoder(int type) { + return (EntityDecoder)DECODER_MAP.get(type); + } + + public static boolean removeDecoder(int type) { + return DECODER_MAP.remove(type) != null; + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/registry/ResponseDataWriterRegistry.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/registry/ResponseDataWriterRegistry.java new file mode 100644 index 00000000..6b878b75 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/registry/ResponseDataWriterRegistry.java @@ -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.EntityWriter; + +import io.netty.buffer.ByteBuf; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public final class ResponseDataWriterRegistry { + + private static final Map> WRITER_MAP = new HashMap<>(); + + public static boolean addWriter(int type, EntityWriter writer) { + if (WRITER_MAP.containsKey(type)) { + return false; + } + WRITER_MAP.put(type, (EntityWriter)writer); + return true; + } + + public static EntityWriter getWriter(int type) { + return WRITER_MAP.get(type); + } + + public static boolean remove(int type) { + return WRITER_MAP.remove(type) != null; + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/FetchClusterFlowRulesCommandHandler.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/FetchClusterFlowRulesCommandHandler.java new file mode 100644 index 00000000..359dfc18 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/FetchClusterFlowRulesCommandHandler.java @@ -0,0 +1,42 @@ +/* + * 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.command.handler; + +import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager; +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.util.StringUtil; +import com.alibaba.fastjson.JSON; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +@CommandMapping(name = "cluster/server/flowRules") +public class FetchClusterFlowRulesCommandHandler implements CommandHandler { + + @Override + public CommandResponse handle(CommandRequest request) { + String namespace = request.getParam("namespace"); + if (StringUtil.isEmpty(namespace)) { + return CommandResponse.ofSuccess(JSON.toJSONString(ClusterFlowRuleManager.getAllFlowRules())); + } else { + return CommandResponse.ofSuccess(JSON.toJSONString(ClusterFlowRuleManager.getFlowRules(namespace))); + } + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/FetchClusterParamFlowRulesCommandHandler.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/FetchClusterParamFlowRulesCommandHandler.java new file mode 100644 index 00000000..5f490411 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/FetchClusterParamFlowRulesCommandHandler.java @@ -0,0 +1,42 @@ +/* + * 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.command.handler; + +import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterParamFlowRuleManager; +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.util.StringUtil; +import com.alibaba.fastjson.JSON; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +@CommandMapping(name = "cluster/server/paramRules") +public class FetchClusterParamFlowRulesCommandHandler implements CommandHandler { + + @Override + public CommandResponse handle(CommandRequest request) { + String namespace = request.getParam("namespace"); + if (StringUtil.isEmpty(namespace)) { + return CommandResponse.ofSuccess(JSON.toJSONString(ClusterParamFlowRuleManager.getAllParamRules())); + } else { + return CommandResponse.ofSuccess(JSON.toJSONString(ClusterParamFlowRuleManager.getParamRules(namespace))); + } + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/FetchClusterServerConfigHandler.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/FetchClusterServerConfigHandler.java new file mode 100644 index 00000000..72b94640 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/FetchClusterServerConfigHandler.java @@ -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.server.command.handler; + +import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; +import com.alibaba.csp.sentinel.cluster.server.config.ServerFlowConfig; +import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig; +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.util.StringUtil; +import com.alibaba.fastjson.JSONObject; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +@CommandMapping(name = "cluster/server/fetchConfig") +public class FetchClusterServerConfigHandler implements CommandHandler { + + @Override + public CommandResponse handle(CommandRequest request) { + String namespace = request.getParam("namespace"); + if (StringUtil.isEmpty(namespace)) { + return globalConfigResult(); + } + return namespaceConfigResult(namespace); + } + + private CommandResponse namespaceConfigResult(/*@NonEmpty*/ String namespace) { + ServerFlowConfig flowConfig = new ServerFlowConfig() + .setExceedCount(ClusterServerConfigManager.getExceedCount(namespace)) + .setMaxOccupyRatio(ClusterServerConfigManager.getMaxOccupyRatio(namespace)) + .setIntervalMs(ClusterServerConfigManager.getIntervalMs(namespace)) + .setSampleCount(ClusterServerConfigManager.getSampleCount(namespace)); + JSONObject config = new JSONObject() + .fluentPut("flow", flowConfig); + return CommandResponse.ofSuccess(config.toJSONString()); + } + + private CommandResponse globalConfigResult() { + ServerTransportConfig transportConfig = new ServerTransportConfig() + .setPort(ClusterServerConfigManager.getPort()) + .setIdleSeconds(ClusterServerConfigManager.getIdleSeconds()); + ServerFlowConfig flowConfig = new ServerFlowConfig() + .setExceedCount(ClusterServerConfigManager.getExceedCount()) + .setMaxOccupyRatio(ClusterServerConfigManager.getMaxOccupyRatio()) + .setIntervalMs(ClusterServerConfigManager.getIntervalMs()) + .setSampleCount(ClusterServerConfigManager.getSampleCount()); + JSONObject config = new JSONObject() + .fluentPut("transport", transportConfig) + .fluentPut("flow", flowConfig) + .fluentPut("namespaceSet", ClusterServerConfigManager.getNamespaceSet()); + return CommandResponse.ofSuccess(config.toJSONString()); + } +} + diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/FetchClusterServerInfoCommandHandler.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/FetchClusterServerInfoCommandHandler.java new file mode 100644 index 00000000..4996be25 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/FetchClusterServerInfoCommandHandler.java @@ -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.server.command.handler; + +import java.util.Set; + +import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; +import com.alibaba.csp.sentinel.cluster.server.config.ServerFlowConfig; +import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig; +import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionGroup; +import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionManager; +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.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +@CommandMapping(name = "cluster/server/info") +public class FetchClusterServerInfoCommandHandler implements CommandHandler { + + @Override + public CommandResponse handle(CommandRequest request) { + JSONObject info = new JSONObject(); + JSONArray connectionGroups = new JSONArray(); + Set namespaceSet = ClusterServerConfigManager.getNamespaceSet(); + for (String namespace : namespaceSet) { + ConnectionGroup group = ConnectionManager.getConnectionGroup(namespace); + if (group != null) { + connectionGroups.add(group); + } + } + + ServerTransportConfig transportConfig = new ServerTransportConfig() + .setPort(ClusterServerConfigManager.getPort()) + .setIdleSeconds(ClusterServerConfigManager.getIdleSeconds()); + ServerFlowConfig flowConfig = new ServerFlowConfig() + .setExceedCount(ClusterServerConfigManager.getExceedCount()) + .setMaxOccupyRatio(ClusterServerConfigManager.getMaxOccupyRatio()) + .setIntervalMs(ClusterServerConfigManager.getIntervalMs()) + .setSampleCount(ClusterServerConfigManager.getSampleCount()); + + info.fluentPut("port", ClusterServerConfigManager.getPort()) + .fluentPut("connection", connectionGroups) + .fluentPut("transport", transportConfig) + .fluentPut("flow", flowConfig) + .fluentPut("namespaceSet", ClusterServerConfigManager.getNamespaceSet()); + + return CommandResponse.ofSuccess(info.toJSONString()); + } +} + diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterFlowRulesCommandHandler.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterFlowRulesCommandHandler.java new file mode 100644 index 00000000..08882c09 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterFlowRulesCommandHandler.java @@ -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.server.command.handler; + +import java.net.URLDecoder; +import java.util.List; + +import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager; +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.log.RecordLog; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; +import com.alibaba.csp.sentinel.util.StringUtil; +import com.alibaba.fastjson.JSONArray; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +@CommandMapping(name = "cluster/server/modifyFlowRules") +public class ModifyClusterFlowRulesCommandHandler implements CommandHandler { + + @Override + public CommandResponse handle(CommandRequest request) { + String namespace = request.getParam("namespace"); + if (StringUtil.isEmpty(namespace)) { + return CommandResponse.ofFailure(new IllegalArgumentException("empty namespace")); + } + 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("[ModifyClusterFlowRulesCommandHandler] Receiving cluster flow rules for namespace <{0}>: {1}", namespace, data); + + List flowRules = JSONArray.parseArray(data, FlowRule.class); + ClusterFlowRuleManager.loadRules(namespace, flowRules); + + return CommandResponse.ofSuccess(SUCCESS); + } catch (Exception e) { + RecordLog.warn("[ModifyClusterFlowRulesCommandHandler] Decode cluster flow rules error", e); + return CommandResponse.ofFailure(e, "decode cluster flow rules error"); + } + } + + private static final String SUCCESS = "success"; +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterParamFlowRulesCommandHandler.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterParamFlowRulesCommandHandler.java new file mode 100644 index 00000000..b4632173 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterParamFlowRulesCommandHandler.java @@ -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.server.command.handler; + +import java.net.URLDecoder; +import java.util.List; + +import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterParamFlowRuleManager; +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.log.RecordLog; +import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; +import com.alibaba.csp.sentinel.util.StringUtil; +import com.alibaba.fastjson.JSONArray; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +@CommandMapping(name = "cluster/server/modifyParamRules") +public class ModifyClusterParamFlowRulesCommandHandler implements CommandHandler { + + @Override + public CommandResponse handle(CommandRequest request) { + String namespace = request.getParam("namespace"); + if (StringUtil.isEmpty(namespace)) { + return CommandResponse.ofFailure(new IllegalArgumentException("empty namespace")); + } + 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("[ModifyClusterParamFlowRulesCommandHandler] Receiving cluster param rules for namespace <{0}>: {1}", namespace, data); + + List flowRules = JSONArray.parseArray(data, ParamFlowRule.class); + ClusterParamFlowRuleManager.loadRules(namespace, flowRules); + + return CommandResponse.ofSuccess(SUCCESS); + } catch (Exception e) { + RecordLog.warn("[ModifyClusterParamFlowRulesCommandHandler] Decode cluster param rules error", e); + return CommandResponse.ofFailure(e, "decode cluster param rules error"); + } + } + + private static final String SUCCESS = "success"; +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterServerFlowConfigHandler.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterServerFlowConfigHandler.java new file mode 100644 index 00000000..9e444666 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterServerFlowConfigHandler.java @@ -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.command.handler; + +import java.net.URLDecoder; + +import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; +import com.alibaba.csp.sentinel.cluster.server.config.ServerFlowConfig; +import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig; +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.log.RecordLog; +import com.alibaba.csp.sentinel.util.StringUtil; +import com.alibaba.fastjson.JSON; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +@CommandMapping(name = "cluster/server/modifyFlowConfig") +public class ModifyClusterServerFlowConfigHandler implements CommandHandler { + + @Override + public CommandResponse handle(CommandRequest request) { + String data = request.getParam("data"); + if (StringUtil.isBlank(data)) { + return CommandResponse.ofFailure(new IllegalArgumentException("empty data")); + } + String namespace = request.getParam("namespace"); + try { + data = URLDecoder.decode(data, "utf-8"); + + if (StringUtil.isEmpty(namespace)) { + RecordLog.info("[ModifyClusterServerFlowConfigHandler] Receiving cluster server global flow config: " + data); + ServerFlowConfig config = JSON.parseObject(data, ServerFlowConfig.class); + ClusterServerConfigManager.loadGlobalFlowConfig(config); + } else { + RecordLog.info("[ModifyClusterServerFlowConfigHandler] Receiving cluster server flow config for namespace <{0}>: {1}", namespace, data); + ServerFlowConfig config = JSON.parseObject(data, ServerFlowConfig.class); + ClusterServerConfigManager.loadFlowConfig(namespace, config); + } + + return CommandResponse.ofSuccess("success"); + } catch (Exception e) { + RecordLog.warn("[ModifyClusterServerFlowConfigHandler] Decode cluster server flow config error", e); + return CommandResponse.ofFailure(e, "decode cluster server flow config error"); + } + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterServerTransportConfigHandler.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterServerTransportConfigHandler.java new file mode 100644 index 00000000..f1e267db --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterServerTransportConfigHandler.java @@ -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.server.command.handler; + +import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; +import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig; +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.util.StringUtil; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +@CommandMapping(name = "cluster/server/modifyTransportConfig") +public class ModifyClusterServerTransportConfigHandler implements CommandHandler { + + @Override + public CommandResponse handle(CommandRequest request) { + String portValue = request.getParam("port"); + if (StringUtil.isBlank(portValue)) { + return CommandResponse.ofFailure(new IllegalArgumentException("invalid empty port")); + } + String idleSecondsValue = request.getParam("idleSeconds"); + if (StringUtil.isBlank(idleSecondsValue)) { + return CommandResponse.ofFailure(new IllegalArgumentException("invalid empty idleSeconds")); + } + try { + int port = Integer.valueOf(portValue); + int idleSeconds = Integer.valueOf(idleSecondsValue); + + ClusterServerConfigManager.loadGlobalTransportConfig(new ServerTransportConfig() + .setPort(port).setIdleSeconds(idleSeconds)); + return CommandResponse.ofSuccess("success"); + } catch (NumberFormatException e) { + return CommandResponse.ofFailure(new IllegalArgumentException("invalid parameter")); + } catch (Exception ex) { + return CommandResponse.ofFailure(new IllegalArgumentException("unexpected error")); + } + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyServerNamespaceSetHandler.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyServerNamespaceSetHandler.java new file mode 100644 index 00000000..4a78600f --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyServerNamespaceSetHandler.java @@ -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.server.command.handler; + +import java.net.URLDecoder; +import java.util.Set; + +import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; +import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig; +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.log.RecordLog; +import com.alibaba.csp.sentinel.util.StringUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.TypeReference; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +@CommandMapping(name = "cluster/server/modifyNamespaceSet") +public class ModifyServerNamespaceSetHandler implements CommandHandler { + + @Override + public CommandResponse 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("[ModifyServerNamespaceSetHandler] Receiving cluster server namespace set: " + data); + Set set = JSON.parseObject(data, new TypeReference>() {}); + ClusterServerConfigManager.loadServerNamespaceSet(set); + return CommandResponse.ofSuccess("success"); + } catch (Exception e) { + RecordLog.warn("[ModifyServerNamespaceSetHandler] Decode cluster server namespace set error", e); + return CommandResponse.ofFailure(e, "decode client cluster config error"); + } + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ClusterServerConfigManager.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ClusterServerConfigManager.java new file mode 100644 index 00000000..92c8319a --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ClusterServerConfigManager.java @@ -0,0 +1,358 @@ +/* + * 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.config; + +import java.util.ArrayList; +import java.util.Collections; +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.rule.ClusterFlowRuleManager; +import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterParamFlowRuleManager; +import com.alibaba.csp.sentinel.cluster.flow.statistic.ClusterMetricStatistics; +import com.alibaba.csp.sentinel.cluster.flow.statistic.ClusterParamMetricStatistics; +import com.alibaba.csp.sentinel.cluster.server.ServerConstants; +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; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public final class ClusterServerConfigManager { + + /** + * Server global transport and scope config. + */ + private static volatile int port = ServerTransportConfig.DEFAULT_PORT; + private static volatile int idleSeconds = ServerTransportConfig.DEFAULT_IDLE_SECONDS; + private static volatile Set namespaceSet = Collections.singleton(ServerConstants.DEFAULT_NAMESPACE); + + /** + * Server global flow config. + */ + private static volatile double exceedCount = ServerFlowConfig.DEFAULT_EXCEED_COUNT; + private static volatile double maxOccupyRatio = ServerFlowConfig.DEFAULT_MAX_OCCUPY_RATIO; + private static volatile int intervalMs = ServerFlowConfig.DEFAULT_INTERVAL_MS; + private static volatile int sampleCount = ServerFlowConfig.DEFAULT_SAMPLE_COUNT; + + /** + * Namespace-specific flow config for token server. + * Format: (namespace, config). + */ + private static final Map NAMESPACE_CONF = new ConcurrentHashMap<>(); + + private static final List TRANSPORT_CONFIG_OBSERVERS = new ArrayList<>(); + + /** + * Property for cluster server global transport configuration. + */ + private static SentinelProperty transportConfigProperty = new DynamicSentinelProperty<>(); + /** + * Property for cluster server namespace set. + */ + private static SentinelProperty> namespaceSetProperty = new DynamicSentinelProperty<>(); + /** + * Property for cluster server global flow control configuration. + */ + private static SentinelProperty globalFlowProperty = new DynamicSentinelProperty<>(); + + private static final PropertyListener TRANSPORT_PROPERTY_LISTENER + = new ServerGlobalTransportPropertyListener(); + private static final PropertyListener GLOBAL_FLOW_PROPERTY_LISTENER + = new ServerGlobalFlowPropertyListener(); + private static final PropertyListener> NAMESPACE_SET_PROPERTY_LISTENER + = new ServerNamespaceSetPropertyListener(); + + static { + transportConfigProperty.addListener(TRANSPORT_PROPERTY_LISTENER); + globalFlowProperty.addListener(GLOBAL_FLOW_PROPERTY_LISTENER); + namespaceSetProperty.addListener(NAMESPACE_SET_PROPERTY_LISTENER); + } + + public static void registerNamespaceSetProperty(SentinelProperty> property) { + synchronized (NAMESPACE_SET_PROPERTY_LISTENER) { + RecordLog.info( + "[ClusterServerConfigManager] Registering new namespace set dynamic property to Sentinel server " + + "config manager"); + namespaceSetProperty.removeListener(NAMESPACE_SET_PROPERTY_LISTENER); + property.addListener(NAMESPACE_SET_PROPERTY_LISTENER); + namespaceSetProperty = property; + } + } + + public static void registerServerTransportProperty(SentinelProperty property) { + synchronized (TRANSPORT_PROPERTY_LISTENER) { + RecordLog.info( + "[ClusterServerConfigManager] Registering new server transport dynamic property to Sentinel server " + + "config manager"); + transportConfigProperty.removeListener(TRANSPORT_PROPERTY_LISTENER); + property.addListener(TRANSPORT_PROPERTY_LISTENER); + transportConfigProperty = property; + } + } + + public static void loadServerNamespaceSet(Set namespaceSet) { + namespaceSetProperty.updateValue(namespaceSet); + } + + public static void loadGlobalTransportConfig(ServerTransportConfig config) { + transportConfigProperty.updateValue(config); + } + + public static void loadGlobalFlowConfig(ServerFlowConfig config) { + globalFlowProperty.updateValue(config); + } + + /** + * Load server flow config for a specific namespace. + * + * @param namespace a valid namespace + * @param config valid flow config for the namespace + */ + public static void loadFlowConfig(String namespace, ServerFlowConfig config) { + AssertUtil.notEmpty(namespace, "namespace cannot be empty"); + // TODO: Support namespace-scope server flow config. + globalFlowProperty.updateValue(config); + } + + public static void addTransportConfigChangeObserver(ServerTransportConfigObserver observer) { + AssertUtil.notNull(observer, "observer cannot be null"); + TRANSPORT_CONFIG_OBSERVERS.add(observer); + } + + private static class ServerNamespaceSetPropertyListener implements PropertyListener> { + + @Override + public synchronized void configLoad(Set set) { + if (set == null || set.isEmpty()) { + RecordLog.warn("[ClusterServerConfigManager] WARN: empty initial server namespace set"); + return; + } + applyNamespaceSetChange(set); + } + + @Override + public synchronized void configUpdate(Set set) { + // TODO: should debounce? + applyNamespaceSetChange(set); + } + } + + private static void applyNamespaceSetChange(Set newSet) { + if (newSet == null) { + return; + } + RecordLog.info("[ClusterServerConfigManager] Server namespace set will be update to: " + newSet); + if (newSet.isEmpty()) { + ClusterServerConfigManager.namespaceSet = Collections.singleton(ServerConstants.DEFAULT_NAMESPACE); + return; + } + + newSet = new HashSet<>(newSet); + newSet.add(ServerConstants.DEFAULT_NAMESPACE); + + Set oldSet = ClusterServerConfigManager.namespaceSet; + if (oldSet != null && !oldSet.isEmpty()) { + for (String ns : oldSet) { + if (!newSet.contains(ns)) { + ClusterFlowRuleManager.removeProperty(ns); + ClusterParamFlowRuleManager.removeProperty(ns); + } + } + } + + ClusterServerConfigManager.namespaceSet = newSet; + for (String ns : newSet) { + ClusterFlowRuleManager.registerPropertyIfAbsent(ns); + ClusterParamFlowRuleManager.registerPropertyIfAbsent(ns); + } + } + + private static class ServerGlobalTransportPropertyListener implements PropertyListener { + + @Override + public void configLoad(ServerTransportConfig config) { + if (config == null) { + RecordLog.warn("[ClusterServerConfigManager] Empty initial server transport config"); + return; + } + applyConfig(config); + } + + @Override + public void configUpdate(ServerTransportConfig config) { + applyConfig(config); + } + + private synchronized void applyConfig(ServerTransportConfig config) { + if (!isValidTransportConfig(config)) { + RecordLog.warn( + "[ClusterServerConfigManager] Invalid cluster server transport config, ignoring: " + config); + return; + } + RecordLog.info("[ClusterServerConfigManager] Updating new server transport config: " + config); + if (config.getIdleSeconds() != idleSeconds) { + idleSeconds = config.getIdleSeconds(); + } + updateTokenServer(config); + } + } + + private static void updateTokenServer(ServerTransportConfig config) { + int newPort = config.getPort(); + AssertUtil.isTrue(newPort > 0, "token server port should be valid (positive)"); + if (newPort == port) { + return; + } + ClusterServerConfigManager.port = newPort; + + for (ServerTransportConfigObserver observer : TRANSPORT_CONFIG_OBSERVERS) { + observer.onTransportConfigChange(config); + } + } + + private static class ServerGlobalFlowPropertyListener implements PropertyListener { + + @Override + public void configUpdate(ServerFlowConfig config) { + applyGlobalFlowConfig(config); + } + + @Override + public void configLoad(ServerFlowConfig config) { + applyGlobalFlowConfig(config); + } + + private synchronized void applyGlobalFlowConfig(ServerFlowConfig config) { + if (!isValidFlowConfig(config)) { + RecordLog.warn( + "[ClusterServerConfigManager] Invalid cluster server global flow config, ignoring: " + config); + return; + } + RecordLog.info("[ClusterServerConfigManager] Updating new server global flow config: " + config); + if (config.getExceedCount() != exceedCount) { + exceedCount = config.getExceedCount(); + } + if (config.getMaxOccupyRatio() != maxOccupyRatio) { + maxOccupyRatio = config.getMaxOccupyRatio(); + } + int newIntervalMs = config.getIntervalMs(); + int newSampleCount = config.getSampleCount(); + if (newIntervalMs != intervalMs || newSampleCount != sampleCount) { + if (newIntervalMs <= 0 || newSampleCount <= 0 || newIntervalMs % newSampleCount != 0) { + RecordLog.warn("[ClusterServerConfigManager] Ignoring invalid flow interval or sample count"); + } else { + intervalMs = newIntervalMs; + sampleCount = newSampleCount; + // Reset all the metrics. + ClusterMetricStatistics.resetFlowMetrics(); + ClusterParamMetricStatistics.resetFlowMetrics(); + } + } + } + } + + public static boolean isValidTransportConfig(ServerTransportConfig config) { + return config != null && config.getPort() > 0 && config.getPort() <= 65535; + } + + public static boolean isValidFlowConfig(ServerFlowConfig config) { + return config != null && config.getMaxOccupyRatio() >= 0 && config.getExceedCount() >= 0; + } + + public static double getExceedCount(String namespace) { + AssertUtil.notEmpty(namespace, "namespace cannot be empty"); + ServerFlowConfig config = NAMESPACE_CONF.get(namespace); + if (config != null) { + return config.getExceedCount(); + } + return exceedCount; + } + + public static double getMaxOccupyRatio(String namespace) { + AssertUtil.notEmpty(namespace, "namespace cannot be empty"); + ServerFlowConfig config = NAMESPACE_CONF.get(namespace); + if (config != null) { + return config.getMaxOccupyRatio(); + } + return maxOccupyRatio; + } + + public static int getIntervalMs(String namespace) { + AssertUtil.notEmpty(namespace, "namespace cannot be empty"); + ServerFlowConfig config = NAMESPACE_CONF.get(namespace); + if (config != null) { + return config.getIntervalMs(); + } + return intervalMs; + } + + /** + * Get sample count of provided namespace. + * + * @param namespace valid namespace + * @return the sample count of namespace; if the namespace does not have customized value, use the global value + */ + public static int getSampleCount(String namespace) { + AssertUtil.notEmpty(namespace, "namespace cannot be empty"); + ServerFlowConfig config = NAMESPACE_CONF.get(namespace); + if (config != null) { + return config.getSampleCount(); + } + return sampleCount; + } + + public static double getExceedCount() { + return exceedCount; + } + + public static double getMaxOccupyRatio() { + return maxOccupyRatio; + } + + public static Set getNamespaceSet() { + return namespaceSet; + } + + public static int getPort() { + return port; + } + + public static int getIdleSeconds() { + return idleSeconds; + } + + public static int getIntervalMs() { + return intervalMs; + } + + public static int getSampleCount() { + return sampleCount; + } + + public static void setNamespaceSet(Set namespaceSet) { + applyNamespaceSetChange(namespaceSet); + } + + private ClusterServerConfigManager() {} +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ServerFlowConfig.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ServerFlowConfig.java new file mode 100644 index 00000000..00991605 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ServerFlowConfig.java @@ -0,0 +1,97 @@ +/* + * 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.config; + +import com.alibaba.csp.sentinel.cluster.server.ServerConstants; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public class ServerFlowConfig { + + public static final double DEFAULT_EXCEED_COUNT = 1.0d; + public static final double DEFAULT_MAX_OCCUPY_RATIO = 1.0d; + + public static final int DEFAULT_INTERVAL_MS = 1000; + public static final int DEFAULT_SAMPLE_COUNT= 10; + + private final String namespace; + + private double exceedCount = DEFAULT_EXCEED_COUNT; + private double maxOccupyRatio = DEFAULT_MAX_OCCUPY_RATIO; + private int intervalMs = DEFAULT_INTERVAL_MS; + private int sampleCount = DEFAULT_SAMPLE_COUNT; + + public ServerFlowConfig() { + this(ServerConstants.DEFAULT_NAMESPACE); + } + + public ServerFlowConfig(String namespace) { + this.namespace = namespace; + } + + public String getNamespace() { + return namespace; + } + + public double getExceedCount() { + return exceedCount; + } + + public ServerFlowConfig setExceedCount(double exceedCount) { + this.exceedCount = exceedCount; + return this; + } + + public double getMaxOccupyRatio() { + return maxOccupyRatio; + } + + public ServerFlowConfig setMaxOccupyRatio(double maxOccupyRatio) { + this.maxOccupyRatio = maxOccupyRatio; + return this; + } + + public int getIntervalMs() { + return intervalMs; + } + + public ServerFlowConfig setIntervalMs(int intervalMs) { + this.intervalMs = intervalMs; + return this; + } + + public int getSampleCount() { + return sampleCount; + } + + public ServerFlowConfig setSampleCount(int sampleCount) { + this.sampleCount = sampleCount; + return this; + } + + @Override + public String toString() { + return "ServerFlowConfig{" + + "namespace='" + namespace + '\'' + + ", exceedCount=" + exceedCount + + ", maxOccupyRatio=" + maxOccupyRatio + + ", intervalMs=" + intervalMs + + ", sampleCount=" + sampleCount + + '}'; + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ServerTransportConfig.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ServerTransportConfig.java new file mode 100644 index 00000000..74575ede --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ServerTransportConfig.java @@ -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.config; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public class ServerTransportConfig { + + public static final int DEFAULT_PORT = 8730; + public static final int DEFAULT_IDLE_SECONDS = 600; + + private int port; + private int idleSeconds; + + public ServerTransportConfig() { + this(DEFAULT_PORT, DEFAULT_IDLE_SECONDS); + } + + public ServerTransportConfig(int port, int idleSeconds) { + this.port = port; + this.idleSeconds = idleSeconds; + } + + public int getPort() { + return port; + } + + public ServerTransportConfig setPort(int port) { + this.port = port; + return this; + } + + public int getIdleSeconds() { + return idleSeconds; + } + + public ServerTransportConfig setIdleSeconds(int idleSeconds) { + this.idleSeconds = idleSeconds; + return this; + } + + @Override + public String toString() { + return "ServerTransportConfig{" + + "port=" + port + + ", idleSeconds=" + idleSeconds + + '}'; + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ServerTransportConfigObserver.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ServerTransportConfigObserver.java new file mode 100644 index 00000000..deccf380 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ServerTransportConfigObserver.java @@ -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.server.config; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public interface ServerTransportConfigObserver { + + /** + * Callback on server transport config (e.g. port) change. + * + * @param config new server transport config + */ + void onTransportConfigChange(ServerTransportConfig config); +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/Connection.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/Connection.java new file mode 100644 index 00000000..a409c661 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/Connection.java @@ -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.server.connection; + +import java.net.SocketAddress; + +/** + * @author xuyue + * @author Eric Zhao + * @since 1.4.0 + */ +public interface Connection extends AutoCloseable { + + SocketAddress getLocalAddress(); + + int getRemotePort(); + + String getRemoteIP(); + + void refreshLastReadTime(long lastReadTime); + + long getLastReadTime(); + + String getConnectionKey(); +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionDescriptor.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionDescriptor.java new file mode 100644 index 00000000..5dd107a2 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionDescriptor.java @@ -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.server.connection; + +import java.util.Objects; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public class ConnectionDescriptor { + + private String address; + private String host; + + public String getAddress() { + return address; + } + + public ConnectionDescriptor setAddress(String address) { + this.address = address; + return this; + } + + public String getHost() { + return host; + } + + public ConnectionDescriptor setHost(String host) { + this.host = host; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) { return true; } + if (o == null || getClass() != o.getClass()) { return false; } + + ConnectionDescriptor that = (ConnectionDescriptor)o; + + return Objects.equals(address, that.address); + } + + @Override + public int hashCode() { + return address != null ? address.hashCode() : 0; + } + + @Override + public String toString() { + return "ConnectionDescriptor{" + + "address='" + address + '\'' + + ", host='" + host + '\'' + + '}'; + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionGroup.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionGroup.java new file mode 100644 index 00000000..c4f84719 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionGroup.java @@ -0,0 +1,86 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.server.connection; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ConcurrentSkipListSet; +import java.util.concurrent.atomic.AtomicInteger; + +import com.alibaba.csp.sentinel.cluster.server.ServerConstants; +import com.alibaba.csp.sentinel.util.AssertUtil; + +/** + * The connection group stores connection set for a specific namespace. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public class ConnectionGroup { + + private final String namespace; + + private final Set connectionSet = Collections.synchronizedSet(new HashSet()); + private final AtomicInteger connectedCount = new AtomicInteger(); + + public ConnectionGroup(String namespace) { + AssertUtil.notEmpty(namespace, "namespace cannot be empty"); + this.namespace = namespace; + } + + public ConnectionGroup() { + this(ServerConstants.DEFAULT_NAMESPACE); + } + + public ConnectionGroup addConnection(String address) { + AssertUtil.notEmpty(address, "address cannot be empty"); + + String[] ip = address.split(":"); + String host; + if (ip != null && ip.length >= 1) { + host = ip[0]; + } else { + host = address; + } + connectionSet.add(new ConnectionDescriptor().setAddress(address).setHost(host)); + connectedCount.incrementAndGet(); + + return this; + } + + public ConnectionGroup removeConnection(String address) { + AssertUtil.notEmpty(address, "address cannot be empty"); + + if (connectionSet.remove(new ConnectionDescriptor().setAddress(address))) { + connectedCount.decrementAndGet(); + } + + return this; + } + + public String getNamespace() { + return namespace; + } + + public Set getConnectionSet() { + return connectionSet; + } + + public int getConnectedCount() { + return connectedCount.get(); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionManager.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionManager.java new file mode 100644 index 00000000..5bbbaeaa --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionManager.java @@ -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.server.connection; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.alibaba.csp.sentinel.log.RecordLog; +import com.alibaba.csp.sentinel.util.AssertUtil; + +/** + * Manager for namespace-scope {@link ConnectionGroup}. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public final class ConnectionManager { + + /** + * Connection map (namespace, connection). + */ + private static final Map CONN_MAP = new ConcurrentHashMap<>(); + /** + * namespace map (address, namespace). + */ + private static final Map NAMESPACE_MAP = new ConcurrentHashMap<>(); + + /** + * Get connected count for specific namespace. + * + * @param namespace namespace to check + * @return connected count for specific namespace + */ + public static int getConnectedCount(String namespace) { + AssertUtil.notEmpty(namespace, "namespace should not be empty"); + ConnectionGroup group = CONN_MAP.get(namespace); + return group == null ? 0 : group.getConnectedCount(); + } + + public static ConnectionGroup getOrCreateGroup(String namespace) { + AssertUtil.assertNotBlank(namespace, "namespace should not be empty"); + ConnectionGroup group = CONN_MAP.get(namespace); + if (group == null) { + synchronized (CREATE_LOCK) { + if (CONN_MAP.get(namespace) == null) { + group = new ConnectionGroup(namespace); + CONN_MAP.put(namespace, group); + } + } + } + return group; + } + + public static void removeConnection(String address) { + AssertUtil.assertNotBlank(address, "address should not be empty"); + String namespace = NAMESPACE_MAP.get(address); + if (namespace != null) { + ConnectionGroup group = CONN_MAP.get(namespace); + if (group == null) { + return; + } + group.removeConnection(address); + RecordLog.info("[ConnectionManager] Client <{0}> disconnected and removed from namespace <{1}>", address, namespace); + } + NAMESPACE_MAP.remove(address); + } + + public static void removeConnection(String namespace, String address) { + AssertUtil.assertNotBlank(namespace, "namespace should not be empty"); + AssertUtil.assertNotBlank(address, "address should not be empty"); + ConnectionGroup group = CONN_MAP.get(namespace); + if (group == null) { + return; + } + group.removeConnection(address); + NAMESPACE_MAP.remove(address); + RecordLog.info("[ConnectionManager] Client <{0}> disconnected and removed from namespace <{1}>", address, namespace); + } + + public static ConnectionGroup addConnection(String namespace, String address) { + AssertUtil.assertNotBlank(namespace, "namespace should not be empty"); + AssertUtil.assertNotBlank(address, "address should not be empty"); + ConnectionGroup group = getOrCreateGroup(namespace); + group.addConnection(address); + NAMESPACE_MAP.put(address, namespace); + RecordLog.info("[ConnectionManager] Client <{0}> registered with namespace <{1}>", address, namespace); + return group; + } + + public static ConnectionGroup getConnectionGroup(String namespace) { + AssertUtil.assertNotBlank(namespace, "namespace should not be empty"); + ConnectionGroup group = getOrCreateGroup(namespace); + return group; + } + + private static final Object CREATE_LOCK = new Object(); + + private ConnectionManager() {} +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionPool.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionPool.java new file mode 100644 index 00000000..e1e320ce --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionPool.java @@ -0,0 +1,138 @@ +/* + * 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.connection; + +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import com.alibaba.csp.sentinel.log.RecordLog; + +import io.netty.channel.Channel; + +/** + * Universal connection pool for connection management. + * + * @author xuyue + * @author Eric Zhao + * @since 1.4.0 + */ +public class ConnectionPool { + + private static final ScheduledExecutorService TIMER = Executors.newScheduledThreadPool(2); + + /** + * Format: ("ip:port", connection) + */ + private final Map CONNECTION_MAP = new ConcurrentHashMap(); + + /** + * Periodic scan task. + */ + private ScheduledFuture scanTaskFuture = null; + + public void createConnection(Channel channel) { + if (channel != null) { + Connection connection = new NettyConnection(channel, this); + + String connKey = getConnectionKey(channel); + CONNECTION_MAP.put(connKey, connection); + } + } + + /** + * Start the scan task for long-idle connections. + */ + private synchronized void startScan() { + if (scanTaskFuture == null + || scanTaskFuture.isCancelled() + || scanTaskFuture.isDone()) { + scanTaskFuture = TIMER.scheduleAtFixedRate( + new ScanIdleConnectionTask(this), 10, 30, TimeUnit.SECONDS); + } + } + + /** + * Format to "ip:port". + * + * @param channel channel + * @return formatted key + */ + private String getConnectionKey(Channel channel) { + InetSocketAddress socketAddress = (InetSocketAddress)channel.remoteAddress(); + String remoteIp = socketAddress.getAddress().getHostAddress(); + int remotePort = socketAddress.getPort(); + return remoteIp + ":" + remotePort; + } + + private String getConnectionKey(String ip, int port) { + return ip + ":" + port; + } + + public void refreshLastReadTime(Channel channel) { + if (channel != null) { + String connKey = getConnectionKey(channel); + Connection connection = CONNECTION_MAP.get(connKey); + if (connection != null) { + connection.refreshLastReadTime(System.currentTimeMillis()); + } + } + } + + public Connection getConnection(String remoteIp, int remotePort) { + String connKey = getConnectionKey(remoteIp, remotePort); + return CONNECTION_MAP.get(connKey); + } + + public void remove(Channel channel) { + String connKey = getConnectionKey(channel); + CONNECTION_MAP.remove(connKey); + } + + public List listAllConnection() { + List connections = new ArrayList(CONNECTION_MAP.values()); + return connections; + } + + public int count() { + return CONNECTION_MAP.size(); + } + + public void clear() { + CONNECTION_MAP.clear(); + } + + public void shutdownAll() throws Exception { + for (Connection c : CONNECTION_MAP.values()) { + c.close(); + } + } + + public void refreshIdleTask() { + if (scanTaskFuture == null || scanTaskFuture.cancel(false)) { + startScan(); + } else { + RecordLog.info("The result of canceling scanTask is error."); + } + } +} + diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/NettyConnection.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/NettyConnection.java new file mode 100644 index 00000000..a3351ced --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/NettyConnection.java @@ -0,0 +1,86 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.server.connection; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; + +import io.netty.channel.Channel; + +/** + * @author xuyue + * @since 1.4.0 + */ +public class NettyConnection implements Connection { + + private String remoteIp; + private int remotePort; + private Channel channel; + + private long lastReadTime; + + private ConnectionPool pool; + + public NettyConnection(Channel channel, ConnectionPool pool) { + this.channel = channel; + this.pool = pool; + + InetSocketAddress socketAddress = (InetSocketAddress) channel.remoteAddress(); + this.remoteIp = socketAddress.getAddress().getHostAddress(); + this.remotePort = socketAddress.getPort(); + this.lastReadTime = System.currentTimeMillis(); + } + + @Override + public SocketAddress getLocalAddress() { + return channel.localAddress(); + } + + @Override + public int getRemotePort() { + return remotePort; + } + + @Override + public String getRemoteIP() { + return remoteIp; + } + + @Override + public void refreshLastReadTime(long lastReadTime) { + this.lastReadTime = lastReadTime; + } + + @Override + public long getLastReadTime() { + return lastReadTime; + } + + @Override + public String getConnectionKey() { + return remoteIp + ":" + remotePort; + } + + @Override + public void close() { + // Remove from connection pool. + pool.remove(channel); + // Close the connection. + if (channel != null && channel.isActive()){ + channel.close(); + } + } +} \ No newline at end of file diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ScanIdleConnectionTask.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ScanIdleConnectionTask.java new file mode 100644 index 00000000..a559aeeb --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ScanIdleConnectionTask.java @@ -0,0 +1,45 @@ +package com.alibaba.csp.sentinel.cluster.server.connection; + +import java.util.List; + +import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; +import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig; +import com.alibaba.csp.sentinel.log.RecordLog; + +/** + * @author xuyue + * @author Eric Zhao + * @since 1.4.0 + */ +public class ScanIdleConnectionTask implements Runnable { + + private final ConnectionPool connectionPool; + + public ScanIdleConnectionTask(ConnectionPool connectionPool) { + this.connectionPool = connectionPool; + } + + @Override + public void run() { + try { + int idleSeconds = ClusterServerConfigManager.getIdleSeconds(); + long idleTimeMillis = idleSeconds * 1000; + if (idleTimeMillis < 0) { + idleTimeMillis = ServerTransportConfig.DEFAULT_IDLE_SECONDS * 1000; + } + long now = System.currentTimeMillis(); + List connections = connectionPool.listAllConnection(); + for (Connection conn : connections) { + if ((now - conn.getLastReadTime()) > idleTimeMillis) { + RecordLog.info( + String.format("[ScanIdleConnectionTask] The connection <%s:%d> has been idle for <%d>s. " + + "It will be closed now.", conn.getRemoteIP(), conn.getRemotePort(), idleSeconds) + ); + conn.close(); + } + } + } catch (Throwable t) { + RecordLog.warn("[ScanIdleConnectionTask] Failed to clean-up idle tasks", t); + } + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/handler/TokenServerHandler.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/handler/TokenServerHandler.java new file mode 100644 index 00000000..b230f30c --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/handler/TokenServerHandler.java @@ -0,0 +1,110 @@ +/* + * 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.handler; + +import com.alibaba.csp.sentinel.cluster.ClusterConstants; +import com.alibaba.csp.sentinel.cluster.request.ClusterRequest; +import com.alibaba.csp.sentinel.cluster.response.ClusterResponse; +import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionManager; +import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionPool; +import com.alibaba.csp.sentinel.cluster.server.processor.RequestProcessor; +import com.alibaba.csp.sentinel.cluster.server.processor.RequestProcessorProvider; +import com.alibaba.csp.sentinel.log.RecordLog; +import com.alibaba.csp.sentinel.util.StringUtil; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; + +/** + * Netty server handler for Sentinel token server. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public class TokenServerHandler extends ChannelInboundHandlerAdapter { + + private final ConnectionPool globalConnectionPool; + + public TokenServerHandler(ConnectionPool globalConnectionPool) { + this.globalConnectionPool = globalConnectionPool; + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + globalConnectionPool.createConnection(ctx.channel()); + String remoteAddress = getRemoteAddress(ctx); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + String remoteAddress = getRemoteAddress(ctx); + globalConnectionPool.remove(ctx.channel()); + ConnectionManager.removeConnection(remoteAddress); + } + + @Override + @SuppressWarnings("unchecked") + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + globalConnectionPool.refreshLastReadTime(ctx.channel()); + if (msg instanceof ClusterRequest) { + ClusterRequest request = (ClusterRequest)msg; + + // Client ping with its namespace, add to connection manager. + if (request.getType() == ClusterConstants.MSG_TYPE_PING) { + handlePingRequest(ctx, request); + return; + } + + // Pick request processor for request type. + RequestProcessor processor = RequestProcessorProvider.getProcessor(request.getType()); + if (processor == null) { + RecordLog.warn("[TokenServerHandler] No processor for request type: " + request.getType()); + writeBadResponse(ctx, request); + } else { + ClusterResponse response = processor.processRequest(request); + writeResponse(ctx, response); + } + } + } + + private void writeBadResponse(ChannelHandlerContext ctx, ClusterRequest request) { + ClusterResponse response = new ClusterResponse<>(request.getId(), request.getType(), + ClusterConstants.RESPONSE_STATUS_BAD, null); + writeResponse(ctx, response); + } + + private void writeResponse(ChannelHandlerContext ctx, ClusterResponse response) { + ctx.writeAndFlush(response); + } + + private void handlePingRequest(ChannelHandlerContext ctx, ClusterRequest request) { + if (request.getData() == null || StringUtil.isBlank((String)request.getData())) { + writeBadResponse(ctx, request); + return; + } + String namespace = (String)request.getData(); + String clientAddress = getRemoteAddress(ctx); + // Add the remote namespace to connection manager. + int curCount = ConnectionManager.addConnection(namespace, clientAddress).getConnectedCount(); + int status = ClusterConstants.RESPONSE_STATUS_OK; + ClusterResponse response = new ClusterResponse<>(request.getId(), request.getType(), status, curCount); + writeResponse(ctx, response); + } + + private String getRemoteAddress(ChannelHandlerContext ctx) { + return ctx.channel().remoteAddress().toString(); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/init/DefaultClusterServerInitFunc.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/init/DefaultClusterServerInitFunc.java new file mode 100644 index 00000000..82b3bdc3 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/init/DefaultClusterServerInitFunc.java @@ -0,0 +1,66 @@ +/* + * 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.init; + +import com.alibaba.csp.sentinel.cluster.ClusterConstants; +import com.alibaba.csp.sentinel.cluster.server.TokenServiceProvider; +import com.alibaba.csp.sentinel.cluster.server.codec.data.FlowRequestDataDecoder; +import com.alibaba.csp.sentinel.cluster.server.codec.data.FlowResponseDataWriter; +import com.alibaba.csp.sentinel.cluster.server.codec.data.ParamFlowRequestDataDecoder; +import com.alibaba.csp.sentinel.cluster.server.codec.data.PingRequestDataDecoder; +import com.alibaba.csp.sentinel.cluster.server.codec.data.PingResponseDataWriter; +import com.alibaba.csp.sentinel.cluster.server.codec.registry.RequestDataDecodeRegistry; +import com.alibaba.csp.sentinel.cluster.server.codec.registry.ResponseDataWriterRegistry; +import com.alibaba.csp.sentinel.cluster.server.processor.RequestProcessorProvider; +import com.alibaba.csp.sentinel.init.InitFunc; +import com.alibaba.csp.sentinel.log.RecordLog; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public class DefaultClusterServerInitFunc implements InitFunc { + + @Override + public void init() throws Exception { + initDefaultEntityDecoders(); + initDefaultEntityWriters(); + + initDefaultProcessors(); + + // Eagerly-trigger the SPI pre-load of token service. + TokenServiceProvider.getService(); + + RecordLog.info("[DefaultClusterServerInitFunc] Default entity codec and processors registered"); + } + + private void initDefaultEntityWriters() { + ResponseDataWriterRegistry.addWriter(ClusterConstants.MSG_TYPE_PING, new PingResponseDataWriter()); + ResponseDataWriterRegistry.addWriter(ClusterConstants.MSG_TYPE_FLOW, new FlowResponseDataWriter()); + ResponseDataWriterRegistry.addWriter(ClusterConstants.MSG_TYPE_PARAM_FLOW, new FlowResponseDataWriter()); + } + + private void initDefaultEntityDecoders() { + RequestDataDecodeRegistry.addDecoder(ClusterConstants.MSG_TYPE_PING, new PingRequestDataDecoder()); + RequestDataDecodeRegistry.addDecoder(ClusterConstants.MSG_TYPE_FLOW, new FlowRequestDataDecoder()); + RequestDataDecodeRegistry.addDecoder(ClusterConstants.MSG_TYPE_PARAM_FLOW, new ParamFlowRequestDataDecoder()); + } + + private void initDefaultProcessors() { + // Eagerly-trigger the SPI pre-load. + RequestProcessorProvider.getProcessor(0); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/log/ClusterServerStatLogUtil.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/log/ClusterServerStatLogUtil.java new file mode 100644 index 00000000..f97441ca --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/log/ClusterServerStatLogUtil.java @@ -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.server.log; + +import com.alibaba.csp.sentinel.eagleeye.EagleEye; +import com.alibaba.csp.sentinel.eagleeye.StatLogger; +import com.alibaba.csp.sentinel.log.LogBase; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public final class ClusterServerStatLogUtil { + + private static final String FILE_NAME = "sentinel-server.log"; + + private static StatLogger statLogger; + + static { + String path = LogBase.getLogBaseDir() + FILE_NAME; + + statLogger = EagleEye.statLoggerBuilder("sentinel-cluster-server-record") + .intervalSeconds(1) + .entryDelimiter('|') + .keyDelimiter(',') + .valueDelimiter(',') + .maxEntryCount(5000) + .configLogFilePath(path) + .maxFileSizeMB(300) + .maxBackupIndex(3) + .buildSingleton(); + } + + public static void log(String msg) { + statLogger.stat(msg).count(); + } + + public static void log(String msg, int count) { + statLogger.stat(msg).count(count); + } + + private ClusterServerStatLogUtil() {} +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/FlowRequestProcessor.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/FlowRequestProcessor.java new file mode 100644 index 00000000..d08a84b3 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/FlowRequestProcessor.java @@ -0,0 +1,54 @@ +/* + * 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.processor; + +import com.alibaba.csp.sentinel.cluster.ClusterConstants; +import com.alibaba.csp.sentinel.cluster.TokenResult; +import com.alibaba.csp.sentinel.cluster.TokenService; +import com.alibaba.csp.sentinel.cluster.annotation.RequestType; +import com.alibaba.csp.sentinel.cluster.request.ClusterRequest; +import com.alibaba.csp.sentinel.cluster.request.data.FlowRequestData; +import com.alibaba.csp.sentinel.cluster.response.ClusterResponse; +import com.alibaba.csp.sentinel.cluster.response.data.FlowTokenResponseData; +import com.alibaba.csp.sentinel.cluster.server.TokenServiceProvider; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +@RequestType(ClusterConstants.MSG_TYPE_FLOW) +public class FlowRequestProcessor implements RequestProcessor { + + @Override + public ClusterResponse processRequest(ClusterRequest request) { + TokenService tokenService = TokenServiceProvider.getService(); + + long flowId = request.getData().getFlowId(); + int count = request.getData().getCount(); + boolean prioritized = request.getData().isPriority(); + + TokenResult result = tokenService.requestToken(flowId, count, prioritized); + return toResponse(result, request); + } + + private ClusterResponse toResponse(TokenResult result, ClusterRequest request) { + return new ClusterResponse<>(request.getId(), request.getType(), result.getStatus(), + new FlowTokenResponseData() + .setRemainingCount(result.getRemaining()) + .setWaitInMs(result.getWaitInMs()) + ); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/ParamFlowRequestProcessor.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/ParamFlowRequestProcessor.java new file mode 100644 index 00000000..888fc2b5 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/ParamFlowRequestProcessor.java @@ -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.server.processor; + +import java.util.Collection; + +import com.alibaba.csp.sentinel.cluster.ClusterConstants; +import com.alibaba.csp.sentinel.cluster.TokenResult; +import com.alibaba.csp.sentinel.cluster.TokenService; +import com.alibaba.csp.sentinel.cluster.annotation.RequestType; +import com.alibaba.csp.sentinel.cluster.request.ClusterRequest; +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.cluster.server.TokenServiceProvider; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +@RequestType(ClusterConstants.MSG_TYPE_PARAM_FLOW) +public class ParamFlowRequestProcessor implements RequestProcessor { + + @Override + public ClusterResponse processRequest(ClusterRequest request) { + TokenService tokenService = TokenServiceProvider.getService(); + + long flowId = request.getData().getFlowId(); + int count = request.getData().getCount(); + Collection args = request.getData().getParams(); + + TokenResult result = tokenService.requestParamToken(flowId, count, args); + return toResponse(result, request); + } + + private ClusterResponse toResponse(TokenResult result, ClusterRequest request) { + return new ClusterResponse<>(request.getId(), request.getType(), result.getStatus(), + new FlowTokenResponseData() + .setRemainingCount(result.getRemaining()) + .setWaitInMs(0) + ); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/RequestProcessor.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/RequestProcessor.java new file mode 100644 index 00000000..8cce73ea --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/RequestProcessor.java @@ -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.server.processor; + +import com.alibaba.csp.sentinel.cluster.request.ClusterRequest; +import com.alibaba.csp.sentinel.cluster.response.ClusterResponse; + +/** + * Interface of cluster request processor. + * + * @param type of request body + * @param type of response body + * @author Eric Zhao + * @since 1.4.0 + */ +public interface RequestProcessor { + + /** + * Process the cluster request. + * + * @param request Sentinel cluster request + * @return the response after processed + */ + ClusterResponse processRequest(ClusterRequest request); +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/RequestProcessorProvider.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/RequestProcessorProvider.java new file mode 100644 index 00000000..bd572f27 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/RequestProcessorProvider.java @@ -0,0 +1,76 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.server.processor; + +import java.util.Map; +import java.util.ServiceLoader; +import java.util.concurrent.ConcurrentHashMap; + +import com.alibaba.csp.sentinel.cluster.annotation.RequestType; +import com.alibaba.csp.sentinel.util.AssertUtil; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public final class RequestProcessorProvider { + + private static final Map PROCESSOR_MAP = new ConcurrentHashMap<>(); + + private static final ServiceLoader SERVICE_LOADER = ServiceLoader.load(RequestProcessor.class); + + static { + loadAndInit(); + } + + private static void loadAndInit() { + for (RequestProcessor processor : SERVICE_LOADER) { + Integer type = parseRequestType(processor); + if (type != null) { + PROCESSOR_MAP.put(type, processor); + } + } + } + + private static Integer parseRequestType(RequestProcessor processor) { + RequestType requestType = processor.getClass().getAnnotation(RequestType.class); + if (requestType != null) { + return requestType.value(); + } else { + return null; + } + } + + public static RequestProcessor getProcessor(int type) { + return PROCESSOR_MAP.get(type); + } + + static void addProcessorIfAbsent(int type, RequestProcessor processor) { + // TBD: use putIfAbsent in JDK 1.8. + if (PROCESSOR_MAP.containsKey(type)) { + return; + } + PROCESSOR_MAP.put(type, processor); + } + + static void addProcessor(int type, RequestProcessor processor) { + AssertUtil.notNull(processor, "processor cannot be null"); + PROCESSOR_MAP.put(type, processor); + } + + + private RequestProcessorProvider() {} +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/util/ClusterRuleUtil.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/util/ClusterRuleUtil.java new file mode 100644 index 00000000..485f2adc --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/util/ClusterRuleUtil.java @@ -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.server.util; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public final class ClusterRuleUtil { + + public static boolean validId(Long id) { + return id != null && id > 0; + } + + private ClusterRuleUtil() {} +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.TokenService b/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.TokenService new file mode 100644 index 00000000..d246cf01 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.TokenService @@ -0,0 +1 @@ +com.alibaba.csp.sentinel.cluster.flow.DefaultTokenService \ No newline at end of file diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityDecoder b/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityDecoder new file mode 100644 index 00000000..89ba536e --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityDecoder @@ -0,0 +1 @@ +com.alibaba.csp.sentinel.cluster.server.codec.DefaultRequestEntityDecoder \ No newline at end of file diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityWriter b/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityWriter new file mode 100644 index 00000000..f8464a8f --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityWriter @@ -0,0 +1 @@ +com.alibaba.csp.sentinel.cluster.server.codec.DefaultResponseEntityWriter \ No newline at end of file diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.server.EmbeddedClusterTokenServer b/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.server.EmbeddedClusterTokenServer new file mode 100644 index 00000000..ccfb81e4 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.server.EmbeddedClusterTokenServer @@ -0,0 +1 @@ +com.alibaba.csp.sentinel.cluster.server.DefaultEmbeddedTokenServer \ No newline at end of file diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.server.processor.RequestProcessor b/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.server.processor.RequestProcessor new file mode 100644 index 00000000..991b781d --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.server.processor.RequestProcessor @@ -0,0 +1,2 @@ +com.alibaba.csp.sentinel.cluster.server.processor.FlowRequestProcessor +com.alibaba.csp.sentinel.cluster.server.processor.ParamFlowRequestProcessor \ No newline at end of file diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler b/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler new file mode 100755 index 00000000..5fe1f5b8 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler @@ -0,0 +1,9 @@ +com.alibaba.csp.sentinel.cluster.server.command.handler.ModifyClusterServerFlowConfigHandler +com.alibaba.csp.sentinel.cluster.server.command.handler.FetchClusterFlowRulesCommandHandler +com.alibaba.csp.sentinel.cluster.server.command.handler.FetchClusterParamFlowRulesCommandHandler +com.alibaba.csp.sentinel.cluster.server.command.handler.FetchClusterServerConfigHandler +com.alibaba.csp.sentinel.cluster.server.command.handler.ModifyClusterServerTransportConfigHandler +com.alibaba.csp.sentinel.cluster.server.command.handler.ModifyServerNamespaceSetHandler +com.alibaba.csp.sentinel.cluster.server.command.handler.ModifyClusterFlowRulesCommandHandler +com.alibaba.csp.sentinel.cluster.server.command.handler.ModifyClusterParamFlowRulesCommandHandler +com.alibaba.csp.sentinel.cluster.server.command.handler.FetchClusterServerInfoCommandHandler \ No newline at end of file diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc b/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc new file mode 100755 index 00000000..95b8c9db --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc @@ -0,0 +1 @@ +com.alibaba.csp.sentinel.cluster.server.init.DefaultClusterServerInitFunc \ No newline at end of file diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/ClusterFlowTestUtil.java b/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/ClusterFlowTestUtil.java new file mode 100644 index 00000000..cfb0e7d9 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/ClusterFlowTestUtil.java @@ -0,0 +1,54 @@ +/* + * 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 static org.junit.Assert.*; + +/** + * Useful for testing clustered flow control. + * Only used for test. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public final class ClusterFlowTestUtil { + + public static void assertResultPass(TokenResult result) { + assertNotNull(result); + assertEquals(TokenResultStatus.OK, (int) result.getStatus()); + } + + public static void assertResultBlock(TokenResult result) { + assertNotNull(result); + assertEquals(TokenResultStatus.BLOCKED, (int) result.getStatus()); + } + + public static void assertResultWait(TokenResult result, int waitInMs) { + assertNotNull(result); + assertEquals(TokenResultStatus.SHOULD_WAIT, (int) result.getStatus()); + assertEquals(waitInMs, result.getWaitInMs()); + } + + public static void sleep(int t) { + try { + Thread.sleep(t); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + private ClusterFlowTestUtil() {} +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowCheckerTest.java b/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowCheckerTest.java new file mode 100644 index 00000000..970e9fca --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowCheckerTest.java @@ -0,0 +1,78 @@ +/* + * 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.flow.statistic.ClusterMetricStatistics; +import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterMetric; +import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant; +import com.alibaba.csp.sentinel.slots.block.flow.ClusterFlowConfig; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; + +import org.junit.Ignore; +import org.junit.Test; + +import static org.junit.Assert.*; +import static com.alibaba.csp.sentinel.cluster.ClusterFlowTestUtil.*; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public class ClusterFlowCheckerTest { + + //@Test + public void testAcquireClusterTokenOccupyPass() { + long flowId = 98765L; + final int threshold = 5; + FlowRule clusterRule = new FlowRule("abc") + .setCount(threshold) + .setClusterMode(true) + .setClusterConfig(new ClusterFlowConfig() + .setFlowId(flowId) + .setThresholdType(ClusterRuleConstant.FLOW_THRESHOLD_GLOBAL)); + int sampleCount = 5; + int intervalInMs = 1000; + int bucketLength = intervalInMs / sampleCount; + ClusterMetric metric = new ClusterMetric(sampleCount, intervalInMs); + ClusterMetricStatistics.putMetric(flowId, metric); + + System.out.println(System.currentTimeMillis()); + assertResultPass(tryAcquire(clusterRule, false)); + assertResultPass(tryAcquire(clusterRule, false)); + sleep(bucketLength); + assertResultPass(tryAcquire(clusterRule, false)); + sleep(bucketLength); + assertResultPass(tryAcquire(clusterRule, true)); + assertResultPass(tryAcquire(clusterRule, false)); + assertResultBlock(tryAcquire(clusterRule, true)); + sleep(bucketLength); + assertResultBlock(tryAcquire(clusterRule, false)); + assertResultBlock(tryAcquire(clusterRule, false)); + sleep(bucketLength); + assertResultBlock(tryAcquire(clusterRule, false)); + assertResultWait(tryAcquire(clusterRule, true), bucketLength); + assertResultBlock(tryAcquire(clusterRule, false)); + sleep(bucketLength); + assertResultPass(tryAcquire(clusterRule, false)); + + ClusterMetricStatistics.removeMetric(flowId); + } + + private TokenResult tryAcquire(FlowRule clusterRule, boolean occupy) { + return ClusterFlowChecker.acquireClusterToken(clusterRule, 1, occupy); + } +} \ No newline at end of file diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterStateManager.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterStateManager.java new file mode 100644 index 00000000..4a4b1334 --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterStateManager.java @@ -0,0 +1,239 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster; + +import com.alibaba.csp.sentinel.cluster.client.ClusterTokenClient; +import com.alibaba.csp.sentinel.cluster.client.TokenClientProvider; +import com.alibaba.csp.sentinel.cluster.server.EmbeddedClusterTokenServer; +import com.alibaba.csp.sentinel.cluster.server.EmbeddedClusterTokenServerProvider; +import com.alibaba.csp.sentinel.init.InitExecutor; +import com.alibaba.csp.sentinel.log.RecordLog; +import com.alibaba.csp.sentinel.property.DynamicSentinelProperty; +import com.alibaba.csp.sentinel.property.PropertyListener; +import com.alibaba.csp.sentinel.property.SentinelProperty; +import com.alibaba.csp.sentinel.util.TimeUtil; + +/** + *

Global tate manager for Sentinel cluster. This enables switching between cluster client and server.

+ * + * @author Eric Zhao + * @since 1.4.0 + */ +public final class ClusterStateManager { + + public static final int CLUSTER_CLIENT = 0; + public static final int CLUSTER_SERVER = 1; + + private static volatile int mode = -1; + private static volatile long lastModified = -1; + + private static volatile SentinelProperty stateProperty = new DynamicSentinelProperty(); + private static final PropertyListener PROPERTY_LISTENER = new ClusterStatePropertyListener(); + + private static final Object UPDATE_LOCK = new Object(); + + static { + InitExecutor.doInit(); + stateProperty.addListener(PROPERTY_LISTENER); + } + + public static void registerProperty(SentinelProperty property) { + synchronized (PROPERTY_LISTENER) { + RecordLog.info("[ClusterStateManager] Registering new property to cluster state manager"); + stateProperty.removeListener(PROPERTY_LISTENER); + property.addListener(PROPERTY_LISTENER); + stateProperty = property; + } + } + + public static int getMode() { + return mode; + } + + public static boolean isClient() { + return mode == CLUSTER_CLIENT; + } + + public static boolean isServer() { + return mode == CLUSTER_SERVER; + } + + /** + *

+ * Set current mode to client mode. If Sentinel currently works in server mode, + * it will be turned off. Then the cluster client will be started. + *

+ */ + public static boolean setToClient() { + if (mode == CLUSTER_CLIENT) { + return true; + } + mode = CLUSTER_CLIENT; + sleepIfNeeded(); + lastModified = TimeUtil.currentTimeMillis(); + return startClient(); + } + + private static boolean startClient() { + try { + EmbeddedClusterTokenServer server = EmbeddedClusterTokenServerProvider.getServer(); + if (server != null) { + server.stop(); + } + ClusterTokenClient tokenClient = TokenClientProvider.getClient(); + if (tokenClient != null) { + tokenClient.start(); + RecordLog.info("[ClusterStateManager] Changing cluster mode to client"); + return true; + } else { + RecordLog.warn("[ClusterStateManager] Cannot change to client (no client SPI found)"); + return false; + } + } catch (Exception ex) { + RecordLog.warn("[ClusterStateManager] Error when changing cluster mode to client", ex); + return false; + } + } + + private static boolean stopClient() { + try { + ClusterTokenClient tokenClient = TokenClientProvider.getClient(); + if (tokenClient != null) { + tokenClient.stop(); + RecordLog.info("[ClusterStateManager] Stopping the cluster token client"); + return true; + } else { + RecordLog.warn("[ClusterStateManager] Cannot stop cluster token client (no server SPI found)"); + return false; + } + } catch (Exception ex) { + RecordLog.warn("[ClusterStateManager] Error when stopping cluster token client", ex); + return false; + } + } + + /** + *

+ * Set current mode to server mode. If Sentinel currently works in client mode, + * it will be turned off. Then the cluster server will be started. + *

+ */ + public static boolean setToServer() { + if (mode == CLUSTER_SERVER) { + return true; + } + mode = CLUSTER_SERVER; + sleepIfNeeded(); + lastModified = TimeUtil.currentTimeMillis(); + return startServer(); + } + + private static boolean startServer() { + try { + ClusterTokenClient tokenClient = TokenClientProvider.getClient(); + if (tokenClient != null) { + tokenClient.stop(); + } + EmbeddedClusterTokenServer server = EmbeddedClusterTokenServerProvider.getServer(); + if (server != null) { + server.start(); + RecordLog.info("[ClusterStateManager] Changing cluster mode to server"); + return true; + } else { + RecordLog.warn("[ClusterStateManager] Cannot change to server (no server SPI found)"); + return false; + } + } catch (Exception ex) { + RecordLog.warn("[ClusterStateManager] Error when changing cluster mode to server", ex); + return false; + } + } + + private static boolean stopServer() { + try { + EmbeddedClusterTokenServer server = EmbeddedClusterTokenServerProvider.getServer(); + if (server != null) { + server.stop(); + RecordLog.info("[ClusterStateManager] Stopping the cluster server"); + return true; + } else { + RecordLog.warn("[ClusterStateManager] Cannot stop server (no server SPI found)"); + return false; + } + } catch (Exception ex) { + RecordLog.warn("[ClusterStateManager] Error when stopping server", ex); + return false; + } + } + + /** + * The interval between two change operations should be greater than {@code MIN_INTERVAL} (by default 10s). + * Or we need to wait for a while. + */ + private static void sleepIfNeeded() { + if (lastModified <= 0) { + return; + } + long now = TimeUtil.currentTimeMillis(); + long durationPast = now - lastModified; + long estimated = durationPast - MIN_INTERVAL; + if (estimated < 0) { + try { + Thread.sleep(-estimated); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + public static long getLastModified() { + return lastModified; + } + + private static class ClusterStatePropertyListener implements PropertyListener { + @Override + public synchronized void configLoad(Integer value) { + applyState(value); + } + + @Override + public synchronized void configUpdate(Integer value) { + applyState(value); + } + } + + public static boolean applyState(Integer state) { + if (state == null || state < 0) { + return false; + } + if (state == mode) { + return true; + } + synchronized (UPDATE_LOCK) { + switch (state) { + case CLUSTER_CLIENT: + return setToClient(); + case CLUSTER_SERVER: + return setToServer(); + default: + RecordLog.warn("[ClusterStateManager] Ignoring unknown cluster state: " + state); + return false; + } + } + } + + private static final int MIN_INTERVAL = 5 * 1000; +} diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenServerDescriptor.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenServerDescriptor.java index 98739179..98636346 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenServerDescriptor.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenServerDescriptor.java @@ -23,11 +23,10 @@ package com.alibaba.csp.sentinel.cluster; */ public class TokenServerDescriptor { - private String host; - private int port; - private String type; + private final String host; + private final int port; - public TokenServerDescriptor() {} + private String type = "default"; public TokenServerDescriptor(String host, int port) { this.host = host; @@ -38,20 +37,10 @@ public class TokenServerDescriptor { return host; } - public TokenServerDescriptor setHost(String host) { - this.host = host; - return this; - } - public int getPort() { return port; } - public TokenServerDescriptor setPort(int port) { - this.port = port; - return this; - } - public String getType() { return type; } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/client/ClusterTokenClient.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/client/ClusterTokenClient.java new file mode 100644 index 00000000..743bfcda --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/client/ClusterTokenClient.java @@ -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; + +import com.alibaba.csp.sentinel.cluster.TokenServerDescriptor; +import com.alibaba.csp.sentinel.cluster.TokenService; + +/** + * Token client interface for distributed flow control. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public interface ClusterTokenClient extends TokenService { + + /** + * Get descriptor of current token server. + * + * @return current token server if connected, otherwise null + */ + TokenServerDescriptor currentServer(); + + /** + * Start the token client. + * + * @throws Exception some error occurs + */ + void start() throws Exception; + + /** + * Stop the token client. + * + * @throws Exception some error occurs + */ + void stop() throws Exception; +} \ No newline at end of file diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/client/TokenClientProvider.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/client/TokenClientProvider.java new file mode 100644 index 00000000..c46a75ec --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/client/TokenClientProvider.java @@ -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; + +import com.alibaba.csp.sentinel.log.RecordLog; +import com.alibaba.csp.sentinel.util.SpiLoader; + +/** + * Provider for a universal {@link ClusterTokenClient} instance. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public final class TokenClientProvider { + + private static ClusterTokenClient client = null; + + static { + // Not strictly thread-safe, but it's OK since it will be resolved only once. + resolveTokenClientInstance(); + } + + public static ClusterTokenClient getClient() { + return client; + } + + private static void resolveTokenClientInstance() { + ClusterTokenClient resolvedClient = SpiLoader.loadFirstInstance(ClusterTokenClient.class); + if (resolvedClient == null) { + RecordLog.info( + "[TokenClientProvider] No existing cluster token client, cluster client mode will not be activated"); + } else { + client = resolvedClient; + RecordLog.info( + "[TokenClientProvider] Cluster token client resolved: " + client.getClass().getCanonicalName()); + } + } + + private TokenClientProvider() {} +} diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/log/ClusterClientStatLogUtil.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/log/ClusterClientStatLogUtil.java new file mode 100644 index 00000000..03b1655c --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/log/ClusterClientStatLogUtil.java @@ -0,0 +1,57 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.log; + +import com.alibaba.csp.sentinel.eagleeye.EagleEye; +import com.alibaba.csp.sentinel.eagleeye.StatLogger; +import com.alibaba.csp.sentinel.log.LogBase; + +/** + * @author jialiang.linjl + * @author Eric Zhao + * @since 1.4.0 + */ +public final class ClusterClientStatLogUtil { + + private static final String FILE_NAME = "sentinel-cluster-client.log"; + + private static StatLogger statLogger; + + static { + String path = LogBase.getLogBaseDir() + FILE_NAME; + + statLogger = EagleEye.statLoggerBuilder("sentinel-cluster-client-record") + .intervalSeconds(1) + .entryDelimiter('|') + .keyDelimiter(',') + .valueDelimiter(',') + .maxEntryCount(5000) + .configLogFilePath(path) + .maxFileSizeMB(300) + .maxBackupIndex(3) + .buildSingleton(); + } + + public static void log(String msg) { + statLogger.stat(msg).count(); + } + + public static void log(String msg, int count) { + statLogger.stat(msg).count(count); + } + + private ClusterClientStatLogUtil() {} +} diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/server/ClusterTokenServer.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/server/ClusterTokenServer.java new file mode 100644 index 00000000..f941230c --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/server/ClusterTokenServer.java @@ -0,0 +1,39 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.server; + +/** + * Token server interface for distributed flow control. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public interface ClusterTokenServer { + + /** + * Start the Sentinel cluster server. + * + * @throws Exception if any error occurs + */ + void start() throws Exception; + + /** + * Stop the Sentinel cluster server. + * + * @throws Exception if any error occurs + */ + void stop() throws Exception; +} diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/server/EmbeddedClusterTokenServer.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/server/EmbeddedClusterTokenServer.java new file mode 100644 index 00000000..03b29f93 --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/server/EmbeddedClusterTokenServer.java @@ -0,0 +1,27 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.server; + +import com.alibaba.csp.sentinel.cluster.TokenService; + +/** + * Embedded token server interface that can work in embedded mode. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public interface EmbeddedClusterTokenServer extends ClusterTokenServer, TokenService { +} diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/server/EmbeddedClusterTokenServerProvider.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/server/EmbeddedClusterTokenServerProvider.java new file mode 100644 index 00000000..bd3da760 --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/server/EmbeddedClusterTokenServerProvider.java @@ -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; + +import com.alibaba.csp.sentinel.log.RecordLog; +import com.alibaba.csp.sentinel.util.SpiLoader; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public final class EmbeddedClusterTokenServerProvider { + + private static EmbeddedClusterTokenServer server = null; + + static { + resolveInstance(); + } + + private static void resolveInstance() { + EmbeddedClusterTokenServer s = SpiLoader.loadFirstInstance(EmbeddedClusterTokenServer.class); + if (s == null) { + RecordLog.warn("[EmbeddedClusterTokenServerProvider] No existing cluster token server, cluster server mode will not be activated"); + } else { + server = s; + RecordLog.info("[EmbeddedClusterTokenServerProvider] Cluster token server resolved: " + server.getClass().getCanonicalName()); + } + } + + public static EmbeddedClusterTokenServer getServer() { + return server; + } + + private EmbeddedClusterTokenServerProvider() {} +} diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/ClusterRuleConstant.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/ClusterRuleConstant.java index 78cacf34..c04fa554 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/ClusterRuleConstant.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/ClusterRuleConstant.java @@ -22,7 +22,7 @@ package com.alibaba.csp.sentinel.slots.block; public final class ClusterRuleConstant { public static final int FLOW_CLUSTER_STRATEGY_NORMAL = 0; - public static final int FLOW_CLUSTER_STRATEGY_REF = 1; + public static final int FLOW_CLUSTER_STRATEGY_BORROW_REF = 1; public static final int FLOW_THRESHOLD_AVG_LOCAL = 0; public static final int FLOW_THRESHOLD_GLOBAL = 1; diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/ClusterFlowConfig.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/ClusterFlowConfig.java index b12a0743..7e3c53c4 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/ClusterFlowConfig.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/ClusterFlowConfig.java @@ -34,17 +34,13 @@ public class ClusterFlowConfig { * Threshold type (average by local value or global value). */ private int thresholdType = ClusterRuleConstant.FLOW_THRESHOLD_AVG_LOCAL; - private boolean fallbackToLocalWhenFail; + private boolean fallbackToLocalWhenFail = true; /** - * 0: normal; 1: using reference (borrow from reference). + * 0: normal. */ private int strategy = ClusterRuleConstant.FLOW_CLUSTER_STRATEGY_NORMAL; - private Long refFlowId; - private int refSampleCount = 10; - private double refRatio = 1d; - public Long getFlowId() { return flowId; } @@ -72,33 +68,6 @@ public class ClusterFlowConfig { return this; } - public Long getRefFlowId() { - return refFlowId; - } - - public ClusterFlowConfig setRefFlowId(Long refFlowId) { - this.refFlowId = refFlowId; - return this; - } - - public int getRefSampleCount() { - return refSampleCount; - } - - public ClusterFlowConfig setRefSampleCount(int refSampleCount) { - this.refSampleCount = refSampleCount; - return this; - } - - public double getRefRatio() { - return refRatio; - } - - public ClusterFlowConfig setRefRatio(double refRatio) { - this.refRatio = refRatio; - return this; - } - public boolean isFallbackToLocalWhenFail() { return fallbackToLocalWhenFail; } @@ -118,24 +87,15 @@ public class ClusterFlowConfig { if (thresholdType != that.thresholdType) { return false; } if (fallbackToLocalWhenFail != that.fallbackToLocalWhenFail) { return false; } if (strategy != that.strategy) { return false; } - if (refSampleCount != that.refSampleCount) { return false; } - if (Double.compare(that.refRatio, refRatio) != 0) { return false; } - if (flowId != null ? !flowId.equals(that.flowId) : that.flowId != null) { return false; } - return refFlowId != null ? refFlowId.equals(that.refFlowId) : that.refFlowId == null; + return flowId != null ? flowId.equals(that.flowId) : that.flowId == null; } @Override public int hashCode() { - int result; - long temp; - result = flowId != null ? flowId.hashCode() : 0; + int result = flowId != null ? flowId.hashCode() : 0; result = 31 * result + thresholdType; result = 31 * result + (fallbackToLocalWhenFail ? 1 : 0); result = 31 * result + strategy; - result = 31 * result + (refFlowId != null ? refFlowId.hashCode() : 0); - result = 31 * result + refSampleCount; - temp = Double.doubleToLongBits(refRatio); - result = 31 * result + (int)(temp ^ (temp >>> 32)); return result; } @@ -146,9 +106,6 @@ public class ClusterFlowConfig { ", thresholdType=" + thresholdType + ", fallbackToLocalWhenFail=" + fallbackToLocalWhenFail + ", strategy=" + strategy + - ", refFlowId=" + refFlowId + - ", refSampleCount=" + refSampleCount + - ", refRatio=" + refRatio + '}'; } } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleChecker.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleChecker.java index 7518d7c1..08cf5dd2 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleChecker.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleChecker.java @@ -15,10 +15,12 @@ */ package com.alibaba.csp.sentinel.slots.block.flow; -import com.alibaba.csp.sentinel.cluster.ClusterTokenClient; -import com.alibaba.csp.sentinel.cluster.TokenClientProvider; +import com.alibaba.csp.sentinel.cluster.ClusterStateManager; +import com.alibaba.csp.sentinel.cluster.server.EmbeddedClusterTokenServerProvider; +import com.alibaba.csp.sentinel.cluster.client.TokenClientProvider; import com.alibaba.csp.sentinel.cluster.TokenResultStatus; import com.alibaba.csp.sentinel.cluster.TokenResult; +import com.alibaba.csp.sentinel.cluster.TokenService; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.node.DefaultNode; @@ -62,38 +64,6 @@ final class FlowRuleChecker { return rule.getRater().canPass(selectedNode, acquireCount); } - static boolean passClusterCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount, - boolean prioritized) { - try { - ClusterTokenClient client = TokenClientProvider.getClient(); - if (client != null) { - TokenResult result = client.requestToken(rule.getClusterConfig().getFlowId(), acquireCount, - prioritized); - switch (result.getStatus()) { - case TokenResultStatus.OK: - return true; - case TokenResultStatus.SHOULD_WAIT: - // Wait for next tick. - Thread.sleep(result.getWaitInMs()); - return true; - case TokenResultStatus.NO_RULE_EXISTS: - case TokenResultStatus.BAD_REQUEST: - case TokenResultStatus.FAIL: - return passLocalCheck(rule, context, node, acquireCount, prioritized); - case TokenResultStatus.BLOCKED: - default: - return false; - } - } - // If client is absent, then fallback to local mode. - } catch (Throwable ex) { - RecordLog.warn("[FlowRuleChecker] Request cluster token unexpected failed", ex); - } - // TODO: choose whether fallback to local or inactivate the rule. - // Downgrade to local flow control when token client or server for this rule is not available. - return passLocalCheck(rule, context, node, acquireCount, prioritized); - } - static Node selectReferenceNode(FlowRule rule, Context context, DefaultNode node) { String refResource = rule.getRefResource(); int strategy = rule.getStrategy(); @@ -153,5 +123,67 @@ final class FlowRuleChecker { return null; } + private static boolean passClusterCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount, + boolean prioritized) { + try { + TokenService clusterService = pickClusterService(); + if (clusterService == null) { + return fallbackToLocalOrPass(rule, context, node, acquireCount, prioritized); + } + long flowId = rule.getClusterConfig().getFlowId(); + TokenResult result = clusterService.requestToken(flowId, acquireCount, prioritized); + return applyTokenResult(result, rule, context, node, acquireCount, prioritized); + // If client is absent, then fallback to local mode. + } catch (Throwable ex) { + RecordLog.warn("[FlowRuleChecker] Request cluster token unexpected failed", ex); + } + // Fallback to local flow control when token client or server for this rule is not available. + // If fallback is not enabled, then directly pass. + return fallbackToLocalOrPass(rule, context, node, acquireCount, prioritized); + } + + private static boolean fallbackToLocalOrPass(FlowRule rule, Context context, DefaultNode node, int acquireCount, + boolean prioritized) { + if (rule.getClusterConfig().isFallbackToLocalWhenFail()) { + return passLocalCheck(rule, context, node, acquireCount, prioritized); + } else { + // The rule won't be activated, just pass. + return true; + } + } + + private static TokenService pickClusterService() { + if (ClusterStateManager.isClient()) { + return TokenClientProvider.getClient(); + } + if (ClusterStateManager.isServer()) { + return EmbeddedClusterTokenServerProvider.getServer(); + } + return null; + } + + private static boolean applyTokenResult(/*@NonNull*/ TokenResult result, FlowRule rule, Context context, DefaultNode node, + int acquireCount, boolean prioritized) { + switch (result.getStatus()) { + case TokenResultStatus.OK: + return true; + case TokenResultStatus.SHOULD_WAIT: + // Wait for next tick. + try { + Thread.sleep(result.getWaitInMs()); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return true; + case TokenResultStatus.NO_RULE_EXISTS: + case TokenResultStatus.BAD_REQUEST: + case TokenResultStatus.FAIL: + return fallbackToLocalOrPass(rule, context, node, acquireCount, prioritized); + case TokenResultStatus.BLOCKED: + default: + return false; + } + } + private FlowRuleChecker() {} } \ No newline at end of file diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java index bea269f1..63d8ba28 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java @@ -189,10 +189,8 @@ public final class FlowRuleUtil { switch (rule.getStrategy()) { case ClusterRuleConstant.FLOW_CLUSTER_STRATEGY_NORMAL: return true; - case ClusterRuleConstant.FLOW_CLUSTER_STRATEGY_REF: - return validClusterRuleId(clusterConfig.getRefFlowId()); default: - return true; + return false; } } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/TrafficShapingController.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/TrafficShapingController.java index bcba4a18..6c7a60a2 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/TrafficShapingController.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/TrafficShapingController.java @@ -24,6 +24,16 @@ import com.alibaba.csp.sentinel.node.Node; */ public interface TrafficShapingController { + /** + * Check whether given resource entry can pass with provided count. + * + * @param node resource node + * @param acquireCount count to acquire + * @param prioritized whether the request is prioritized + * @return true if the resource entry can pass; false if it should be blocked + */ + boolean canPass(Node node, int acquireCount, boolean prioritized); + /** * Check whether given resource entry can pass with provided count. * diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/DefaultController.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/DefaultController.java index 4dc8368f..e3289cd7 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/DefaultController.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/DefaultController.java @@ -36,6 +36,11 @@ public class DefaultController implements TrafficShapingController { @Override public boolean canPass(Node node, int acquireCount) { + return canPass(node, acquireCount, false); + } + + @Override + public boolean canPass(Node node, int acquireCount, boolean prioritized) { int curCount = avgUsedTokens(node); if (curCount + acquireCount > count) { return false; @@ -51,4 +56,11 @@ public class DefaultController implements TrafficShapingController { return grade == RuleConstant.FLOW_GRADE_THREAD ? node.curThreadNum() : (int)node.passQps(); } + private void sleep(int timeMillis) { + try { + Thread.sleep(timeMillis); + } catch (InterruptedException e) { + // Ignore. + } + } } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/RateLimiterController.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/RateLimiterController.java index f1dc74af..e17e53cf 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/RateLimiterController.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/RateLimiterController.java @@ -39,6 +39,11 @@ public class RateLimiterController implements TrafficShapingController { @Override public boolean canPass(Node node, int acquireCount) { + return canPass(node, acquireCount, false); + } + + @Override + public boolean canPass(Node node, int acquireCount, boolean prioritized) { long currentTime = TimeUtil.currentTimeMillis(); // Calculate the interval between every two requests. long costTime = Math.round(1.0 * (acquireCount) / count * 1000); diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/WarmUpController.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/WarmUpController.java index 08c71166..2cc99a15 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/WarmUpController.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/WarmUpController.java @@ -107,6 +107,11 @@ public class WarmUpController implements TrafficShapingController { @Override public boolean canPass(Node node, int acquireCount) { + return canPass(node, acquireCount, false); + } + + @Override + public boolean canPass(Node node, int acquireCount, boolean prioritized) { long passQps = node.passQps(); long previousQps = node.previousPassQps(); diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/WarmUpRateLimiterController.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/WarmUpRateLimiterController.java index 8c2c20dd..7080f4c4 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/WarmUpRateLimiterController.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/WarmUpRateLimiterController.java @@ -39,6 +39,11 @@ public class WarmUpRateLimiterController extends WarmUpController { @Override public boolean canPass(Node node, int acquireCount) { + return canPass(node, acquireCount, false); + } + + @Override + public boolean canPass(Node node, int acquireCount, boolean prioritized) { long previousQps = node.previousPassQps(); syncToken(previousQps); diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/MetricEvent.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/MetricEvent.java new file mode 100644 index 00000000..f05dc966 --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/MetricEvent.java @@ -0,0 +1,37 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.slots.statistic; + +/** + * @author Eric Zhao + */ +public enum MetricEvent { + + /** + * Normal pass. + */ + PASS, + /** + * Normal block. + */ + BLOCK, + EXCEPTION, + SUCCESS, + RT, + OCCUPIED_PASS, + OCCUPIED_BLOCK, + WAITING +} diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/LeapArray.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/LeapArray.java index 51aae33d..d3044f3c 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/LeapArray.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/LeapArray.java @@ -25,12 +25,12 @@ import com.alibaba.csp.sentinel.util.TimeUtil; /** *

- * Basic data structure for statistic metrics. + * Basic data structure for statistic metrics in Sentinel. *

*

- * Using sliding window algorithm to count data. Each bucket cover {@link #windowLengthInMs} time span, - * and the total time span is {@link #intervalInMs}, so the total bucket count is: - * {@link #sampleCount} = intervalInMs / windowLengthInMs. + * Leap array use sliding window algorithm to count data. Each bucket cover {code windowLengthInMs} time span, + * and the total time span is {@link #intervalInMs}, so the total bucket amount is: + * {@code sampleCount = intervalInMs / windowLengthInMs}. *

* * @param type of statistic data @@ -47,7 +47,7 @@ public abstract class LeapArray { protected final AtomicReferenceArray> array; /** - * The fine-grained update lock is used only when current bucket is deprecated. + * The conditional (predicate) update lock is used only when current bucket is deprecated. */ private final ReentrantLock updateLock = new ReentrantLock(); @@ -58,9 +58,10 @@ public abstract class LeapArray { * @param intervalInSec the total time span of this {@link LeapArray} in seconds. */ public LeapArray(int windowLengthInMs, int intervalInSec) { + // TODO: change `intervalInSec` to `intervalInMs` AssertUtil.isTrue(windowLengthInMs > 0, "bucket length is invalid: " + windowLengthInMs); int intervalInMs = intervalInSec * 1000; - AssertUtil.isTrue(intervalInSec * 1000 > windowLengthInMs, + AssertUtil.isTrue(intervalInMs > windowLengthInMs, "total time span of the window should be greater than bucket length"); AssertUtil.isTrue(intervalInMs % windowLengthInMs == 0, "time span needs to be evenly divided"); @@ -90,28 +91,36 @@ public abstract class LeapArray { /** * Reset given bucket to provided start time and reset the value. * - * @param startTime the start time of the bucket + * @param startTime the start time of the bucket in milliseconds * @param windowWrap current bucket * @return new clean bucket at given start time */ protected abstract WindowWrap resetWindowTo(WindowWrap windowWrap, long startTime); + protected int calculateTimeIdx(/*@Valid*/ long timeMillis) { + long timeId = timeMillis / windowLengthInMs; + // Calculate current index so we can map the timestamp to the leap array. + return (int)(timeId % array.length()); + } + + protected long calculateWindowStart(/*@Valid*/ long timeMillis) { + return timeMillis - timeMillis % windowLengthInMs; + } + /** * Get bucket item at provided timestamp. * - * @param time a valid timestamp + * @param timeMillis a valid timestamp in milliseconds * @return current bucket item at provided timestamp if the time is valid; null if time is invalid */ - public WindowWrap currentWindow(long time) { - if (time < 0) { + public WindowWrap currentWindow(long timeMillis) { + if (timeMillis < 0) { return null; } - long timeId = time / windowLengthInMs; - // Calculate current index so we can map the timestamp to the leap array. - int idx = (int)(timeId % array.length()); + int idx = calculateTimeIdx(timeMillis); // Calculate current bucket start time. - long windowStart = time - time % windowLengthInMs; + long windowStart = calculateWindowStart(timeMillis); /* * Get bucket item at given time from the array. @@ -171,7 +180,7 @@ public abstract class LeapArray { * Note that the reset and clean-up operations are hard to be atomic, * so we need a update lock to guarantee the correctness of bucket update. * - * The update lock is fine-grained and will take effect only when + * The update lock is conditional (tiny scope) and will take effect only when * bucket is deprecated, so in most cases it won't lead to performance loss. */ if (updateLock.tryLock()) { @@ -195,23 +204,23 @@ public abstract class LeapArray { /** * Get the previous bucket item before provided timestamp. * - * @param time a valid timestamp + * @param timeMillis a valid timestamp in milliseconds * @return the previous bucket item before provided timestamp */ - public WindowWrap getPreviousWindow(long time) { - if (time < 0) { + public WindowWrap getPreviousWindow(long timeMillis) { + if (timeMillis < 0) { return null; } - long timeId = (time - windowLengthInMs) / windowLengthInMs; + long timeId = (timeMillis - windowLengthInMs) / windowLengthInMs; int idx = (int)(timeId % array.length()); - time = time - windowLengthInMs; + timeMillis = timeMillis - windowLengthInMs; WindowWrap wrap = array.get(idx); if (wrap == null || isWindowDeprecated(wrap)) { return null; } - if (wrap.windowStart() + windowLengthInMs < (time)) { + if (wrap.windowStart() + windowLengthInMs < (timeMillis)) { return null; } @@ -230,15 +239,14 @@ public abstract class LeapArray { /** * Get statistic value from bucket for provided timestamp. * - * @param time a valid timestamp + * @param time a valid timestamp in milliseconds * @return the statistic value if bucket for provided timestamp is up-to-date; otherwise null */ public T getWindowValue(long time) { if (time < 0) { return null; } - long timeId = time / windowLengthInMs; - int idx = (int)(timeId % array.length()); + int idx = calculateTimeIdx(time); WindowWrap old = array.get(idx); if (old == null || isWindowDeprecated(old)) { @@ -255,7 +263,7 @@ public abstract class LeapArray { * @param windowWrap a non-null bucket * @return true if the bucket is deprecated; otherwise false */ - private boolean isWindowDeprecated(/*@NonNull*/ WindowWrap windowWrap) { + protected boolean isWindowDeprecated(/*@NonNull*/ WindowWrap windowWrap) { return TimeUtil.currentTimeMillis() - windowWrap.windowStart() >= intervalInMs; } @@ -266,9 +274,10 @@ public abstract class LeapArray { * @return valid bucket list for entire sliding window. */ public List> list() { - List> result = new ArrayList>(); + int size = array.length(); + List> result = new ArrayList>(size); - for (int i = 0; i < array.length(); i++) { + for (int i = 0; i < size; i++) { WindowWrap windowWrap = array.get(i); if (windowWrap == null || isWindowDeprecated(windowWrap)) { continue; @@ -286,9 +295,10 @@ public abstract class LeapArray { * @return aggregated value list for entire sliding window */ public List values() { - List result = new ArrayList(); + int size = array.length(); + List result = new ArrayList(size); - for (int i = 0; i < array.length(); i++) { + for (int i = 0; i < size; i++) { WindowWrap windowWrap = array.get(i); if (windowWrap == null || isWindowDeprecated(windowWrap)) { continue; @@ -298,6 +308,34 @@ public abstract class LeapArray { return result; } + /** + * Get the valid "head" bucket of the sliding window for provided timestamp. + * Package-private for test. + * + * @param timeMillis a valid timestamp in milliseconds + * @return the "head" bucket if it exists and is valid; otherwise null + */ + WindowWrap getValidHead(long timeMillis) { + // Calculate index for expected head time. + int idx = calculateTimeIdx(timeMillis + windowLengthInMs); + + WindowWrap wrap = array.get(idx); + if (wrap == null || isWindowDeprecated(wrap)) { + return null; + } + + return wrap; + } + + /** + * Get the valid "head" bucket of the sliding window at current timestamp. + * + * @return the "head" bucket if it exists and is valid; otherwise null + */ + public WindowWrap getValidHead() { + return getValidHead(TimeUtil.currentTimeMillis()); + } + /** * Get sample count (total amount of buckets). * diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/MetricBucket.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/data/MetricBucket.java similarity index 59% rename from sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/MetricBucket.java rename to sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/data/MetricBucket.java index d59ca984..5648cb0f 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/MetricBucket.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/data/MetricBucket.java @@ -13,9 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alibaba.csp.sentinel.slots.statistic.base; +package com.alibaba.csp.sentinel.slots.statistic.data; import com.alibaba.csp.sentinel.Constants; +import com.alibaba.csp.sentinel.slots.statistic.MetricEvent; +import com.alibaba.csp.sentinel.slots.statistic.base.LongAdder; /** * Represents metrics data in a period of time span. @@ -25,15 +27,16 @@ import com.alibaba.csp.sentinel.Constants; */ public class MetricBucket { - private final LongAdder pass = new LongAdder(); - private final LongAdder block = new LongAdder(); - private final LongAdder exception = new LongAdder(); - private final LongAdder rt = new LongAdder(); - private final LongAdder success = new LongAdder(); + private final LongAdder[] counters; private volatile long minRt; public MetricBucket() { + MetricEvent[] events = MetricEvent.values(); + this.counters = new LongAdder[events.length]; + for (MetricEvent event : events) { + counters[event.ordinal()] = new LongAdder(); + } initMinRt(); } @@ -47,29 +50,36 @@ public class MetricBucket { * @return new metric bucket in initial state */ public MetricBucket reset() { - pass.reset(); - block.reset(); - exception.reset(); - rt.reset(); - success.reset(); + for (MetricEvent event : MetricEvent.values()) { + counters[event.ordinal()].reset(); + } initMinRt(); return this; } + public long get(MetricEvent event) { + return counters[event.ordinal()].sum(); + } + + public MetricBucket add(MetricEvent event, long n) { + counters[event.ordinal()].add(n); + return this; + } + public long pass() { - return pass.sum(); + return get(MetricEvent.PASS); } public long block() { - return block.sum(); + return get(MetricEvent.BLOCK); } public long exception() { - return exception.sum(); + return get(MetricEvent.EXCEPTION); } public long rt() { - return rt.sum(); + return get(MetricEvent.RT); } public long minRt() { @@ -77,27 +87,27 @@ public class MetricBucket { } public long success() { - return success.sum(); + return get(MetricEvent.SUCCESS); } public void addPass() { - pass.add(1L); + add(MetricEvent.PASS, 1); } public void addException() { - exception.add(1L); + add(MetricEvent.EXCEPTION, 1); } public void addBlock() { - block.add(1L); + add(MetricEvent.BLOCK, 1); } public void addSuccess() { - success.add(1L); + add(MetricEvent.SUCCESS, 1); } public void addRT(long rt) { - this.rt.add(rt); + add(MetricEvent.RT, rt); // Not thread-safe, but it's okay. if (rt < minRt) { diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/ArrayMetric.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/ArrayMetric.java index 7a141a54..e0c75418 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/ArrayMetric.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/ArrayMetric.java @@ -20,7 +20,7 @@ import java.util.List; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.node.metric.MetricNode; -import com.alibaba.csp.sentinel.slots.statistic.base.MetricBucket; +import com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket; import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; /** diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/Metric.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/Metric.java index 1b62057a..954614a3 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/Metric.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/Metric.java @@ -18,7 +18,7 @@ package com.alibaba.csp.sentinel.slots.statistic.metric; import java.util.List; import com.alibaba.csp.sentinel.node.metric.MetricNode; -import com.alibaba.csp.sentinel.slots.statistic.base.MetricBucket; +import com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket; /** * Represents a basic structure recording invocation metrics of protected resources. diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/MetricsLeapArray.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/MetricsLeapArray.java index fcf452e1..2a1cda4b 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/MetricsLeapArray.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/MetricsLeapArray.java @@ -16,7 +16,7 @@ package com.alibaba.csp.sentinel.slots.statistic.metric; import com.alibaba.csp.sentinel.slots.statistic.base.LeapArray; -import com.alibaba.csp.sentinel.slots.statistic.base.MetricBucket; +import com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket; import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; /** diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/AssertUtil.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/AssertUtil.java index 46dcc11e..4c67b738 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/AssertUtil.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/AssertUtil.java @@ -28,6 +28,12 @@ public class AssertUtil { } } + public static void assertNotBlank(String string, String message) { + if (StringUtil.isBlank(string)) { + throw new IllegalArgumentException(message); + } + } + public static void notNull(Object object, String message) { if (object == null) { throw new IllegalArgumentException(message); diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/SpiLoader.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/SpiLoader.java new file mode 100644 index 00000000..b057fc55 --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/SpiLoader.java @@ -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.util; + +import java.util.Iterator; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public final class SpiLoader { + + private static final Map SERVICE_LOADER_MAP = new ConcurrentHashMap(); + + public static T loadFirstInstance(Class clazz) { + String key = clazz.getName(); + // Not thread-safe, as it's expected to be resolved in a thread-safe context. + ServiceLoader serviceLoader = SERVICE_LOADER_MAP.get(key); + if (serviceLoader == null) { + serviceLoader = ServiceLoader.load(clazz); + SERVICE_LOADER_MAP.put(key, serviceLoader); + } + + Iterator iterator = serviceLoader.iterator(); + if (iterator.hasNext()) { + return iterator.next(); + } else { + return null; + } + } + + private SpiLoader() {} +} diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/function/Supplier.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/function/Supplier.java new file mode 100644 index 00000000..02b3208c --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/function/Supplier.java @@ -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.util.function; + +/** + * Supplier functional interface from JDK 8. + */ +public interface Supplier { + + /** + * Gets a result. + * + * @return a result + */ + T get(); +} diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/base/metric/ArrayMetricTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/base/metric/ArrayMetricTest.java index 02e33732..6df540cd 100755 --- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/base/metric/ArrayMetricTest.java +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/base/metric/ArrayMetricTest.java @@ -19,7 +19,7 @@ import java.util.ArrayList; import org.junit.Test; -import com.alibaba.csp.sentinel.slots.statistic.base.MetricBucket; +import com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket; import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; import com.alibaba.csp.sentinel.slots.statistic.metric.ArrayMetric; import com.alibaba.csp.sentinel.slots.statistic.metric.MetricsLeapArray; diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/base/metric/MetricsLeapArrayTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/base/metric/MetricsLeapArrayTest.java index 735838e4..8998895c 100755 --- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/base/metric/MetricsLeapArrayTest.java +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/base/metric/MetricsLeapArrayTest.java @@ -21,7 +21,7 @@ import java.util.List; import java.util.Set; import java.util.concurrent.CountDownLatch; -import com.alibaba.csp.sentinel.slots.statistic.base.MetricBucket; +import com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket; import com.alibaba.csp.sentinel.util.TimeUtil; import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; import com.alibaba.csp.sentinel.slots.statistic.metric.MetricsLeapArray; diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/statistic/base/LeapArrayTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/statistic/base/LeapArrayTest.java new file mode 100644 index 00000000..e5ccbda2 --- /dev/null +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/statistic/base/LeapArrayTest.java @@ -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.slots.statistic.base; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * @author Eric Zhao + */ +public class LeapArrayTest { + + @Test + public void testGetValidHead() { + int windowLengthInMs = 100; + int intervalInSec = 1; + int sampleCount = intervalInSec * 1000 / windowLengthInMs; + LeapArray leapArray = new LeapArray(windowLengthInMs, intervalInSec) { + @Override + public AtomicInteger newEmptyBucket() { + return new AtomicInteger(0); + } + + @Override + protected WindowWrap resetWindowTo(WindowWrap windowWrap, long startTime) { + windowWrap.resetTo(startTime); + windowWrap.value().set(0); + return windowWrap; + } + }; + WindowWrap expected1 = leapArray.currentWindow(); + expected1.value().addAndGet(1); + sleep(windowLengthInMs); + WindowWrap expected2 = leapArray.currentWindow(); + expected2.value().addAndGet(2); + for (int i = 0; i < sampleCount - 2; i++) { + sleep(windowLengthInMs); + leapArray.currentWindow().value().addAndGet(i + 3); + } + + assertSame(expected1, leapArray.getValidHead()); + sleep(windowLengthInMs); + assertSame(expected2, leapArray.getValidHead()); + } + + private void sleep(int t) { + try { + Thread.sleep(t); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/sentinel-demo/pom.xml b/sentinel-demo/pom.xml index 3ab45f6f..2cc6f9ad 100755 --- a/sentinel-demo/pom.xml +++ b/sentinel-demo/pom.xml @@ -28,6 +28,7 @@ sentinel-demo-annotation-spring-aop sentinel-demo-parameter-flow-control sentinel-demo-slot-chain-spi + sentinel-demo-cluster diff --git a/sentinel-demo/sentinel-demo-cluster/pom.xml b/sentinel-demo/sentinel-demo-cluster/pom.xml new file mode 100644 index 00000000..e76d8bb5 --- /dev/null +++ b/sentinel-demo/sentinel-demo-cluster/pom.xml @@ -0,0 +1,44 @@ + + + + sentinel-demo + com.alibaba.csp + 1.4.0-SNAPSHOT + + 4.0.0 + + sentinel-demo-cluster + + + + com.alibaba.csp + sentinel-core + + + com.alibaba.csp + sentinel-transport-simple-http + + + com.alibaba.csp + sentinel-parameter-flow-control + + + + com.alibaba.csp + sentinel-cluster-client-default + ${project.version} + + + com.alibaba.csp + sentinel-cluster-server-default + ${project.version} + + + + com.alibaba.csp + sentinel-datasource-nacos + + + \ No newline at end of file diff --git a/sentinel-demo/sentinel-demo-cluster/src/main/java/com/alibaba/csp/sentinel/demo/cluster/ClusterClientDemo.java b/sentinel-demo/sentinel-demo-cluster/src/main/java/com/alibaba/csp/sentinel/demo/cluster/ClusterClientDemo.java new file mode 100644 index 00000000..c665fc3a --- /dev/null +++ b/sentinel-demo/sentinel-demo-cluster/src/main/java/com/alibaba/csp/sentinel/demo/cluster/ClusterClientDemo.java @@ -0,0 +1,61 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.demo.cluster; + +import com.alibaba.csp.sentinel.Entry; +import com.alibaba.csp.sentinel.EntryType; +import com.alibaba.csp.sentinel.SphU; +import com.alibaba.csp.sentinel.cluster.ClusterStateManager; +import com.alibaba.csp.sentinel.init.InitExecutor; +import com.alibaba.csp.sentinel.slots.block.BlockException; + +/** + *

Run this demo with the following args: -Dproject.name=appA

+ * + * @author Eric Zhao + */ +public class ClusterClientDemo { + + public static void main(String[] args) { + InitExecutor.doInit(); + + // Manually schedule the cluster mode to client. + // In common, we need a scheduling system to modify the cluster mode automatically. + // Command HTTP API: http://:/setClusterMode?mode= + ClusterStateManager.setToClient(); + + String resourceName = "cluster-demo-entry"; + + // Assume we have a cluster flow rule for `demo-resource`: QPS = 5 in AVG_LOCAL mode. + for (int i = 0; i < 10; i++) { + tryEntry(resourceName); + } + } + + private static void tryEntry(String res) { + Entry entry = null; + try { + entry = SphU.entry(res, EntryType.IN, 1, "abc", "def"); + System.out.println("Passed"); + } catch (BlockException ex) { + ex.printStackTrace(); + } finally { + if (entry != null) { + entry.exit(); + } + } + } +} diff --git a/sentinel-demo/sentinel-demo-cluster/src/main/java/com/alibaba/csp/sentinel/demo/cluster/ClusterServerDemo.java b/sentinel-demo/sentinel-demo-cluster/src/main/java/com/alibaba/csp/sentinel/demo/cluster/ClusterServerDemo.java new file mode 100644 index 00000000..8d731f28 --- /dev/null +++ b/sentinel-demo/sentinel-demo-cluster/src/main/java/com/alibaba/csp/sentinel/demo/cluster/ClusterServerDemo.java @@ -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.demo.cluster; + +import java.util.Collections; + +import com.alibaba.csp.sentinel.cluster.server.ClusterTokenServer; +import com.alibaba.csp.sentinel.cluster.server.SentinelDefaultTokenServer; +import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; +import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig; + +/** + * Cluster server demo (alone mode). + * + * Here we init the cluster server dynamic data sources in {@link com.alibaba.csp.sentinel.demo.cluster.init.DemoClusterServerInitFunc}. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public class ClusterServerDemo { + + public static void main(String[] args) throws Exception { + // Not embedded mode by default (alone mode). + ClusterTokenServer tokenServer = new SentinelDefaultTokenServer(); + + // A sample for manually load config for cluster server. + // It's recommended to use dynamic data source to cluster manage config and rules. + // See the sample in DemoClusterServerInitFunc for detail. + ClusterServerConfigManager.loadGlobalTransportConfig(new ServerTransportConfig() + .setIdleSeconds(600) + .setPort(11111)); + ClusterServerConfigManager.loadServerNamespaceSet(Collections.singleton(DemoConstants.APP_NAME)); + + // Start the server. + tokenServer.start(); + } +} diff --git a/sentinel-demo/sentinel-demo-cluster/src/main/java/com/alibaba/csp/sentinel/demo/cluster/DemoConstants.java b/sentinel-demo/sentinel-demo-cluster/src/main/java/com/alibaba/csp/sentinel/demo/cluster/DemoConstants.java new file mode 100644 index 00000000..06ee4e03 --- /dev/null +++ b/sentinel-demo/sentinel-demo-cluster/src/main/java/com/alibaba/csp/sentinel/demo/cluster/DemoConstants.java @@ -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.demo.cluster; + +/** + * @author Eric Zhao + */ +public final class DemoConstants { + + public static final String APP_NAME = "appA"; + + public static final String FLOW_POSTFIX = "-flow-rules"; + public static final String PARAM_FLOW_POSTFIX = "-param-rules"; + + private DemoConstants() {} +} diff --git a/sentinel-demo/sentinel-demo-cluster/src/main/java/com/alibaba/csp/sentinel/demo/cluster/init/DemoClusterClientInitFunc.java b/sentinel-demo/sentinel-demo-cluster/src/main/java/com/alibaba/csp/sentinel/demo/cluster/init/DemoClusterClientInitFunc.java new file mode 100644 index 00000000..d75804be --- /dev/null +++ b/sentinel-demo/sentinel-demo-cluster/src/main/java/com/alibaba/csp/sentinel/demo/cluster/init/DemoClusterClientInitFunc.java @@ -0,0 +1,57 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.demo.cluster.init; + +import java.util.List; + +import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfig; +import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfigManager; +import com.alibaba.csp.sentinel.datasource.ReadableDataSource; +import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource; +import com.alibaba.csp.sentinel.demo.cluster.DemoConstants; +import com.alibaba.csp.sentinel.init.InitFunc; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; +import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; +import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.TypeReference; + +/** + * @author Eric Zhao + */ +public class DemoClusterClientInitFunc implements InitFunc { + + @Override + public void init() throws Exception { + final String remoteAddress = "localhost"; + final String groupId = "SENTINEL_GROUP"; + final String flowDataId = DemoConstants.APP_NAME + DemoConstants.FLOW_POSTFIX; + final String paramDataId = DemoConstants.APP_NAME + DemoConstants.PARAM_FLOW_POSTFIX; + final String configDataId = DemoConstants.APP_NAME + "-cluster-client-config"; + + ReadableDataSource> ruleSource = new NacosDataSource<>(remoteAddress, groupId, + flowDataId, source -> JSON.parseObject(source, new TypeReference>() {})); + FlowRuleManager.register2Property(ruleSource.getProperty()); + ReadableDataSource> paramRuleSource = new NacosDataSource<>(remoteAddress, groupId, + paramDataId, source -> JSON.parseObject(source, new TypeReference>() {})); + ParamFlowRuleManager.register2Property(paramRuleSource.getProperty()); + + ReadableDataSource dataSource = new NacosDataSource<>(remoteAddress, groupId, + configDataId, source -> JSON.parseObject(source, new TypeReference() {})); + ClusterClientConfigManager.register2Property(dataSource.getProperty()); + } +} diff --git a/sentinel-demo/sentinel-demo-cluster/src/main/java/com/alibaba/csp/sentinel/demo/cluster/init/DemoClusterServerInitFunc.java b/sentinel-demo/sentinel-demo-cluster/src/main/java/com/alibaba/csp/sentinel/demo/cluster/init/DemoClusterServerInitFunc.java new file mode 100644 index 00000000..8fe5313a --- /dev/null +++ b/sentinel-demo/sentinel-demo-cluster/src/main/java/com/alibaba/csp/sentinel/demo/cluster/init/DemoClusterServerInitFunc.java @@ -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.demo.cluster.init; + +import java.util.List; +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.server.config.ClusterServerConfigManager; +import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig; +import com.alibaba.csp.sentinel.datasource.ReadableDataSource; +import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource; +import com.alibaba.csp.sentinel.demo.cluster.DemoConstants; +import com.alibaba.csp.sentinel.init.InitFunc; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; +import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.TypeReference; + +/** + * @author Eric Zhao + */ +public class DemoClusterServerInitFunc implements InitFunc { + + private final String remoteAddress = "localhost"; + private final String groupId = "SENTINEL_GROUP"; + private final String namespaceSetDataId = "cluster-server-namespace-set"; + private final String serverTransportDataId = "cluster-server-transport-config"; + + @Override + public void init() throws Exception { + // Register cluster flow rule property supplier which creates data source by namespace. + ClusterFlowRuleManager.setPropertySupplier(namespace -> { + ReadableDataSource> ds = new NacosDataSource<>(remoteAddress, groupId, + namespace + DemoConstants.FLOW_POSTFIX, + source -> JSON.parseObject(source, new TypeReference>() {})); + return ds.getProperty(); + }); + // Register cluster parameter flow rule property supplier. + ClusterParamFlowRuleManager.setPropertySupplier(namespace -> { + ReadableDataSource> ds = new NacosDataSource<>(remoteAddress, groupId, + namespace + DemoConstants.PARAM_FLOW_POSTFIX, + source -> JSON.parseObject(source, new TypeReference>() {})); + return ds.getProperty(); + }); + + // Server namespace set (scope) data source. + ReadableDataSource> namespaceDs = new NacosDataSource<>(remoteAddress, groupId, + namespaceSetDataId, source -> JSON.parseObject(source, new TypeReference>() {})); + ClusterServerConfigManager.registerNamespaceSetProperty(namespaceDs.getProperty()); + // Server transport configuration data source. + ReadableDataSource transportConfigDs = new NacosDataSource<>(remoteAddress, + groupId, serverTransportDataId, + source -> JSON.parseObject(source, new TypeReference() {})); + ClusterServerConfigManager.registerServerTransportProperty(transportConfigDs.getProperty()); + } +} diff --git a/sentinel-demo/sentinel-demo-cluster/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc b/sentinel-demo/sentinel-demo-cluster/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc new file mode 100755 index 00000000..11b70ec5 --- /dev/null +++ b/sentinel-demo/sentinel-demo-cluster/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc @@ -0,0 +1,2 @@ +com.alibaba.csp.sentinel.demo.cluster.init.DemoClusterClientInitFunc +com.alibaba.csp.sentinel.demo.cluster.init.DemoClusterServerInitFunc \ No newline at end of file diff --git a/sentinel-extension/sentinel-datasource-nacos/pom.xml b/sentinel-extension/sentinel-datasource-nacos/pom.xml index 54f686d5..dd0b0444 100644 --- a/sentinel-extension/sentinel-datasource-nacos/pom.xml +++ b/sentinel-extension/sentinel-datasource-nacos/pom.xml @@ -13,7 +13,7 @@ jar - 0.4.0 + 0.2.1 diff --git a/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowChecker.java b/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowChecker.java index 2488643f..dfacaf28 100644 --- a/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowChecker.java +++ b/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowChecker.java @@ -22,8 +22,8 @@ import java.util.Collections; import java.util.List; import java.util.Set; -import com.alibaba.csp.sentinel.cluster.ClusterTokenClient; -import com.alibaba.csp.sentinel.cluster.TokenClientProvider; +import com.alibaba.csp.sentinel.cluster.client.ClusterTokenClient; +import com.alibaba.csp.sentinel.cluster.client.TokenClientProvider; import com.alibaba.csp.sentinel.cluster.TokenResult; import com.alibaba.csp.sentinel.cluster.TokenResultStatus; import com.alibaba.csp.sentinel.log.RecordLog; @@ -62,57 +62,6 @@ final class ParamFlowChecker { return passLocalCheck(resourceWrapper, rule, count, value); } - @SuppressWarnings("unchecked") - private static Collection toCollection(Object value) { - if (value instanceof Collection) { - return (Collection)value; - } else if (value.getClass().isArray()) { - List params = new ArrayList(); - int length = Array.getLength(value); - for (int i = 0; i < length; i++) { - Object param = Array.get(value, i); - params.add(param); - } - return params; - } else { - return Collections.singletonList(value); - } - } - - private static boolean passClusterCheck(ResourceWrapper resourceWrapper, ParamFlowRule rule, int count, - Object value) { - try { - ClusterTokenClient client = TokenClientProvider.getClient(); - if (client == null) { - return true; - } - Collection params = toCollection(value); - - TokenResult result = client.requestParamToken(rule.getClusterConfig().getFlowId(), count, params); - switch (result.getStatus()) { - case TokenResultStatus.OK: - return true; - case TokenResultStatus.BLOCKED: - return false; - default: - return fallbackToLocalOrPass(resourceWrapper, rule, count, params); - } - } catch (Throwable ex) { - RecordLog.warn("[ParamFlowChecker] Request cluster token for parameter unexpected failed", ex); - return fallbackToLocalOrPass(resourceWrapper, rule, count, value); - } - } - - private static boolean fallbackToLocalOrPass(ResourceWrapper resourceWrapper, ParamFlowRule rule, int count, - Object value) { - if (rule.getClusterConfig().isFallbackToLocalWhenFail()) { - return passLocalCheck(resourceWrapper, rule, count, value); - } else { - // The rule won't be activated, just pass. - return true; - } - } - private static boolean passLocalCheck(ResourceWrapper resourceWrapper, ParamFlowRule rule, int count, Object value) { try { @@ -165,5 +114,56 @@ final class ParamFlowChecker { return ParamFlowSlot.getParamMetric(resourceWrapper); } + @SuppressWarnings("unchecked") + private static Collection toCollection(Object value) { + if (value instanceof Collection) { + return (Collection)value; + } else if (value.getClass().isArray()) { + List params = new ArrayList(); + int length = Array.getLength(value); + for (int i = 0; i < length; i++) { + Object param = Array.get(value, i); + params.add(param); + } + return params; + } else { + return Collections.singletonList(value); + } + } + + private static boolean passClusterCheck(ResourceWrapper resourceWrapper, ParamFlowRule rule, int count, + Object value) { + try { + ClusterTokenClient client = TokenClientProvider.getClient(); + if (client == null) { + return true; + } + Collection params = toCollection(value); + + TokenResult result = client.requestParamToken(rule.getClusterConfig().getFlowId(), count, params); + switch (result.getStatus()) { + case TokenResultStatus.OK: + return true; + case TokenResultStatus.BLOCKED: + return false; + default: + return fallbackToLocalOrPass(resourceWrapper, rule, count, params); + } + } catch (Throwable ex) { + RecordLog.warn("[ParamFlowChecker] Request cluster token for parameter unexpected failed", ex); + return fallbackToLocalOrPass(resourceWrapper, rule, count, value); + } + } + + private static boolean fallbackToLocalOrPass(ResourceWrapper resourceWrapper, ParamFlowRule rule, int count, + Object value) { + if (rule.getClusterConfig().isFallbackToLocalWhenFail()) { + return passLocalCheck(resourceWrapper, rule, count, value); + } else { + // The rule won't be activated, just pass. + return true; + } + } + private ParamFlowChecker() {} } diff --git a/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRule.java b/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRule.java index 18f6db16..031dccef 100644 --- a/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRule.java +++ b/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRule.java @@ -104,6 +104,13 @@ public class ParamFlowRule extends AbstractRule { return this; } + public Integer retrieveExclusiveItemCount(Object value) { + if (value == null || hotItems == null) { + return null; + } + return hotItems.get(value); + } + Map getParsedHotItems() { return hotItems; } @@ -126,8 +133,7 @@ public class ParamFlowRule extends AbstractRule { return clusterConfig; } - public ParamFlowRule setClusterConfig( - ParamFlowClusterConfig clusterConfig) { + public ParamFlowRule setClusterConfig(ParamFlowClusterConfig clusterConfig) { this.clusterConfig = clusterConfig; return this; } diff --git a/sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/cluster/FetchClusterModeCommandHandler.java b/sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/cluster/FetchClusterModeCommandHandler.java new file mode 100644 index 00000000..14be5199 --- /dev/null +++ b/sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/cluster/FetchClusterModeCommandHandler.java @@ -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.command.handler.cluster; + +import com.alibaba.csp.sentinel.cluster.ClusterStateManager; +import com.alibaba.csp.sentinel.cluster.client.TokenClientProvider; +import com.alibaba.csp.sentinel.cluster.server.EmbeddedClusterTokenServerProvider; +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.fastjson.JSONObject; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +@CommandMapping(name = "getClusterMode") +public class FetchClusterModeCommandHandler implements CommandHandler { + + @Override + public CommandResponse handle(CommandRequest request) { + JSONObject res = new JSONObject() + .fluentPut("mode", ClusterStateManager.getMode()) + .fluentPut("lastModified", ClusterStateManager.getLastModified()) + .fluentPut("clientAvailable", isClusterClientSpiAvailable()) + .fluentPut("serverAvailable", isClusterServerSpiAvailable()); + return CommandResponse.ofSuccess(res.toJSONString()); + } + + private boolean isClusterClientSpiAvailable() { + return TokenClientProvider.getClient() != null; + } + + private boolean isClusterServerSpiAvailable() { + return EmbeddedClusterTokenServerProvider.getServer() != null; + } +} diff --git a/sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/cluster/ModifyClusterModeCommandHandler.java b/sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/cluster/ModifyClusterModeCommandHandler.java new file mode 100644 index 00000000..072a52b0 --- /dev/null +++ b/sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/cluster/ModifyClusterModeCommandHandler.java @@ -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.command.handler.cluster; + +import com.alibaba.csp.sentinel.cluster.ClusterStateManager; +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.log.RecordLog; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +@CommandMapping(name = "setClusterMode") +public class ModifyClusterModeCommandHandler implements CommandHandler { + + @Override + public CommandResponse handle(CommandRequest request) { + try { + int mode = Integer.valueOf(request.getParam("mode")); + RecordLog.info("[ModifyClusterModeCommandHandler] Modifying cluster mode to: " + mode); + if (ClusterStateManager.applyState(mode)) { + return CommandResponse.ofSuccess("success"); + } else { + return CommandResponse.ofSuccess("failed"); + } + } catch (NumberFormatException ex) { + return CommandResponse.ofFailure(new IllegalArgumentException("invalid mode")); + } catch (Exception ex) { + return CommandResponse.ofFailure(ex); + } + } +} diff --git a/sentinel-transport/sentinel-transport-common/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler b/sentinel-transport/sentinel-transport-common/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler index 1dd95c88..2edadbd4 100755 --- a/sentinel-transport/sentinel-transport-common/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler +++ b/sentinel-transport/sentinel-transport-common/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler @@ -11,4 +11,6 @@ com.alibaba.csp.sentinel.command.handler.ModifyRulesCommandHandler com.alibaba.csp.sentinel.command.handler.OnOffGetCommandHandler com.alibaba.csp.sentinel.command.handler.OnOffSetCommandHandler com.alibaba.csp.sentinel.command.handler.SendMetricCommandHandler -com.alibaba.csp.sentinel.command.handler.VersionCommandHandler \ No newline at end of file +com.alibaba.csp.sentinel.command.handler.VersionCommandHandler +com.alibaba.csp.sentinel.command.handler.cluster.FetchClusterModeCommandHandler +com.alibaba.csp.sentinel.command.handler.cluster.ModifyClusterModeCommandHandler \ No newline at end of file