init
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
# Sentinel DataSource Eureka
|
||||
|
||||
Sentinel DataSource Eureka provides integration with [Eureka](https://github.com/Netflix/eureka) so that Eureka
|
||||
can be the dynamic rule data source of Sentinel.
|
||||
|
||||
To use Sentinel DataSource Eureka, you should add the following dependency:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-datasource-eureka</artifactId>
|
||||
<version>x.y.z</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
Then you can create an `EurekaDataSource` and register to rule managers.
|
||||
|
||||
SDK usage:
|
||||
|
||||
```java
|
||||
EurekaDataSource<List<FlowRule>> eurekaDataSource = new EurekaDataSource("app-id", "instance-id",
|
||||
Arrays.asList("http://localhost:8761/eureka", "http://localhost:8762/eureka", "http://localhost:8763/eureka"),
|
||||
"rule-key", flowRuleParser);
|
||||
FlowRuleManager.register2Property(eurekaDataSource.getProperty());
|
||||
```
|
||||
|
||||
Example for Spring Cloud Application:
|
||||
|
||||
```java
|
||||
@Bean
|
||||
public EurekaDataSource<List<FlowRule>> eurekaDataSource(EurekaInstanceConfig eurekaInstanceConfig, EurekaClientConfig eurekaClientConfig) {
|
||||
|
||||
List<String> serviceUrls = EndpointUtils.getServiceUrlsFromConfig(eurekaClientConfig,
|
||||
eurekaInstanceConfig.getMetadataMap().get("zone"), eurekaClientConfig.shouldPreferSameZoneEureka());
|
||||
|
||||
EurekaDataSource<List<FlowRule>> eurekaDataSource = new EurekaDataSource(eurekaInstanceConfig.getAppname(),
|
||||
eurekaInstanceConfig.getInstanceId(), serviceUrls, "flowrules", new Converter<String, List<FlowRule>>() {
|
||||
@Override
|
||||
public List<FlowRule> convert(String o) {
|
||||
return JSON.parseObject(o, new TypeReference<List<FlowRule>>() {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
FlowRuleManager.register2Property(eurekaDataSource.getProperty());
|
||||
return eurekaDataSource;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
To refresh the rule dynamically, you need to call [Eureka-REST-operations](https://github.com/Netflix/eureka/wiki/Eureka-REST-operations)
|
||||
to update instance metadata:
|
||||
|
||||
```
|
||||
PUT /eureka/apps/{appID}/{instanceID}/metadata?{ruleKey}={json of the rules}
|
||||
```
|
||||
|
||||
Note: don't forget to encode your JSON string in the url.
|
||||
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>sentinel-extension</artifactId>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<version>1.8.3</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>sentinel-datasource-eureka</artifactId>
|
||||
|
||||
<properties>
|
||||
<spring.cloud.version>2.1.2.RELEASE</spring.cloud.version>
|
||||
</properties>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-datasource-extension</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.awaitility</groupId>
|
||||
<artifactId>awaitility</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<version>${spring.cloud.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
|
||||
<version>${spring.cloud.version}</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* Copyright 1999-2020 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
|
||||
*
|
||||
* https://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.eureka;
|
||||
|
||||
import com.alibaba.csp.sentinel.datasource.AutoRefreshDataSource;
|
||||
import com.alibaba.csp.sentinel.datasource.Converter;
|
||||
import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* A {@link ReadableDataSource} based on Eureka. This class will automatically
|
||||
* fetches the metadata of the instance every period.
|
||||
* </p>
|
||||
* <p>
|
||||
* Limitations: Default refresh interval is 10s. Because there is synchronization between eureka servers,
|
||||
* it may take longer to take effect.
|
||||
* </p>
|
||||
*
|
||||
* @author liyang
|
||||
* @since 1.8.0
|
||||
*/
|
||||
public class EurekaDataSource<T> extends AutoRefreshDataSource<String, T> {
|
||||
|
||||
private static final long DEFAULT_REFRESH_MS = 10000;
|
||||
|
||||
/**
|
||||
* Default connect timeout: 3s
|
||||
*/
|
||||
private static final int DEFAULT_CONNECT_TIMEOUT_MS = 3000;
|
||||
|
||||
/**
|
||||
* Default read timeout: 30s
|
||||
*/
|
||||
private static final int DEFAULT_READ_TIMEOUT_MS = 30000;
|
||||
|
||||
private final int connectTimeoutMills;
|
||||
private final int readTimeoutMills;
|
||||
|
||||
/**
|
||||
* Eureka instance app ID.
|
||||
*/
|
||||
private final String appId;
|
||||
/**
|
||||
* Eureka instance id.
|
||||
*/
|
||||
private final String instanceId;
|
||||
|
||||
/**
|
||||
* Eureka server URL list.
|
||||
*/
|
||||
private final List<String> serviceUrls;
|
||||
|
||||
/**
|
||||
* Metadata key of the rule source.
|
||||
*/
|
||||
private final String ruleKey;
|
||||
|
||||
public EurekaDataSource(String appId, String instanceId, List<String> serviceUrls, String ruleKey,
|
||||
Converter<String, T> configParser) {
|
||||
this(appId, instanceId, serviceUrls, ruleKey, configParser, DEFAULT_REFRESH_MS, DEFAULT_CONNECT_TIMEOUT_MS,
|
||||
DEFAULT_READ_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
public EurekaDataSource(String appId, String instanceId, List<String> serviceUrls, String ruleKey,
|
||||
Converter<String, T> configParser, long refreshMs, int connectTimeoutMills,
|
||||
int readTimeoutMills) {
|
||||
super(configParser, refreshMs);
|
||||
AssertUtil.notNull(appId, "appId can't be null");
|
||||
AssertUtil.notNull(instanceId, "instanceId can't be null");
|
||||
AssertUtil.assertNotEmpty(serviceUrls, "serviceUrls can't be empty");
|
||||
AssertUtil.notNull(ruleKey, "ruleKey can't be null");
|
||||
AssertUtil.assertState(connectTimeoutMills > 0, "connectTimeoutMills must be greater than 0");
|
||||
AssertUtil.assertState(readTimeoutMills > 0, "readTimeoutMills must be greater than 0");
|
||||
|
||||
this.appId = appId;
|
||||
this.instanceId = instanceId;
|
||||
this.serviceUrls = ensureEndWithSlash(serviceUrls);
|
||||
AssertUtil.assertNotEmpty(this.serviceUrls, "No available service url");
|
||||
this.ruleKey = ruleKey;
|
||||
this.connectTimeoutMills = connectTimeoutMills;
|
||||
this.readTimeoutMills = readTimeoutMills;
|
||||
}
|
||||
|
||||
private List<String> ensureEndWithSlash(List<String> serviceUrls) {
|
||||
List<String> newServiceUrls = new ArrayList<>();
|
||||
for (String serviceUrl : serviceUrls) {
|
||||
if (StringUtil.isBlank(serviceUrl)) {
|
||||
continue;
|
||||
}
|
||||
if (!serviceUrl.endsWith("/")) {
|
||||
serviceUrl = serviceUrl + "/";
|
||||
}
|
||||
newServiceUrls.add(serviceUrl);
|
||||
}
|
||||
return newServiceUrls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readSource() throws Exception {
|
||||
return fetchStringSourceFromEurekaMetadata(this.appId, this.instanceId, this.serviceUrls, ruleKey);
|
||||
}
|
||||
|
||||
private String fetchStringSourceFromEurekaMetadata(String appId, String instanceId, List<String> serviceUrls,
|
||||
String ruleKey) throws Exception {
|
||||
List<String> shuffleUrls = new ArrayList<>(serviceUrls.size());
|
||||
shuffleUrls.addAll(serviceUrls);
|
||||
Collections.shuffle(shuffleUrls);
|
||||
for (int i = 0; i < shuffleUrls.size(); i++) {
|
||||
String serviceUrl = shuffleUrls.get(i) + String.format("apps/%s/%s", appId, instanceId);
|
||||
HttpURLConnection conn = null;
|
||||
try {
|
||||
conn = (HttpURLConnection) new URL(serviceUrl).openConnection();
|
||||
conn.addRequestProperty("Accept", "application/json;charset=utf-8");
|
||||
|
||||
conn.setConnectTimeout(connectTimeoutMills);
|
||||
conn.setReadTimeout(readTimeoutMills);
|
||||
conn.setRequestMethod("GET");
|
||||
conn.setDoOutput(true);
|
||||
conn.connect();
|
||||
RecordLog.debug("[EurekaDataSource] Request from eureka server: " + serviceUrl);
|
||||
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
|
||||
String s = toString(conn.getInputStream());
|
||||
String ruleString = JSON.parseObject(s)
|
||||
.getJSONObject("instance")
|
||||
.getJSONObject("metadata")
|
||||
.getString(ruleKey);
|
||||
return ruleString;
|
||||
}
|
||||
RecordLog.warn("[EurekaDataSource] Warn: retrying on another server if available " +
|
||||
"due to response code: {}, response message: {}", conn.getResponseCode(),
|
||||
toString(conn.getErrorStream()));
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
if (conn != null) {
|
||||
RecordLog.warn("[EurekaDataSource] Warn: failed to request " + conn.getURL() + " from "
|
||||
+ InetAddress.getByName(conn.getURL().getHost()).getHostAddress(), e);
|
||||
}
|
||||
} catch (Exception e1) {
|
||||
RecordLog.warn("[EurekaDataSource] Warn: failed to request ", e1);
|
||||
//ignore
|
||||
}
|
||||
RecordLog.warn("[EurekaDataSource] Warn: failed to request,retrying on another server if available");
|
||||
|
||||
} finally {
|
||||
if (conn != null) {
|
||||
conn.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new EurekaMetadataFetchException("Can't get any data");
|
||||
}
|
||||
|
||||
public static class EurekaMetadataFetchException extends Exception {
|
||||
|
||||
public EurekaMetadataFetchException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
private String toString(InputStream input) throws IOException {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
InputStreamReader inputStreamReader = new InputStreamReader(input, "utf-8");
|
||||
CharArrayWriter sw = new CharArrayWriter();
|
||||
copy(inputStreamReader, sw);
|
||||
return sw.toString();
|
||||
}
|
||||
|
||||
private long copy(Reader input, Writer output) throws IOException {
|
||||
char[] buffer = new char[1 << 12];
|
||||
long count = 0;
|
||||
for (int n = 0; (n = input.read(buffer)) >= 0; ) {
|
||||
output.write(buffer, 0, n);
|
||||
count += n;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.datasource.eureka;
|
||||
|
||||
import com.alibaba.csp.sentinel.datasource.Converter;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.TypeReference;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.awaitility.Awaitility.await;
|
||||
|
||||
/**
|
||||
* @author liyang
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@EnableEurekaServer
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
|
||||
public class EurekaDataSourceTest {
|
||||
|
||||
private static final String SENTINEL_KEY = "sentinel-rules";
|
||||
|
||||
@Value("${server.port}")
|
||||
private int port;
|
||||
|
||||
@Value("${eureka.instance.appname}")
|
||||
private String appname;
|
||||
|
||||
@Value("${eureka.instance.instance-id}")
|
||||
private String instanceId;
|
||||
|
||||
|
||||
@Test
|
||||
public void testEurekaDataSource() throws Exception {
|
||||
String url = "http://localhost:" + port + "/eureka";
|
||||
|
||||
EurekaDataSource<List<FlowRule>> eurekaDataSource = new EurekaDataSource(appname, instanceId, Arrays.asList(url)
|
||||
, SENTINEL_KEY, new Converter<String, List<FlowRule>>() {
|
||||
@Override
|
||||
public List<FlowRule> convert(String source) {
|
||||
return JSON.parseObject(source, new TypeReference<List<FlowRule>>() {
|
||||
});
|
||||
}
|
||||
});
|
||||
FlowRuleManager.register2Property(eurekaDataSource.getProperty());
|
||||
|
||||
await().timeout(15, TimeUnit.SECONDS)
|
||||
.until(new Callable<Boolean>() {
|
||||
@Override
|
||||
public Boolean call() throws Exception {
|
||||
return FlowRuleManager.getRules().size() > 0;
|
||||
}
|
||||
});
|
||||
Assert.assertTrue(FlowRuleManager.getRules().size() > 0);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) 2018 the original author or authors.
|
||||
*
|
||||
* 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.eureka;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* @author liyang
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class SimpleSpringApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SimpleSpringApplication.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
server:
|
||||
port: 8761
|
||||
eureka:
|
||||
instance:
|
||||
instance-id: instance-0
|
||||
appname: testapp
|
||||
metadata-map:
|
||||
sentinel-rules: "[{'clusterMode':false,'controlBehavior':0,'count':20.0,'grade':1,'limitApp':'default','maxQueueingTimeMs':500,'resource':'resource-demo-name','strategy':0,'warmUpPeriodSec':10}]"
|
||||
|
||||
client:
|
||||
register-with-eureka: true
|
||||
fetch-registry: false
|
||||
service-url:
|
||||
defaultZone: http://localhost:8761/eureka/
|
||||
Reference in New Issue
Block a user