Skip to content

Commit

Permalink
Improvements for Redis data source and demo
Browse files Browse the repository at this point in the history
Signed-off-by: Eric Zhao <[email protected]>
  • Loading branch information
sczyh30 committed Sep 14, 2018
1 parent 4ff2e37 commit b1f3367
Show file tree
Hide file tree
Showing 11 changed files with 207 additions and 214 deletions.
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
2 changes: 1 addition & 1 deletion sentinel-demo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ The examples demonstrate:
- How to use various data source extensions of Sentinel (e.g. file, Nacos, ZooKeeper)
- How to use Dubbo with Sentinel
- How to use Apache RocketMQ client with Sentinel

- How to use Sentinel annotation support
52 changes: 28 additions & 24 deletions sentinel-extension/sentinel-datasource-redis/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# 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:

Expand All @@ -25,39 +27,41 @@ ReadableDataSource<String, List<FlowRule>> redisDataSource = new RedisDataSource
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
PUBLISH channel value
SET ruleKey value
PUBLISH channel value
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


### Build with redis standLone mode
### Build with Redis standalone mode

```java
RedisConnectionConfig config = RedisConnectionConfig.builder()
Expand All @@ -70,7 +74,7 @@ RedisConnectionConfig config = RedisConnectionConfig.builder()
```


### Build with redis sentinel mode
### Build with Redis Sentinel mode

```java
RedisConnectionConfig config = RedisConnectionConfig.builder()
Expand Down
12 changes: 8 additions & 4 deletions sentinel-extension/sentinel-datasource-redis/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,24 @@
<packaging>jar</packaging>

<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>
<redis.mock.version>0.1.6</redis.mock.version>
</properties>

<dependencies>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-extension</artifactId>
</dependency>

<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>${lettuce.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-extension</artifactId>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,77 +19,91 @@
import com.alibaba.csp.sentinel.datasource.AbstractDataSource;
import com.alibaba.csp.sentinel.datasource.Converter;
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.util.StringUtil;

import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
import io.lettuce.core.api.sync.RedisCommands;
import io.lettuce.core.pubsub.RedisPubSubAdapter;
import io.lettuce.core.pubsub.StatefulRedisPubSubConnection;
import io.lettuce.core.pubsub.api.sync.RedisPubSubCommands;

import java.time.Duration;
import java.util.concurrent.TimeUnit;

/**
* <p>
* A read-only {@code DataSource} with Redis backend.
* </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>
*
* @author tiger
*/

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);
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.ruleKey = ruleKey;
loadInitialConfig();
subscribeFromChannel(channel);
}

/**
* build redis client form {@code RedisConnectionConfig} with io.lettuce.
* Build Redis client fromm {@code RedisConnectionConfig}.
*
* @return a new {@link RedisClient}
*/
private RedisClient getRedisClient(RedisConnectionConfig connectionConfig) {
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 {
RecordLog.info("start redis sentinel mode to connect to redis");
RecordLog.info("[RedisDataSource] Creating Redis Sentinel mode Redis client");
return getRedisSentinelClient(connectionConfig);
}
}

private RedisClient getRedisStandLoneClient(RedisConnectionConfig connectionConfig) {
private RedisClient getRedisStandaloneClient(RedisConnectionConfig connectionConfig) {
char[] password = connectionConfig.getPassword();
String clientName = connectionConfig.getClientName();
RedisURI.Builder redisUriBuilder = RedisURI.builder();
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) {
redisUriBuilder.withPassword(connectionConfig.getPassword());
}
Expand All @@ -113,7 +127,7 @@ private RedisClient getRedisSentinelClient(RedisConnectionConfig connectionConfi
sentinelRedisUriBuilder.withClientName(clientName);
}
sentinelRedisUriBuilder.withSentinelMasterId(connectionConfig.getRedisSentinelMasterId())
.withTimeout(connectionConfig.getTimeout(), TimeUnit.MILLISECONDS);
.withTimeout(connectionConfig.getTimeout(), TimeUnit.MILLISECONDS);
return RedisClient.create(sentinelRedisUriBuilder.build());
}

Expand All @@ -138,16 +152,16 @@ private void loadInitialConfig() {
}

@Override
public String readSource() throws Exception {
public String readSource() {
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();
return stringRedisCommands.get(ruleKey);
}

@Override
public void close() throws Exception {
public void close() {
redisClient.shutdown();
}

Expand All @@ -158,9 +172,8 @@ private class DelegatingRedisPubSubListener extends RedisPubSubAdapter<String, S

@Override
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));
}
}

}
Loading

0 comments on commit b1f3367

Please sign in to comment.