Skip to content

Commit

Permalink
Refactor Sentinel data source hierarchy
Browse files Browse the repository at this point in the history
- Spilt DataSource into two types: ReadableDataSource and WritableDataSource
- The AbstractDataSource now is read-only
- Refactor the file data source for writable implementation
- Rename: ConfigParser -> Converter (represents both encoder `T -> S` and decoder `S -> T`)
- Some other refinement

Signed-off-by: Eric Zhao <sczyh16@gmail.com>
  • Loading branch information
sczyh30 committed Sep 4, 2018
1 parent 6799da2 commit 0060e80
Show file tree
Hide file tree
Showing 13 changed files with 183 additions and 154 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.alibaba.csp.sentinel.datasource.apollo;

import com.alibaba.csp.sentinel.datasource.AbstractDataSource;
import com.alibaba.csp.sentinel.datasource.ConfigParser;
import com.alibaba.csp.sentinel.datasource.DataSource;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.log.RecordLog;

import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigChangeListener;
import com.ctrip.framework.apollo.ConfigService;
Expand All @@ -14,81 +14,84 @@
import com.google.common.collect.Sets;

/**
* A {@link DataSource} with <a href="http://github.com/ctripcorp/apollo">Apollo</a> as its configuration source.
* A read-only {@code DataSource} with <a href="http://github.com/ctripcorp/apollo">Apollo</a> as its configuration
* source.
* <br />
* When the rule is changed in Apollo, it will take effect in real time.
*
* @author Jason Song
*/
public class ApolloDataSource<T> extends AbstractDataSource<String, T> {

private final Config config;
private final String flowRulesKey;
private final String defaultFlowRuleValue;

/**
* Constructs the Apollo data source
*
* @param namespaceName the namespace name in Apollo, should not be null or empty
* @param flowRulesKey the flow rules key in the namespace, should not be null or empty
* @param defaultFlowRuleValue the default flow rules value when the flow rules key is not found or any error occurred
* @param parser the parser to transform string configuration to actual flow rules
*/
public ApolloDataSource(String namespaceName, String flowRulesKey, String defaultFlowRuleValue,
ConfigParser<String, T> parser) {
super(parser);
private final Config config;
private final String flowRulesKey;
private final String defaultFlowRuleValue;

Preconditions.checkArgument(!Strings.isNullOrEmpty(namespaceName), "Namespace name could not be null or empty");
Preconditions.checkArgument(!Strings.isNullOrEmpty(flowRulesKey), "FlowRuleKey could not be null or empty!");
/**
* Constructs the Apollo data source
*
* @param namespaceName the namespace name in Apollo, should not be null or empty
* @param flowRulesKey the flow rules key in the namespace, should not be null or empty
* @param defaultFlowRuleValue the default flow rules value when the flow rules key is not found or any error
* occurred
* @param parser the parser to transform string configuration to actual flow rules
*/
public ApolloDataSource(String namespaceName, String flowRulesKey, String defaultFlowRuleValue,
Converter<String, T> parser) {
super(parser);

this.flowRulesKey = flowRulesKey;
this.defaultFlowRuleValue = defaultFlowRuleValue;
Preconditions.checkArgument(!Strings.isNullOrEmpty(namespaceName), "Namespace name could not be null or empty");
Preconditions.checkArgument(!Strings.isNullOrEmpty(flowRulesKey), "FlowRuleKey could not be null or empty!");

this.config = ConfigService.getConfig(namespaceName);
this.flowRulesKey = flowRulesKey;
this.defaultFlowRuleValue = defaultFlowRuleValue;

initialize();
this.config = ConfigService.getConfig(namespaceName);

RecordLog.info(String.format("Initialized rule for namespace: %s, flow rules key: %s", namespaceName, flowRulesKey));
}
initialize();

private void initialize() {
initializeConfigChangeListener();
loadAndUpdateRules();
}
RecordLog.info(String.format("Initialized rule for namespace: %s, flow rules key: %s",
namespaceName, flowRulesKey));
}

private void loadAndUpdateRules() {
try {
T newValue = loadConfig();
if (newValue == null) {
RecordLog.warn("[ApolloDataSource] WARN: rule config is null, you may have to check your data source");
}
getProperty().updateValue(newValue);
} catch (Throwable ex) {
RecordLog.warn("[ApolloDataSource] Error when loading rule config", ex);
private void initialize() {
initializeConfigChangeListener();
loadAndUpdateRules();
}
}

private void initializeConfigChangeListener() {
config.addChangeListener(new ConfigChangeListener() {
@Override
public void onChange(ConfigChangeEvent changeEvent) {
ConfigChange change = changeEvent.getChange(flowRulesKey);
//change is never null because the listener will only notify for this key
if (change != null) {
RecordLog.info("[ApolloDataSource] Received config changes: " + change.toString());
private void loadAndUpdateRules() {
try {
T newValue = loadConfig();
if (newValue == null) {
RecordLog.warn("[ApolloDataSource] WARN: rule config is null, you may have to check your data source");
}
getProperty().updateValue(newValue);
} catch (Throwable ex) {
RecordLog.warn("[ApolloDataSource] Error when loading rule config", ex);
}
loadAndUpdateRules();
}
}, Sets.newHashSet(flowRulesKey));
}
}

@Override
public String readSource() throws Exception {
return config.getProperty(flowRulesKey, defaultFlowRuleValue);
}
private void initializeConfigChangeListener() {
config.addChangeListener(new ConfigChangeListener() {
@Override
public void onChange(ConfigChangeEvent changeEvent) {
ConfigChange change = changeEvent.getChange(flowRulesKey);
//change is never null because the listener will only notify for this key
if (change != null) {
RecordLog.info("[ApolloDataSource] Received config changes: " + change.toString());
}
loadAndUpdateRules();
}
}, Sets.newHashSet(flowRulesKey));
}

@Override
public void close() throws Exception {
// nothing to destroy
}
@Override
public String readSource() throws Exception {
return config.getProperty(flowRulesKey, defaultFlowRuleValue);
}

@Override
public void close() throws Exception {
// nothing to destroy
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,20 @@
import com.alibaba.csp.sentinel.property.DynamicSentinelProperty;
import com.alibaba.csp.sentinel.property.SentinelProperty;

public abstract class AbstractDataSource<S, T> implements DataSource<S, T> {
/**
* The abstract readable data source provides basic functionality for loading and parsing config.
*
* @param <S> source data type
* @param <T> target data type
* @author Carpenter Lee
* @author Eric Zhao
*/
public abstract class AbstractDataSource<S, T> implements ReadableDataSource<S, T> {

protected final ConfigParser<S, T> parser;
protected final Converter<S, T> parser;
protected final SentinelProperty<T> property;

public AbstractDataSource(ConfigParser<S, T> parser) {
public AbstractDataSource(Converter<S, T> parser) {
if (parser == null) {
throw new IllegalArgumentException("parser can't be null");
}
Expand All @@ -33,24 +41,16 @@ public AbstractDataSource(ConfigParser<S, T> parser) {

@Override
public T loadConfig() throws Exception {
S readValue = readSource();
T value = parser.parse(readValue);
return value;
return loadConfig(readSource());
}

public T loadConfig(S conf) throws Exception {
T value = parser.parse(conf);
T value = parser.convert(conf);
return value;
}

@Override
public SentinelProperty<T> getProperty() {
return property;
}

@Override
public void writeDataSource(T values) throws Exception {
throw new UnsupportedOperationException();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import com.alibaba.csp.sentinel.log.RecordLog;

/**
* A {@link DataSource} automatically fetches the backend data.
* A {@link ReadableDataSource} automatically fetches the backend data.
*
* @param <S> source data type
* @param <T> target data type
Expand All @@ -34,12 +34,12 @@ public abstract class AutoRefreshDataSource<S, T> extends AbstractDataSource<S,
private ScheduledExecutorService service;
protected long recommendRefreshMs = 3000;

public AutoRefreshDataSource(ConfigParser<S, T> configParser) {
public AutoRefreshDataSource(Converter<S, T> configParser) {
super(configParser);
startTimerService();
}

public AutoRefreshDataSource(ConfigParser<S, T> configParser, final long recommendRefreshMs) {
public AutoRefreshDataSource(Converter<S, T> configParser, final long recommendRefreshMs) {
super(configParser);
if (recommendRefreshMs <= 0) {
throw new IllegalArgumentException("recommendRefreshMs must > 0, but " + recommendRefreshMs + " get");
Expand Down Expand Up @@ -72,5 +72,4 @@ public void close() throws Exception {
service = null;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,18 @@
package com.alibaba.csp.sentinel.datasource;

/**
* Parse config from source data type S to target data type T.
* Convert an object from source type {@code S} to target type {@code T}.
*
* @author leyou
* @author Eric Zhao
*/
public interface ConfigParser<S, T> {
public interface Converter<S, T> {

/**
* Parse {@code source} to the target format.
* Convert {@code source} to the target type.
*
* @param source the source.
* @return the target.
* @param source the source object
* @return the target object
*/
T parse(S source);
T convert(S source);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,18 @@
import com.alibaba.csp.sentinel.property.SentinelProperty;

/**
* A {@link DataSource} based on nothing. {@link EmptyDataSource#getProperty()} will always return the same cached
* A {@link ReadableDataSource} based on nothing. {@link EmptyDataSource#getProperty()} will always return the same cached
* {@link SentinelProperty} that doing nothing.
* <br/>
* This class is used when we want to use default settings instead of configs from the {@link DataSource}
* This class is used when we want to use default settings instead of configs from the {@link ReadableDataSource}.
*
* @author leyou
*/
public class EmptyDataSource implements DataSource<Object, Object> {
public final class EmptyDataSource implements ReadableDataSource<Object, Object> {

public static final DataSource<Object, Object> EMPTY_DATASOURCE = new EmptyDataSource();
public static final ReadableDataSource<Object, Object> EMPTY_DATASOURCE = new EmptyDataSource();

private static final SentinelProperty<Object> property = new NoOpSentinelProperty();
private static final SentinelProperty<Object> PROPERTY = new NoOpSentinelProperty();

private EmptyDataSource() { }

Expand All @@ -46,15 +46,9 @@ public Object readSource() throws Exception {

@Override
public SentinelProperty<Object> getProperty() {
return property;
return PROPERTY;
}

@Override
public void close() throws Exception { }

@Override
public void writeDataSource(Object config) throws Exception {
throw new UnsupportedOperationException();
}

}
Loading

0 comments on commit 0060e80

Please sign in to comment.