Signed-off-by: Eric Zhao <sczyh16@gmail.com>master
@@ -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.util; | |||||
/** | |||||
* Util class for checking arguments. | |||||
*/ | |||||
public class AssertUtil { | |||||
private AssertUtil(){} | |||||
public static void notEmpty(String string, String message) { | |||||
if (StringUtil.isEmpty(string)) { | |||||
throw new IllegalArgumentException(message); | |||||
} | |||||
} | |||||
public static void notNull(Object object, String message) { | |||||
if (object == null) { | |||||
throw new IllegalArgumentException(message); | |||||
} | |||||
} | |||||
public static void isTrue(boolean value, String message) { | |||||
if (!value) { | |||||
throw new IllegalArgumentException(message); | |||||
} | |||||
} | |||||
public static void assertState(boolean condition, String message) { | |||||
if (!condition) { | |||||
throw new IllegalStateException(message); | |||||
} | |||||
} | |||||
} |
@@ -6,4 +6,4 @@ The examples demonstrate: | |||||
- How to use various data source extensions of Sentinel (e.g. file, Nacos, ZooKeeper) | - How to use various data source extensions of Sentinel (e.g. file, Nacos, ZooKeeper) | ||||
- How to use Dubbo with Sentinel | - How to use Dubbo with Sentinel | ||||
- How to use Apache RocketMQ client with Sentinel | - How to use Apache RocketMQ client with Sentinel | ||||
- How to use Sentinel annotation support |
@@ -1,10 +1,12 @@ | |||||
# Sentinel DataSource Redis | # Sentinel DataSource Redis | ||||
Sentinel DataSource Redis provides integration with Redis. make Redis | |||||
as dynamic rule data source of Sentinel. The data source uses push model (listener) with redis pub/sub feature. | |||||
Sentinel DataSource Redis provides integration with Redis. The data source leverages Redis pub-sub feature to implement push model (listener). | |||||
**NOTE**: | |||||
we not support redis cluster as a pub/sub dataSource now. | |||||
The data source uses [Lettuce](https://lettuce.io/) as the Redis client internal. Requires JDK 1.8 or later. | |||||
> **NOTE**: Currently we do not support Redis Cluster now. | |||||
## Usage | |||||
To use Sentinel DataSource Redis, you should add the following dependency: | To use Sentinel DataSource Redis, you should add the following dependency: | ||||
@@ -25,39 +27,41 @@ ReadableDataSource<String, List<FlowRule>> redisDataSource = new RedisDataSource | |||||
FlowRuleManager.register2Property(redisDataSource.getProperty()); | FlowRuleManager.register2Property(redisDataSource.getProperty()); | ||||
``` | ``` | ||||
_**redisConnectionConfig**_ : use `RedisConnectionConfig` class to build your connection config. | |||||
_**ruleKey**_ : when the json rule data publish. it also should save to the key for init read. | |||||
_**channel**_ : the channel to listen. | |||||
you can also create multi data source listen for different rule type. | |||||
you can see test cases for usage. | |||||
## Before start | |||||
- `redisConnectionConfig`: use `RedisConnectionConfig` class to build your Redis connection config | |||||
- `ruleKey`: the rule persistence key of a Redis String | |||||
- `channel`: the channel to subscribe | |||||
RedisDataSource init config by read from redis key `ruleKey`, value store the latest rule config data. | |||||
so you should first config your redis ruleData in back end. | |||||
You can also create multi data sources to subscribe for different rule type. | |||||
since update redis rule data. it should simultaneously send data to `channel`. | |||||
you may implement like this (using Redis transaction): | |||||
Note that the data source first loads initial rules from a Redis String (provided `ruleKey`) during initialization. | |||||
So for consistency, users should publish the value and save the value to the `ruleKey` simultaneously like this (using Redis transaction): | |||||
``` | ``` | ||||
MULTI | MULTI | ||||
PUBLISH channel value | |||||
SET ruleKey value | SET ruleKey value | ||||
PUBLISH channel value | |||||
EXEC | EXEC | ||||
``` | |||||
``` | |||||
An example using Lettuce Redis client: | |||||
```java | |||||
public <T> void pushRules(List<T> rules, Converter<List<T>, String> encoder) { | |||||
StatefulRedisPubSubConnection<String, String> connection = client.connectPubSub(); | |||||
RedisPubSubCommands<String, String> subCommands = connection.sync(); | |||||
String value = encoder.convert(rules); | |||||
subCommands.multi(); | |||||
subCommands.set(ruleKey, value); | |||||
subCommands.publish(ruleChannel, value); | |||||
subCommands.exec(); | |||||
} | |||||
``` | |||||
## How to build RedisConnectionConfig | ## How to build RedisConnectionConfig | ||||
### Build with redis standLone mode | |||||
### Build with Redis standalone mode | |||||
```java | ```java | ||||
RedisConnectionConfig config = RedisConnectionConfig.builder() | RedisConnectionConfig config = RedisConnectionConfig.builder() | ||||
@@ -70,7 +74,7 @@ RedisConnectionConfig config = RedisConnectionConfig.builder() | |||||
``` | ``` | ||||
### Build with redis sentinel mode | |||||
### Build with Redis Sentinel mode | |||||
```java | ```java | ||||
RedisConnectionConfig config = RedisConnectionConfig.builder() | RedisConnectionConfig config = RedisConnectionConfig.builder() | ||||
@@ -13,20 +13,24 @@ | |||||
<packaging>jar</packaging> | <packaging>jar</packaging> | ||||
<properties> | <properties> | ||||
<java.source.version>1.8</java.source.version> | |||||
<java.target.version>1.8</java.target.version> | |||||
<lettuce.version>5.0.1.RELEASE</lettuce.version> | <lettuce.version>5.0.1.RELEASE</lettuce.version> | ||||
<redis.mock.version>0.1.6</redis.mock.version> | <redis.mock.version>0.1.6</redis.mock.version> | ||||
</properties> | </properties> | ||||
<dependencies> | <dependencies> | ||||
<dependency> | |||||
<groupId>com.alibaba.csp</groupId> | |||||
<artifactId>sentinel-datasource-extension</artifactId> | |||||
</dependency> | |||||
<dependency> | <dependency> | ||||
<groupId>io.lettuce</groupId> | <groupId>io.lettuce</groupId> | ||||
<artifactId>lettuce-core</artifactId> | <artifactId>lettuce-core</artifactId> | ||||
<version>${lettuce.version}</version> | <version>${lettuce.version}</version> | ||||
</dependency> | </dependency> | ||||
<dependency> | |||||
<groupId>com.alibaba.csp</groupId> | |||||
<artifactId>sentinel-datasource-extension</artifactId> | |||||
</dependency> | |||||
<dependency> | <dependency> | ||||
<groupId>junit</groupId> | <groupId>junit</groupId> | ||||
<artifactId>junit</artifactId> | <artifactId>junit</artifactId> | ||||
@@ -19,9 +19,10 @@ package com.alibaba.csp.sentinel.datasource.redis; | |||||
import com.alibaba.csp.sentinel.datasource.AbstractDataSource; | import com.alibaba.csp.sentinel.datasource.AbstractDataSource; | ||||
import com.alibaba.csp.sentinel.datasource.Converter; | import com.alibaba.csp.sentinel.datasource.Converter; | ||||
import com.alibaba.csp.sentinel.datasource.redis.config.RedisConnectionConfig; | import com.alibaba.csp.sentinel.datasource.redis.config.RedisConnectionConfig; | ||||
import com.alibaba.csp.sentinel.datasource.redis.util.AssertUtil; | |||||
import com.alibaba.csp.sentinel.util.AssertUtil; | |||||
import com.alibaba.csp.sentinel.log.RecordLog; | import com.alibaba.csp.sentinel.log.RecordLog; | ||||
import com.alibaba.csp.sentinel.util.StringUtil; | import com.alibaba.csp.sentinel.util.StringUtil; | ||||
import io.lettuce.core.RedisClient; | import io.lettuce.core.RedisClient; | ||||
import io.lettuce.core.RedisURI; | import io.lettuce.core.RedisURI; | ||||
import io.lettuce.core.api.sync.RedisCommands; | import io.lettuce.core.api.sync.RedisCommands; | ||||
@@ -29,38 +30,51 @@ import io.lettuce.core.pubsub.RedisPubSubAdapter; | |||||
import io.lettuce.core.pubsub.StatefulRedisPubSubConnection; | import io.lettuce.core.pubsub.StatefulRedisPubSubConnection; | ||||
import io.lettuce.core.pubsub.api.sync.RedisPubSubCommands; | import io.lettuce.core.pubsub.api.sync.RedisPubSubCommands; | ||||
import java.time.Duration; | |||||
import java.util.concurrent.TimeUnit; | import java.util.concurrent.TimeUnit; | ||||
/** | /** | ||||
* <p> | |||||
* A read-only {@code DataSource} with Redis backend. | * A read-only {@code DataSource} with Redis backend. | ||||
* </p> | |||||
* <p> | * <p> | ||||
* When data source init,reads form redis with string k-v,value is string format rule config data. | |||||
* This data source subscribe from specific channel and then data published in redis with this channel,data source | |||||
* will be notified and then update rule config in time. | |||||
* The data source first loads initial rules from a Redis String during initialization. | |||||
* Then the data source subscribe from specific channel. When new rules is published to the channel, | |||||
* the data source will observe the change in realtime and update to memory. | |||||
* </p> | |||||
* <p> | |||||
* Note that for consistency, users should publish the value and save the value to the ruleKey simultaneously | |||||
* like this (using Redis transaction): | |||||
* <pre> | |||||
* MULTI | |||||
* SET ruleKey value | |||||
* PUBLISH channel value | |||||
* EXEC | |||||
* </pre> | |||||
* </p> | * </p> | ||||
* | * | ||||
* @author tiger | * @author tiger | ||||
*/ | */ | ||||
public class RedisDataSource<T> extends AbstractDataSource<String, T> { | public class RedisDataSource<T> extends AbstractDataSource<String, T> { | ||||
private RedisClient redisClient = null; | |||||
private final RedisClient redisClient; | |||||
private String ruleKey; | |||||
private final String ruleKey; | |||||
/** | /** | ||||
* Constructor of {@code RedisDataSource} | |||||
* Constructor of {@code RedisDataSource}. | |||||
* | * | ||||
* @param connectionConfig redis connection config. | |||||
* @param ruleKey data save in redis. | |||||
* @param channel subscribe from channel. | |||||
* @param parser convert <code>ruleKey<code>`s value to {@literal alibaba/Sentinel} rule type | |||||
* @param connectionConfig Redis connection config | |||||
* @param ruleKey data key in Redis | |||||
* @param channel channel to subscribe in Redis | |||||
* @param parser customized data parser, cannot be empty | |||||
*/ | */ | ||||
public RedisDataSource(RedisConnectionConfig connectionConfig, String ruleKey, String channel, Converter<String, T> parser) { | |||||
public RedisDataSource(RedisConnectionConfig connectionConfig, String ruleKey, String channel, | |||||
Converter<String, T> parser) { | |||||
super(parser); | super(parser); | ||||
AssertUtil.notNull(connectionConfig, "redis connection config can not be null"); | |||||
AssertUtil.notEmpty(ruleKey, "redis subscribe ruleKey can not be empty"); | |||||
AssertUtil.notEmpty(channel, "redis subscribe channel can not be empty"); | |||||
AssertUtil.notNull(connectionConfig, "Redis connection config can not be null"); | |||||
AssertUtil.notEmpty(ruleKey, "Redis ruleKey can not be empty"); | |||||
AssertUtil.notEmpty(channel, "Redis subscribe channel can not be empty"); | |||||
this.redisClient = getRedisClient(connectionConfig); | this.redisClient = getRedisClient(connectionConfig); | ||||
this.ruleKey = ruleKey; | this.ruleKey = ruleKey; | ||||
loadInitialConfig(); | loadInitialConfig(); | ||||
@@ -68,28 +82,28 @@ public class RedisDataSource<T> extends AbstractDataSource<String, T> { | |||||
} | } | ||||
/** | /** | ||||
* build redis client form {@code RedisConnectionConfig} with io.lettuce. | |||||
* Build Redis client fromm {@code RedisConnectionConfig}. | |||||
* | * | ||||
* @return a new {@link RedisClient} | * @return a new {@link RedisClient} | ||||
*/ | */ | ||||
private RedisClient getRedisClient(RedisConnectionConfig connectionConfig) { | private RedisClient getRedisClient(RedisConnectionConfig connectionConfig) { | ||||
if (connectionConfig.getRedisSentinels().size() == 0) { | if (connectionConfig.getRedisSentinels().size() == 0) { | ||||
RecordLog.info("start standLone mode to connect to redis"); | |||||
return getRedisStandLoneClient(connectionConfig); | |||||
RecordLog.info("[RedisDataSource] Creating stand-alone mode Redis client"); | |||||
return getRedisStandaloneClient(connectionConfig); | |||||
} else { | } else { | ||||
RecordLog.info("start redis sentinel mode to connect to redis"); | |||||
RecordLog.info("[RedisDataSource] Creating Redis Sentinel mode Redis client"); | |||||
return getRedisSentinelClient(connectionConfig); | return getRedisSentinelClient(connectionConfig); | ||||
} | } | ||||
} | } | ||||
private RedisClient getRedisStandLoneClient(RedisConnectionConfig connectionConfig) { | |||||
private RedisClient getRedisStandaloneClient(RedisConnectionConfig connectionConfig) { | |||||
char[] password = connectionConfig.getPassword(); | char[] password = connectionConfig.getPassword(); | ||||
String clientName = connectionConfig.getClientName(); | String clientName = connectionConfig.getClientName(); | ||||
RedisURI.Builder redisUriBuilder = RedisURI.builder(); | RedisURI.Builder redisUriBuilder = RedisURI.builder(); | ||||
redisUriBuilder.withHost(connectionConfig.getHost()) | redisUriBuilder.withHost(connectionConfig.getHost()) | ||||
.withPort(connectionConfig.getPort()) | |||||
.withDatabase(connectionConfig.getDatabase()) | |||||
.withTimeout(connectionConfig.getTimeout(), TimeUnit.MILLISECONDS); | |||||
.withPort(connectionConfig.getPort()) | |||||
.withDatabase(connectionConfig.getDatabase()) | |||||
.withTimeout(Duration.ofMillis(connectionConfig.getTimeout())); | |||||
if (password != null) { | if (password != null) { | ||||
redisUriBuilder.withPassword(connectionConfig.getPassword()); | redisUriBuilder.withPassword(connectionConfig.getPassword()); | ||||
} | } | ||||
@@ -113,7 +127,7 @@ public class RedisDataSource<T> extends AbstractDataSource<String, T> { | |||||
sentinelRedisUriBuilder.withClientName(clientName); | sentinelRedisUriBuilder.withClientName(clientName); | ||||
} | } | ||||
sentinelRedisUriBuilder.withSentinelMasterId(connectionConfig.getRedisSentinelMasterId()) | sentinelRedisUriBuilder.withSentinelMasterId(connectionConfig.getRedisSentinelMasterId()) | ||||
.withTimeout(connectionConfig.getTimeout(), TimeUnit.MILLISECONDS); | |||||
.withTimeout(connectionConfig.getTimeout(), TimeUnit.MILLISECONDS); | |||||
return RedisClient.create(sentinelRedisUriBuilder.build()); | return RedisClient.create(sentinelRedisUriBuilder.build()); | ||||
} | } | ||||
@@ -138,16 +152,16 @@ public class RedisDataSource<T> extends AbstractDataSource<String, T> { | |||||
} | } | ||||
@Override | @Override | ||||
public String readSource() throws Exception { | |||||
public String readSource() { | |||||
if (this.redisClient == null) { | if (this.redisClient == null) { | ||||
throw new IllegalStateException("redis client has not been initialized or error occurred"); | |||||
throw new IllegalStateException("Redis client has not been initialized or error occurred"); | |||||
} | } | ||||
RedisCommands<String, String> stringRedisCommands = redisClient.connect().sync(); | RedisCommands<String, String> stringRedisCommands = redisClient.connect().sync(); | ||||
return stringRedisCommands.get(ruleKey); | return stringRedisCommands.get(ruleKey); | ||||
} | } | ||||
@Override | @Override | ||||
public void close() throws Exception { | |||||
public void close() { | |||||
redisClient.shutdown(); | redisClient.shutdown(); | ||||
} | } | ||||
@@ -158,9 +172,8 @@ public class RedisDataSource<T> extends AbstractDataSource<String, T> { | |||||
@Override | @Override | ||||
public void message(String channel, String message) { | public void message(String channel, String message) { | ||||
RecordLog.info(String.format("[RedisDataSource] New property value received for channel %s: %s", channel, message)); | |||||
RecordLog.info(String.format("[RedisDataSource] New property value received for channel %s: %s", channel, message)); | |||||
getProperty().updateValue(parser.convert(message)); | getProperty().updateValue(parser.convert(message)); | ||||
} | } | ||||
} | } | ||||
} | } |
@@ -16,7 +16,7 @@ | |||||
package com.alibaba.csp.sentinel.datasource.redis.config; | package com.alibaba.csp.sentinel.datasource.redis.config; | ||||
import com.alibaba.csp.sentinel.datasource.redis.util.AssertUtil; | |||||
import com.alibaba.csp.sentinel.util.AssertUtil; | |||||
import com.alibaba.csp.sentinel.util.StringUtil; | import com.alibaba.csp.sentinel.util.StringUtil; | ||||
import java.util.*; | import java.util.*; | ||||
@@ -76,7 +76,6 @@ public class RedisConnectionConfig { | |||||
setTimeout(timeout); | setTimeout(timeout); | ||||
} | } | ||||
/** | /** | ||||
* Returns a new {@link RedisConnectionConfig.Builder} to construct a {@link RedisConnectionConfig}. | * Returns a new {@link RedisConnectionConfig.Builder} to construct a {@link RedisConnectionConfig}. | ||||
* | * | ||||
@@ -86,7 +85,6 @@ public class RedisConnectionConfig { | |||||
return new RedisConnectionConfig.Builder(); | return new RedisConnectionConfig.Builder(); | ||||
} | } | ||||
/** | /** | ||||
* Returns the host. | * Returns the host. | ||||
* | * | ||||
@@ -240,7 +238,6 @@ public class RedisConnectionConfig { | |||||
return redisSentinels; | return redisSentinels; | ||||
} | } | ||||
@Override | @Override | ||||
public String toString() { | public String toString() { | ||||
final StringBuilder sb = new StringBuilder(); | final StringBuilder sb = new StringBuilder(); | ||||
@@ -269,7 +266,7 @@ public class RedisConnectionConfig { | |||||
if (!(o instanceof RedisConnectionConfig)) { | if (!(o instanceof RedisConnectionConfig)) { | ||||
return false; | return false; | ||||
} | } | ||||
RedisConnectionConfig redisURI = (RedisConnectionConfig) o; | |||||
RedisConnectionConfig redisURI = (RedisConnectionConfig)o; | |||||
if (port != redisURI.port) { | if (port != redisURI.port) { | ||||
return false; | return false; | ||||
@@ -280,10 +277,12 @@ public class RedisConnectionConfig { | |||||
if (host != null ? !host.equals(redisURI.host) : redisURI.host != null) { | if (host != null ? !host.equals(redisURI.host) : redisURI.host != null) { | ||||
return false; | return false; | ||||
} | } | ||||
if (redisSentinelMasterId != null ? !redisSentinelMasterId.equals(redisURI.redisSentinelMasterId) : redisURI.redisSentinelMasterId != null) { | |||||
if (redisSentinelMasterId != null ? !redisSentinelMasterId.equals(redisURI.redisSentinelMasterId) | |||||
: redisURI.redisSentinelMasterId != null) { | |||||
return false; | return false; | ||||
} | } | ||||
return !(redisSentinels != null ? !redisSentinels.equals(redisURI.redisSentinels) : redisURI.redisSentinels != null); | |||||
return !(redisSentinels != null ? !redisSentinels.equals(redisURI.redisSentinels) | |||||
: redisURI.redisSentinels != null); | |||||
} | } | ||||
@@ -297,7 +296,6 @@ public class RedisConnectionConfig { | |||||
return result; | return result; | ||||
} | } | ||||
/** | /** | ||||
* Builder for Redis RedisConnectionConfig. | * Builder for Redis RedisConnectionConfig. | ||||
*/ | */ | ||||
@@ -315,7 +313,6 @@ public class RedisConnectionConfig { | |||||
private Builder() { | private Builder() { | ||||
} | } | ||||
/** | /** | ||||
* Set Redis host. Creates a new builder. | * Set Redis host. Creates a new builder. | ||||
* | * | ||||
@@ -435,7 +432,8 @@ public class RedisConnectionConfig { | |||||
*/ | */ | ||||
public RedisConnectionConfig.Builder withHost(String host) { | public RedisConnectionConfig.Builder withHost(String host) { | ||||
AssertUtil.assertState(this.redisSentinels.isEmpty(), "Sentinels are non-empty. Cannot use in Sentinel mode."); | |||||
AssertUtil.assertState(this.redisSentinels.isEmpty(), | |||||
"Sentinels are non-empty. Cannot use in Sentinel mode."); | |||||
AssertUtil.notEmpty(host, "Host must not be empty"); | AssertUtil.notEmpty(host, "Host must not be empty"); | ||||
this.host = host; | this.host = host; | ||||
@@ -548,7 +546,8 @@ public class RedisConnectionConfig { | |||||
if (redisSentinels.isEmpty() && StringUtil.isEmpty(host)) { | if (redisSentinels.isEmpty() && StringUtil.isEmpty(host)) { | ||||
throw new IllegalStateException( | throw new IllegalStateException( | ||||
"Cannot build a RedisConnectionConfig. One of the following must be provided Host, Socket or Sentinel"); | |||||
"Cannot build a RedisConnectionConfig. One of the following must be provided Host, Socket or " | |||||
+ "Sentinel"); | |||||
} | } | ||||
RedisConnectionConfig redisURI = new RedisConnectionConfig(); | RedisConnectionConfig redisURI = new RedisConnectionConfig(); | ||||
@@ -565,7 +564,8 @@ public class RedisConnectionConfig { | |||||
redisURI.setRedisSentinelMasterId(redisSentinelMasterId); | redisURI.setRedisSentinelMasterId(redisSentinelMasterId); | ||||
for (RedisHostAndPort sentinel : redisSentinels) { | for (RedisHostAndPort sentinel : redisSentinels) { | ||||
redisURI.getRedisSentinels().add(new RedisConnectionConfig(sentinel.getHost(), sentinel.getPort(), timeout)); | |||||
redisURI.getRedisSentinels().add( | |||||
new RedisConnectionConfig(sentinel.getHost(), sentinel.getPort(), timeout)); | |||||
} | } | ||||
redisURI.setTimeout(timeout); | redisURI.setTimeout(timeout); | ||||
@@ -16,7 +16,7 @@ | |||||
package com.alibaba.csp.sentinel.datasource.redis.config; | package com.alibaba.csp.sentinel.datasource.redis.config; | ||||
import com.alibaba.csp.sentinel.datasource.redis.util.AssertUtil; | |||||
import com.alibaba.csp.sentinel.util.AssertUtil; | |||||
/** | /** | ||||
* An immutable representation of a host and port. | * An immutable representation of a host and port. | ||||
@@ -85,7 +85,7 @@ public class RedisHostAndPort { | |||||
if (!(o instanceof RedisHostAndPort)) { | if (!(o instanceof RedisHostAndPort)) { | ||||
return false; | return false; | ||||
} | } | ||||
RedisHostAndPort that = (RedisHostAndPort) o; | |||||
RedisHostAndPort that = (RedisHostAndPort)o; | |||||
return port == that.port && (host != null ? host.equals(that.host) : that.host == null); | return port == that.port && (host != null ? host.equals(that.host) : that.host == null); | ||||
} | } | ||||
@@ -1,81 +0,0 @@ | |||||
/* | |||||
* 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.datasource.redis.util; | |||||
import com.alibaba.csp.sentinel.util.StringUtil; | |||||
/** | |||||
* A util class for filed check | |||||
* | |||||
* @author tiger | |||||
*/ | |||||
public class AssertUtil { | |||||
private AssertUtil(){} | |||||
/** | |||||
* Assert that a string is not empty, it must not be {@code null} and it must not be empty. | |||||
* | |||||
* @param string the object to check | |||||
* @param message the exception message to use if the assertion fails | |||||
* @throws IllegalArgumentException if the object is {@code null} or the underlying string is empty | |||||
*/ | |||||
public static void notEmpty(String string, String message) { | |||||
if (StringUtil.isEmpty(string)) { | |||||
throw new IllegalArgumentException(message); | |||||
} | |||||
} | |||||
/** | |||||
* Assert that an object is not {@code null} . | |||||
* | |||||
* @param object the object to check | |||||
* @param message the exception message to use if the assertion fails | |||||
* @throws IllegalArgumentException if the object is {@code null} | |||||
*/ | |||||
public static void notNull(Object object, String message) { | |||||
if (object == null) { | |||||
throw new IllegalArgumentException(message); | |||||
} | |||||
} | |||||
/** | |||||
* Assert that {@code value} is {@literal true}. | |||||
* | |||||
* @param value the value to check | |||||
* @param message the exception message to use if the assertion fails | |||||
* @throws IllegalArgumentException if the object array contains a {@code null} element | |||||
*/ | |||||
public static void isTrue(boolean value, String message) { | |||||
if (!value) { | |||||
throw new IllegalArgumentException(message); | |||||
} | |||||
} | |||||
/** | |||||
* Ensures the truth of an expression involving the state of the calling instance, but not involving any parameters to the | |||||
* calling method. | |||||
* | |||||
* @param condition a boolean expression | |||||
* @param message the exception message to use if the assertion fails | |||||
* @throws IllegalStateException if {@code expression} is false | |||||
*/ | |||||
public static void assertState(boolean condition, String message) { | |||||
if (!condition) { | |||||
throw new IllegalStateException(message); | |||||
} | |||||
} | |||||
} |
@@ -17,10 +17,10 @@ | |||||
package com.alibaba.csp.sentinel.datasource.redis; | package com.alibaba.csp.sentinel.datasource.redis; | ||||
import com.alibaba.csp.sentinel.datasource.redis.config.RedisConnectionConfig; | import com.alibaba.csp.sentinel.datasource.redis.config.RedisConnectionConfig; | ||||
import org.junit.Assert; | import org.junit.Assert; | ||||
import org.junit.Test; | import org.junit.Test; | ||||
/** | /** | ||||
* Test cases for {@link RedisConnectionConfig}. | * Test cases for {@link RedisConnectionConfig}. | ||||
* | * | ||||
@@ -42,8 +42,8 @@ public class RedisConnectionConfigTest { | |||||
String host = "localhost"; | String host = "localhost"; | ||||
String clientName = "clientName"; | String clientName = "clientName"; | ||||
RedisConnectionConfig redisConnectionConfig = RedisConnectionConfig.Builder.redis(host) | RedisConnectionConfig redisConnectionConfig = RedisConnectionConfig.Builder.redis(host) | ||||
.withClientName("clientName") | |||||
.build(); | |||||
.withClientName("clientName") | |||||
.build(); | |||||
Assert.assertEquals(redisConnectionConfig.getClientName(), clientName); | Assert.assertEquals(redisConnectionConfig.getClientName(), clientName); | ||||
} | } | ||||
@@ -52,8 +52,8 @@ public class RedisConnectionConfigTest { | |||||
String host = "localhost"; | String host = "localhost"; | ||||
long timeout = 70 * 1000; | long timeout = 70 * 1000; | ||||
RedisConnectionConfig redisConnectionConfig = RedisConnectionConfig.Builder.redis(host) | RedisConnectionConfig redisConnectionConfig = RedisConnectionConfig.Builder.redis(host) | ||||
.withTimeout(timeout) | |||||
.build(); | |||||
.withTimeout(timeout) | |||||
.build(); | |||||
Assert.assertEquals(redisConnectionConfig.getTimeout(), timeout); | Assert.assertEquals(redisConnectionConfig.getTimeout(), timeout); | ||||
} | } | ||||
@@ -61,11 +61,12 @@ public class RedisConnectionConfigTest { | |||||
public void testRedisSentinelDefaultPortSuccess() { | public void testRedisSentinelDefaultPortSuccess() { | ||||
String host = "localhost"; | String host = "localhost"; | ||||
RedisConnectionConfig redisConnectionConfig = RedisConnectionConfig.Builder.redisSentinel(host) | RedisConnectionConfig redisConnectionConfig = RedisConnectionConfig.Builder.redisSentinel(host) | ||||
.withPassword("211233") | |||||
.build(); | |||||
Assert.assertEquals(null, redisConnectionConfig.getHost()); | |||||
.withPassword("211233") | |||||
.build(); | |||||
Assert.assertNull(redisConnectionConfig.getHost()); | |||||
Assert.assertEquals(1, redisConnectionConfig.getRedisSentinels().size()); | Assert.assertEquals(1, redisConnectionConfig.getRedisSentinels().size()); | ||||
Assert.assertEquals(RedisConnectionConfig.DEFAULT_SENTINEL_PORT, redisConnectionConfig.getRedisSentinels().get(0).getPort()); | |||||
Assert.assertEquals(RedisConnectionConfig.DEFAULT_SENTINEL_PORT, | |||||
redisConnectionConfig.getRedisSentinels().get(0).getPort()); | |||||
} | } | ||||
@Test | @Test | ||||
@@ -74,9 +75,9 @@ public class RedisConnectionConfigTest { | |||||
String host2 = "server2"; | String host2 = "server2"; | ||||
int port2 = 1879; | int port2 = 1879; | ||||
RedisConnectionConfig redisConnectionConfig = RedisConnectionConfig.Builder.redisSentinel(host) | RedisConnectionConfig redisConnectionConfig = RedisConnectionConfig.Builder.redisSentinel(host) | ||||
.withRedisSentinel(host2, port2) | |||||
.build(); | |||||
Assert.assertEquals(null, redisConnectionConfig.getHost()); | |||||
.withRedisSentinel(host2, port2) | |||||
.build(); | |||||
Assert.assertNull(redisConnectionConfig.getHost()); | |||||
Assert.assertEquals(2, redisConnectionConfig.getRedisSentinels().size()); | Assert.assertEquals(2, redisConnectionConfig.getRedisSentinels().size()); | ||||
} | } | ||||
@@ -86,12 +87,11 @@ public class RedisConnectionConfigTest { | |||||
String host2 = "server2"; | String host2 = "server2"; | ||||
int port2 = 1879; | int port2 = 1879; | ||||
RedisConnectionConfig redisConnectionConfig = RedisConnectionConfig.Builder.redisSentinel(host) | RedisConnectionConfig redisConnectionConfig = RedisConnectionConfig.Builder.redisSentinel(host) | ||||
.withRedisSentinel(host2, port2) | |||||
.withRedisSentinel(host2, port2) | |||||
.withPassword("211233") | |||||
.build(); | |||||
Assert.assertEquals(null, redisConnectionConfig.getHost()); | |||||
.withRedisSentinel(host2, port2) | |||||
.withRedisSentinel(host2, port2) | |||||
.withPassword("211233") | |||||
.build(); | |||||
Assert.assertNull(redisConnectionConfig.getHost()); | |||||
Assert.assertEquals(3, redisConnectionConfig.getRedisSentinels().size()); | Assert.assertEquals(3, redisConnectionConfig.getRedisSentinels().size()); | ||||
} | } | ||||
} | } |
@@ -23,6 +23,7 @@ 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.FlowRuleManager; | ||||
import com.alibaba.fastjson.JSON; | import com.alibaba.fastjson.JSON; | ||||
import com.alibaba.fastjson.TypeReference; | import com.alibaba.fastjson.TypeReference; | ||||
import io.lettuce.core.RedisClient; | import io.lettuce.core.RedisClient; | ||||
import io.lettuce.core.RedisURI; | import io.lettuce.core.RedisURI; | ||||
import io.lettuce.core.api.sync.RedisCommands; | import io.lettuce.core.api.sync.RedisCommands; | ||||
@@ -36,39 +37,43 @@ import java.util.Random; | |||||
* | * | ||||
* @author tiger | * @author tiger | ||||
*/ | */ | ||||
@Ignore(value = "before run this test. you should build your own redisSentinel config in local") | |||||
@Ignore(value = "Before run this test, you need to set up your Redis Sentinel.") | |||||
public class SentinelModeRedisDataSourceTest { | public class SentinelModeRedisDataSourceTest { | ||||
private String host = "localhost"; | private String host = "localhost"; | ||||
private int redisSentinelPort = 5000; | private int redisSentinelPort = 5000; | ||||
private String redisSentinelMasterId = "mymaster"; | |||||
private String ruleKey = "redis.redisSentinel.flow.rulekey"; | |||||
private String redisSentinelMasterId = "myMaster"; | |||||
private String channel = "redis.redisSentinel.flow.channel"; | |||||
private String ruleKey = "sentinel.rules.flow.ruleKey"; | |||||
private String channel = "sentinel.rules.flow.channel"; | |||||
private final RedisClient client = RedisClient.create(RedisURI.Builder.sentinel(host, redisSentinelPort) | private final RedisClient client = RedisClient.create(RedisURI.Builder.sentinel(host, redisSentinelPort) | ||||
.withSentinelMasterId(redisSentinelMasterId).build()); | |||||
.withSentinelMasterId(redisSentinelMasterId).build()); | |||||
@Before | @Before | ||||
public void initData() { | public void initData() { | ||||
Converter<String, List<FlowRule>> flowConfigParser = buildFlowConfigParser(); | Converter<String, List<FlowRule>> flowConfigParser = buildFlowConfigParser(); | ||||
RedisConnectionConfig config = RedisConnectionConfig.builder() | RedisConnectionConfig config = RedisConnectionConfig.builder() | ||||
.withRedisSentinel(host, redisSentinelPort) | |||||
.withRedisSentinel(host, redisSentinelPort) | |||||
.withSentinelMasterId(redisSentinelMasterId).build(); | |||||
.withRedisSentinel(host, redisSentinelPort) | |||||
.withRedisSentinel(host, redisSentinelPort) | |||||
.withSentinelMasterId(redisSentinelMasterId).build(); | |||||
initRedisRuleData(); | initRedisRuleData(); | ||||
ReadableDataSource<String, List<FlowRule>> redisDataSource = new RedisDataSource<List<FlowRule>>(config, ruleKey, channel, flowConfigParser); | |||||
ReadableDataSource<String, List<FlowRule>> redisDataSource = new RedisDataSource<>(config, | |||||
ruleKey, channel, flowConfigParser); | |||||
FlowRuleManager.register2Property(redisDataSource.getProperty()); | FlowRuleManager.register2Property(redisDataSource.getProperty()); | ||||
} | } | ||||
@Test | @Test | ||||
public void testConnectToSentinelAndPubMsgSuccess() { | public void testConnectToSentinelAndPubMsgSuccess() { | ||||
int maxQueueingTimeMs = new Random().nextInt(); | int maxQueueingTimeMs = new Random().nextInt(); | ||||
String flowRulesJson = "[{\"resource\":\"test\", \"limitApp\":\"default\", \"grade\":1, \"count\":\"0.0\", \"strategy\":0, \"refResource\":null, " + | |||||
"\"controlBehavior\":0, \"warmUpPeriodSec\":10, \"maxQueueingTimeMs\":" + maxQueueingTimeMs + ", \"controller\":null}]"; | |||||
String flowRulesJson = | |||||
"[{\"resource\":\"test\", \"limitApp\":\"default\", \"grade\":1, \"count\":\"0.0\", \"strategy\":0, " | |||||
+ "\"refResource\":null, " | |||||
+ | |||||
"\"controlBehavior\":0, \"warmUpPeriodSec\":10, \"maxQueueingTimeMs\":" + maxQueueingTimeMs | |||||
+ ", \"controller\":null}]"; | |||||
RedisCommands<String, String> subCommands = client.connect().sync(); | RedisCommands<String, String> subCommands = client.connect().sync(); | ||||
subCommands.multi(); | subCommands.multi(); | ||||
subCommands.set(ruleKey, flowRulesJson); | subCommands.set(ruleKey, flowRulesJson); | ||||
@@ -97,20 +102,17 @@ public class SentinelModeRedisDataSourceTest { | |||||
} | } | ||||
private Converter<String, List<FlowRule>> buildFlowConfigParser() { | private Converter<String, List<FlowRule>> buildFlowConfigParser() { | ||||
return new Converter<String, List<FlowRule>>() { | |||||
@Override | |||||
public List<FlowRule> convert(String source) { | |||||
return JSON.parseObject(source, new TypeReference<List<FlowRule>>() { | |||||
}); | |||||
} | |||||
}; | |||||
return source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {}); | |||||
} | } | ||||
private void initRedisRuleData() { | private void initRedisRuleData() { | ||||
String flowRulesJson = "[{\"resource\":\"test\", \"limitApp\":\"default\", \"grade\":1, \"count\":\"0.0\", \"strategy\":0, \"refResource\":null, " + | |||||
String flowRulesJson = | |||||
"[{\"resource\":\"test\", \"limitApp\":\"default\", \"grade\":1, \"count\":\"0.0\", \"strategy\":0, " | |||||
+ "\"refResource\":null, " | |||||
+ | |||||
"\"controlBehavior\":0, \"warmUpPeriodSec\":10, \"maxQueueingTimeMs\":500, \"controller\":null}]"; | "\"controlBehavior\":0, \"warmUpPeriodSec\":10, \"maxQueueingTimeMs\":500, \"controller\":null}]"; | ||||
RedisCommands<String, String> stringRedisCommands = client.connect().sync(); | RedisCommands<String, String> stringRedisCommands = client.connect().sync(); | ||||
String ok = stringRedisCommands.set(ruleKey, flowRulesJson); | String ok = stringRedisCommands.set(ruleKey, flowRulesJson); | ||||
Assert.assertTrue(ok.equals("OK")); | |||||
Assert.assertEquals("OK", ok); | |||||
} | } | ||||
} | } |
@@ -17,6 +17,7 @@ | |||||
package com.alibaba.csp.sentinel.datasource.redis; | package com.alibaba.csp.sentinel.datasource.redis; | ||||
import ai.grakn.redismock.RedisServer; | import ai.grakn.redismock.RedisServer; | ||||
import com.alibaba.csp.sentinel.datasource.Converter; | import com.alibaba.csp.sentinel.datasource.Converter; | ||||
import com.alibaba.csp.sentinel.datasource.ReadableDataSource; | import com.alibaba.csp.sentinel.datasource.ReadableDataSource; | ||||
import com.alibaba.csp.sentinel.datasource.redis.config.RedisConnectionConfig; | import com.alibaba.csp.sentinel.datasource.redis.config.RedisConnectionConfig; | ||||
@@ -24,6 +25,7 @@ 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.FlowRuleManager; | ||||
import com.alibaba.fastjson.JSON; | import com.alibaba.fastjson.JSON; | ||||
import com.alibaba.fastjson.TypeReference; | import com.alibaba.fastjson.TypeReference; | ||||
import io.lettuce.core.RedisClient; | import io.lettuce.core.RedisClient; | ||||
import io.lettuce.core.RedisURI; | import io.lettuce.core.RedisURI; | ||||
import io.lettuce.core.api.sync.RedisCommands; | import io.lettuce.core.api.sync.RedisCommands; | ||||
@@ -39,24 +41,23 @@ import java.util.List; | |||||
import java.util.Random; | import java.util.Random; | ||||
/** | /** | ||||
* Redis standLone mode test cases for {@link RedisDataSource}. | |||||
* Redis stand-alone mode test cases for {@link RedisDataSource}. | |||||
* | * | ||||
* @author tiger | * @author tiger | ||||
*/ | */ | ||||
public class StandLoneRedisDataSourceTest { | |||||
public class StandaloneRedisDataSourceTest { | |||||
private static RedisServer server = null; | private static RedisServer server = null; | ||||
private RedisClient client; | private RedisClient client; | ||||
private String ruleKey = "redisSentinel.flow.rulekey"; | |||||
private String channel = "redisSentinel.flow.channel"; | |||||
private String ruleKey = "sentinel.rules.flow.ruleKey"; | |||||
private String channel = "sentinel.rules.flow.channel"; | |||||
@Before | @Before | ||||
public void buildResource() { | public void buildResource() { | ||||
try { | try { | ||||
//bind to a random port | |||||
// Bind to a random port. | |||||
server = RedisServer.newRedisServer(); | server = RedisServer.newRedisServer(); | ||||
server.start(); | server.start(); | ||||
} catch (IOException e) { | } catch (IOException e) { | ||||
@@ -65,11 +66,12 @@ public class StandLoneRedisDataSourceTest { | |||||
Converter<String, List<FlowRule>> flowConfigParser = buildFlowConfigParser(); | Converter<String, List<FlowRule>> flowConfigParser = buildFlowConfigParser(); | ||||
client = RedisClient.create(RedisURI.create(server.getHost(), server.getBindPort())); | client = RedisClient.create(RedisURI.create(server.getHost(), server.getBindPort())); | ||||
RedisConnectionConfig config = RedisConnectionConfig.builder() | RedisConnectionConfig config = RedisConnectionConfig.builder() | ||||
.withHost(server.getHost()) | |||||
.withPort(server.getBindPort()) | |||||
.build(); | |||||
.withHost(server.getHost()) | |||||
.withPort(server.getBindPort()) | |||||
.build(); | |||||
initRedisRuleData(); | initRedisRuleData(); | ||||
ReadableDataSource<String, List<FlowRule>> redisDataSource = new RedisDataSource<List<FlowRule>>(config, ruleKey, channel, flowConfigParser); | |||||
ReadableDataSource<String, List<FlowRule>> redisDataSource = new RedisDataSource<List<FlowRule>>(config, | |||||
ruleKey, channel, flowConfigParser); | |||||
FlowRuleManager.register2Property(redisDataSource.getProperty()); | FlowRuleManager.register2Property(redisDataSource.getProperty()); | ||||
} | } | ||||
@@ -79,8 +81,12 @@ public class StandLoneRedisDataSourceTest { | |||||
Assert.assertEquals(1, rules.size()); | Assert.assertEquals(1, rules.size()); | ||||
int maxQueueingTimeMs = new Random().nextInt(); | int maxQueueingTimeMs = new Random().nextInt(); | ||||
StatefulRedisPubSubConnection<String, String> connection = client.connectPubSub(); | StatefulRedisPubSubConnection<String, String> connection = client.connectPubSub(); | ||||
String flowRules = "[{\"resource\":\"test\", \"limitApp\":\"default\", \"grade\":1, \"count\":\"0.0\", \"strategy\":0, \"refResource\":null, " + | |||||
"\"controlBehavior\":0, \"warmUpPeriodSec\":10, \"maxQueueingTimeMs\":" + maxQueueingTimeMs + ", \"controller\":null}]"; | |||||
String flowRules = | |||||
"[{\"resource\":\"test\", \"limitApp\":\"default\", \"grade\":1, \"count\":\"0.0\", \"strategy\":0, " | |||||
+ "\"refResource\":null, " | |||||
+ | |||||
"\"controlBehavior\":0, \"warmUpPeriodSec\":10, \"maxQueueingTimeMs\":" + maxQueueingTimeMs | |||||
+ ", \"controller\":null}]"; | |||||
RedisPubSubCommands<String, String> subCommands = connection.sync(); | RedisPubSubCommands<String, String> subCommands = connection.sync(); | ||||
subCommands.multi(); | subCommands.multi(); | ||||
subCommands.set(ruleKey, flowRules); | subCommands.set(ruleKey, flowRules); | ||||
@@ -113,7 +119,7 @@ public class StandLoneRedisDataSourceTest { | |||||
RedisCommands<String, String> stringRedisCommands = client.connect().sync(); | RedisCommands<String, String> stringRedisCommands = client.connect().sync(); | ||||
stringRedisCommands.del(ruleKey); | stringRedisCommands.del(ruleKey); | ||||
String value = stringRedisCommands.get(ruleKey); | String value = stringRedisCommands.get(ruleKey); | ||||
Assert.assertEquals(value, null); | |||||
Assert.assertNull(value); | |||||
} | } | ||||
@After | @After | ||||
@@ -126,17 +132,14 @@ public class StandLoneRedisDataSourceTest { | |||||
} | } | ||||
private Converter<String, List<FlowRule>> buildFlowConfigParser() { | private Converter<String, List<FlowRule>> buildFlowConfigParser() { | ||||
return new Converter<String, List<FlowRule>>() { | |||||
@Override | |||||
public List<FlowRule> convert(String source) { | |||||
return JSON.parseObject(source, new TypeReference<List<FlowRule>>() { | |||||
}); | |||||
} | |||||
}; | |||||
return source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {}); | |||||
} | } | ||||
private void initRedisRuleData() { | private void initRedisRuleData() { | ||||
String flowRulesJson = "[{\"resource\":\"test\", \"limitApp\":\"default\", \"grade\":1, \"count\":\"0.0\", \"strategy\":0, \"refResource\":null, " + | |||||
String flowRulesJson = | |||||
"[{\"resource\":\"test\", \"limitApp\":\"default\", \"grade\":1, \"count\":\"0.0\", \"strategy\":0, " | |||||
+ "\"refResource\":null, " | |||||
+ | |||||
"\"controlBehavior\":0, \"warmUpPeriodSec\":10, \"maxQueueingTimeMs\":500, \"controller\":null}]"; | "\"controlBehavior\":0, \"warmUpPeriodSec\":10, \"maxQueueingTimeMs\":500, \"controller\":null}]"; | ||||
RedisCommands<String, String> stringRedisCommands = client.connect().sync(); | RedisCommands<String, String> stringRedisCommands = client.connect().sync(); | ||||
String ok = stringRedisCommands.set(ruleKey, flowRulesJson); | String ok = stringRedisCommands.set(ruleKey, flowRulesJson); |