Add FileInJarReadableDataSource to support reading config file in jar (#646)
This commit is contained in:
parent
359e65932c
commit
882a06007d
|
|
@ -0,0 +1,70 @@
|
|||
package com.alibaba.csp.sentinel.demo.file.rule;
|
||||
|
||||
import com.alibaba.csp.sentinel.datasource.Converter;
|
||||
import com.alibaba.csp.sentinel.datasource.FileInJarReadableDataSource;
|
||||
import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
|
||||
import com.alibaba.csp.sentinel.property.PropertyListener;
|
||||
import com.alibaba.csp.sentinel.property.SentinelProperty;
|
||||
import com.alibaba.csp.sentinel.slots.block.Rule;
|
||||
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
|
||||
import com.alibaba.csp.sentinel.slots.system.SystemRuleManager;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.TypeReference;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* This Demo shows how to use {@link FileInJarReadableDataSource} to read {@link Rule}s from jarfile. The
|
||||
* {@link FileInJarReadableDataSource} will automatically fetches the backend file every 3 seconds, and
|
||||
* inform the listener if the file is updated.
|
||||
* </p>
|
||||
* <p>
|
||||
* Each {@link ReadableDataSource} has a {@link SentinelProperty} to hold the deserialized config data.
|
||||
* {@link PropertyListener} will listen to the {@link SentinelProperty} instead of the datasource.
|
||||
* {@link Converter} is used for telling how to deserialize the data.
|
||||
* </p>
|
||||
* <p>
|
||||
* {@link FlowRuleManager#register2Property(SentinelProperty)},
|
||||
* {@link DegradeRuleManager#register2Property(SentinelProperty)},
|
||||
* {@link SystemRuleManager#register2Property(SentinelProperty)} could be called for listening the
|
||||
* {@link Rule}s change.
|
||||
* </p>
|
||||
* <p>
|
||||
* For other kinds of data source, such as <a href="https://github.com/alibaba/nacos">Nacos</a>,
|
||||
* Zookeeper, Git, or even CSV file, We could implement {@link ReadableDataSource} interface to read these
|
||||
* configs.
|
||||
* </p>
|
||||
*
|
||||
* @author dingq
|
||||
* @date 2019-03-30
|
||||
*/
|
||||
public class JarFileDataSourceDemo {
|
||||
public static void main(String[] args) throws Exception {
|
||||
JarFileDataSourceDemo demo = new JarFileDataSourceDemo();
|
||||
demo.listenRules();
|
||||
|
||||
/*
|
||||
* Start to require tokens, rate will be limited by rule in FlowRule.json
|
||||
*/
|
||||
FlowQpsRunner runner = new FlowQpsRunner();
|
||||
runner.simulateTraffic();
|
||||
runner.tick();
|
||||
}
|
||||
|
||||
private void listenRules() throws Exception {
|
||||
String jarPath = System.getProperty("user.dir") + "/sentinel-demo/sentinel-demo-dynamic-file-rule/target/sentinel-demo-dynamic-file-rule-1.5.1-SNAPSHOT.jar";
|
||||
// eg: if flowRuleInJarName full path is 'sentinel-demo-dynamic-file-rule-1.5.1-SNAPSHOT.jar!/classes/FlowRule.json',
|
||||
// your flowRuleInJarName is 'classes/FlowRule.json'
|
||||
String flowRuleInJarPath = "FlowRule.json";
|
||||
|
||||
FileInJarReadableDataSource<List<FlowRule>> flowRuleDataSource = new FileInJarReadableDataSource<>(
|
||||
jarPath,flowRuleInJarPath, flowRuleListParser);
|
||||
FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
|
||||
}
|
||||
|
||||
private Converter<String, List<FlowRule>> flowRuleListParser = source -> JSON.parseObject(source,
|
||||
new TypeReference<List<FlowRule>>() {});
|
||||
}
|
||||
|
|
@ -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.datasource;
|
||||
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* A {@link ReadableDataSource} based on jarfile. This class can only read file when it
|
||||
* run but will not automatically refresh if it is changed.
|
||||
* </p>
|
||||
* <p>
|
||||
* Limitations: Default read buffer size is 1 MB. If file size is greater than
|
||||
* buffer size, exceeding bytes will be ignored. Default charset is UTF-8.
|
||||
* </p>
|
||||
*
|
||||
* @author dingq
|
||||
* @date 2019-03-30
|
||||
*/
|
||||
public class FileInJarReadableDataSource<T> extends AutoRefreshDataSource<String, T> {
|
||||
private static final int MAX_SIZE = 1024 * 1024 * 4;
|
||||
private static final long DEFAULT_REFRESH_MS = 3000;
|
||||
private static final int DEFAULT_BUF_SIZE = 1024 * 1024;
|
||||
private static final Charset DEFAULT_CHAR_SET = Charset.forName("utf-8");
|
||||
|
||||
private byte[] buf;
|
||||
private JarEntry jarEntry;
|
||||
private JarFile jarFile;
|
||||
private final Charset charset;
|
||||
private final String jarName;
|
||||
private final String fileInJarName;
|
||||
|
||||
/**
|
||||
* @param jarName the jar to read
|
||||
* @param fileInJarName the file in jar to read
|
||||
* @param configParser the config decoder (parser)
|
||||
* @throws FileNotFoundException
|
||||
*/
|
||||
public FileInJarReadableDataSource(String jarName, String fileInJarName, Converter<String, T> configParser)
|
||||
throws IOException {
|
||||
this(jarName, fileInJarName, configParser, DEFAULT_REFRESH_MS, DEFAULT_BUF_SIZE, DEFAULT_CHAR_SET);
|
||||
}
|
||||
|
||||
public FileInJarReadableDataSource(String jarName, String fileInJarName, Converter<String, T> configParser, int bufSize)
|
||||
throws IOException {
|
||||
this(jarName, fileInJarName, configParser, DEFAULT_REFRESH_MS, bufSize, DEFAULT_CHAR_SET);
|
||||
}
|
||||
|
||||
public FileInJarReadableDataSource(String jarName, String fileInJarName, Converter<String, T> configParser, Charset charset)
|
||||
throws IOException {
|
||||
this(jarName, fileInJarName, configParser, DEFAULT_REFRESH_MS, DEFAULT_BUF_SIZE, charset);
|
||||
}
|
||||
|
||||
public FileInJarReadableDataSource(String jarName, String fileInJarName, Converter<String, T> configParser, long recommendRefreshMs, int bufSize,
|
||||
Charset charset) throws IOException {
|
||||
super(configParser, recommendRefreshMs);
|
||||
if (bufSize <= 0 || bufSize > MAX_SIZE) {
|
||||
throw new IllegalArgumentException("bufSize must between (0, " + MAX_SIZE + "], but " + bufSize + " get");
|
||||
}
|
||||
if (charset == null) {
|
||||
throw new IllegalArgumentException("charset can't be null");
|
||||
}
|
||||
this.buf = new byte[bufSize];
|
||||
this.charset = charset;
|
||||
this.jarName = jarName;
|
||||
this.fileInJarName = fileInJarName;
|
||||
refreshJar();
|
||||
firstLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readSource() throws Exception {
|
||||
if (null == jarEntry) {
|
||||
// Will throw FileNotFoundException later.
|
||||
RecordLog.warn(String.format("[FileInJarReadableDataSource] File does not exist: %s", jarFile.getName()));
|
||||
}
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
inputStream = jarFile.getInputStream(jarEntry);
|
||||
if (inputStream.available() > buf.length) {
|
||||
throw new IllegalStateException(jarFile.getName() + " file size=" + inputStream.available()
|
||||
+ ", is bigger than bufSize=" + buf.length + ". Can't read");
|
||||
}
|
||||
int len = inputStream.read(buf);
|
||||
return new String(buf, 0, len, charset);
|
||||
} finally {
|
||||
if (inputStream != null) {
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isModified() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void firstLoad() {
|
||||
try {
|
||||
T newValue = loadConfig();
|
||||
getProperty().updateValue(newValue);
|
||||
} catch (Throwable e) {
|
||||
RecordLog.info("loadConfig exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
super.close();
|
||||
buf = null;
|
||||
}
|
||||
|
||||
private void refreshJar() throws IOException {
|
||||
this.jarFile = new JarFile(jarName);
|
||||
this.jarEntry = jarFile.getJarEntry(fileInJarName);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue