diff --git a/.editorconfig b/.editorconfig index 8c727f3..0d996cf 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,4 +1,4 @@ -# EditorConfig is awesome: http://EditorConfig.org +# EditorConfig is awesome: https://EditorConfig.org # top-most EditorConfig file root = true diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java index 0cf25d6..474be33 100755 --- a/.mvn/wrapper/MavenWrapperDownloader.java +++ b/.mvn/wrapper/MavenWrapperDownloader.java @@ -7,7 +7,7 @@ to you 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 + 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 diff --git a/.settings.xml b/.settings.xml index 6c35512..08951ca 100644 --- a/.settings.xml +++ b/.settings.xml @@ -21,7 +21,7 @@ spring-snapshots Spring Snapshots - http://repo.spring.io/libs-snapshot-local + https://repo.spring.io/libs-snapshot-local true @@ -29,7 +29,7 @@ spring-milestones Spring Milestones - http://repo.spring.io/libs-milestone-local + https://repo.spring.io/libs-milestone-local false @@ -37,7 +37,7 @@ spring-releases Spring Releases - http://repo.spring.io/release + https://repo.spring.io/release false @@ -47,7 +47,7 @@ spring-snapshots Spring Snapshots - http://repo.spring.io/libs-snapshot-local + https://repo.spring.io/libs-snapshot-local true @@ -55,7 +55,7 @@ spring-milestones Spring Milestones - http://repo.spring.io/libs-milestone-local + https://repo.spring.io/libs-milestone-local false diff --git a/LICENSE.txt b/LICENSE.txt index d645695..62589ed 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,7 +1,7 @@ Apache License Version 2.0, January 2004 - http://www.apache.org/licenses/ + https://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION @@ -193,7 +193,7 @@ 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 + 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, diff --git a/README.adoc b/README.adoc index 2475709..96d5f6c 100644 --- a/README.adoc +++ b/README.adoc @@ -10,8 +10,8 @@ image::https://api.codacy.com/project/badge/Grade/a6885a06921e4f72a0df0b7aabd6d1 :github-tag: master :github-repo: spring-cloud-incubator/spring-cloud-circuitbreaker -:github-raw: http://raw.github.com/{github-repo}/{github-tag} -:github-code: http://github.com/{github-repo}/tree/{github-tag} +:github-raw: https://raw.github.com/{github-repo}/{github-tag} +:github-code: https://github.com/{github-repo}/tree/{github-tag} :all: {asterisk}{asterisk} :nofooter: :branch: master @@ -290,7 +290,7 @@ credentials and you already have those. The projects that require middleware generally include a `docker-compose.yml`, so consider using -http://compose.docker.io/[Docker Compose] to run the middeware servers +https://compose.docker.io/[Docker Compose] to run the middeware servers in Docker containers. See the README in the https://github.com/spring-cloud-samples/scripts[scripts demo repository] for specific instructions about the common cases of mongo, @@ -312,13 +312,13 @@ a modified file in the correct place. Just commit it and push the change. === Working with the code If you don't have an IDE preference we would recommend that you use -http://www.springsource.com/developer/sts[Spring Tools Suite] or -http://eclipse.org[Eclipse] when working with the code. We use the -http://eclipse.org/m2e/[m2eclipse] eclipse plugin for maven support. Other IDEs and tools +https://www.springsource.com/developer/sts[Spring Tools Suite] or +https://eclipse.org[Eclipse] when working with the code. We use the +https://eclipse.org/m2e/[m2eclipse] eclipse plugin for maven support. Other IDEs and tools should also work without issue as long as they use Maven 3.3.3 or better. ==== Importing into eclipse with m2eclipse -We recommend the http://eclipse.org/m2e/[m2eclipse] eclipse plugin when working with +We recommend the https://eclipse.org/m2e/[m2eclipse] eclipse plugin when working with eclipse. If you don't already have m2eclipse installed it is available from the "eclipse marketplace". @@ -377,7 +377,7 @@ added after the original pull request but before a merge. `eclipse-code-formatter.xml` file from the https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/spring-cloud-dependencies-parent/eclipse-code-formatter.xml[Spring Cloud Build] project. If using IntelliJ, you can use the - http://plugins.jetbrains.com/plugin/6546[Eclipse Code Formatter + https://plugins.jetbrains.com/plugin/6546[Eclipse Code Formatter Plugin] to import the same file. * Make sure all new `.java` files to have a simple Javadoc class comment with at least an `@author` tag identifying you, and preferably at least a paragraph on what the class is @@ -390,7 +390,7 @@ added after the original pull request but before a merge. * A few unit tests would help a lot as well -- someone has to do it. * If no-one else is using your branch, please rebase it against the current master (or other target branch in the main project). -* When writing a commit message please follow http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html[these conventions], +* When writing a commit message please follow https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html[these conventions], if you are fixing an existing issue please add `Fixes gh-XXXX` at the end of the commit message (where XXXX is the issue number). @@ -461,7 +461,7 @@ If you need to suppress some rules (e.g. line length needs to be longer), then i + "https://www.puppycrawl.com/dtds/suppressions_1_1.dtd"> diff --git a/docs/pom.xml b/docs/pom.xml index ad6ed86..4a34138 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -1,7 +1,7 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> spring-cloud-circuitbreaker org.springframework.cloud diff --git a/docs/src/main/asciidoc/spring-cloud-circuitbreaker-sentinel.adoc b/docs/src/main/asciidoc/spring-cloud-circuitbreaker-sentinel.adoc new file mode 100644 index 0000000..6671dff --- /dev/null +++ b/docs/src/main/asciidoc/spring-cloud-circuitbreaker-sentinel.adoc @@ -0,0 +1,75 @@ +=== Configuring Sentinel Circuit Breakers + +==== Default Configuration + +To provide a default configuration for all of your circuit breakers create a `Customizer` bean that is passed a +`SentinelCircuitBreakerFactory` or `ReactiveSentinelCircuitBreakerFactory`. +The `configureDefault` method can be used to provide a default configuration. + +==== +[source,java] +---- +@Bean +public Customizer defaultCustomizer() { + return factory -> factory.configureDefault(id -> new SentinelConfigBuilder(id) + .build()); +} +---- +==== + +You can choose to provide default circuit breaking rules via `SentinelConfigBuilder#rules(rules)`. +You can also choose to load circuit breaking rules later elsewhere using +`DegradeRuleManager.loadRules(rules)` API of Sentinel, or via Sentinel dashboard. + + +===== Reactive Example + +==== +[source,java] +---- +@Bean +public Customizer defaultCustomizer() { + return factory -> factory.configureDefault(id -> new SentinelConfigBuilder(id) + .build()); +} +---- +==== + + +==== Specific Circuit Breaker Configuration + +Similarly to providing a default configuration, you can create a `Customizer` bean this is passed a +`SentinelCircuitBreakerFactory`. + +==== +[source,java] +---- +@Bean +public Customizer slowCustomizer() { + String slowId = "slow"; + List rules = Collections.singletonList( + new DegradeRule(slowId).setGrade(RuleConstant.DEGRADE_GRADE_RT) + .setCount(100) + .setTimeWindow(10) + ); + return factory -> factory.configure(builder -> builder.rules(rules), slowId); +} +---- +==== + +===== Reactive Example + +==== +[source,java] +---- +@Bean +public Customizer customizer() { + List rules = Collections.singletonList( + new DegradeRule().setGrade(RuleConstant.DEGRADE_GRADE_RT) + .setCount(100) + .setTimeWindow(10) + ); + return factory -> factory.configure(builder -> builder.rules(rules), "foo", "bar"); +} +---- +==== diff --git a/docs/src/main/asciidoc/spring-cloud-circuitbreaker.adoc b/docs/src/main/asciidoc/spring-cloud-circuitbreaker.adoc index a1abd0f..9f01d85 100755 --- a/docs/src/main/asciidoc/spring-cloud-circuitbreaker.adoc +++ b/docs/src/main/asciidoc/spring-cloud-circuitbreaker.adoc @@ -1,7 +1,7 @@ :github-tag: master :github-repo: spring-cloud-incubator/spring-cloud-circuitbreaker -:github-raw: http://raw.github.com/{github-repo}/{github-tag} -:github-code: http://github.com/{github-repo}/tree/{github-tag} +:github-raw: https://raw.github.com/{github-repo}/{github-tag} +:github-code: https://github.com/{github-repo}/tree/{github-tag} :all: {asterisk}{asterisk} :nofooter: :branch: master @@ -20,6 +20,7 @@ breaker implementation that best fits your needs for your app. * https://github.com/Netflix/Hystrix[Netfix Hystrix] * https://github.com/resilience4j/resilience4j[Resilience4J] +* https://github.com/alibaba/Sentinel[Sentinel] == Core Concepts @@ -96,6 +97,9 @@ include::spring-cloud-circuitbreaker-hystrix.adoc[] include::spring-cloud-circuitbreaker-resilience4j.adoc[] + +include::spring-cloud-circuitbreaker-sentinel.adoc[] + == Building include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/docs/src/main/asciidoc/building-jdk8.adoc[] diff --git a/pom.xml b/pom.xml index 312d0db..18e0b55 100644 --- a/pom.xml +++ b/pom.xml @@ -1,7 +1,7 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 org.springframework.cloud @@ -75,6 +75,7 @@ spring-cloud-circuitbreaker-commons spring-cloud-starter-circuitbreaker spring-cloud-circuitbreaker-hystrix + spring-cloud-circuitbreaker-sentinel docs spring-cloud-circuitbreaker-spring-retry diff --git a/spring-cloud-circuitbreaker-commons/pom.xml b/spring-cloud-circuitbreaker-commons/pom.xml index 18964ef..f430ecd 100644 --- a/spring-cloud-circuitbreaker-commons/pom.xml +++ b/spring-cloud-circuitbreaker-commons/pom.xml @@ -1,7 +1,7 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> spring-cloud-circuitbreaker org.springframework.cloud diff --git a/spring-cloud-circuitbreaker-commons/src/main/java/org/springframework/cloud/circuitbreaker/commons/AbstractCircuitBreakerFactory.java b/spring-cloud-circuitbreaker-commons/src/main/java/org/springframework/cloud/circuitbreaker/commons/AbstractCircuitBreakerFactory.java index c7b3296..ceb9f20 100644 --- a/spring-cloud-circuitbreaker-commons/src/main/java/org/springframework/cloud/circuitbreaker/commons/AbstractCircuitBreakerFactory.java +++ b/spring-cloud-circuitbreaker-commons/src/main/java/org/springframework/cloud/circuitbreaker/commons/AbstractCircuitBreakerFactory.java @@ -5,7 +5,7 @@ * 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 + * 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, diff --git a/spring-cloud-circuitbreaker-commons/src/main/java/org/springframework/cloud/circuitbreaker/commons/CircuitBreaker.java b/spring-cloud-circuitbreaker-commons/src/main/java/org/springframework/cloud/circuitbreaker/commons/CircuitBreaker.java index 8cbdf79..5f78fb7 100644 --- a/spring-cloud-circuitbreaker-commons/src/main/java/org/springframework/cloud/circuitbreaker/commons/CircuitBreaker.java +++ b/spring-cloud-circuitbreaker-commons/src/main/java/org/springframework/cloud/circuitbreaker/commons/CircuitBreaker.java @@ -5,7 +5,7 @@ * 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 + * 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, diff --git a/spring-cloud-circuitbreaker-commons/src/main/java/org/springframework/cloud/circuitbreaker/commons/CircuitBreakerFactory.java b/spring-cloud-circuitbreaker-commons/src/main/java/org/springframework/cloud/circuitbreaker/commons/CircuitBreakerFactory.java index 22d092e..55381fe 100644 --- a/spring-cloud-circuitbreaker-commons/src/main/java/org/springframework/cloud/circuitbreaker/commons/CircuitBreakerFactory.java +++ b/spring-cloud-circuitbreaker-commons/src/main/java/org/springframework/cloud/circuitbreaker/commons/CircuitBreakerFactory.java @@ -5,7 +5,7 @@ * 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 + * 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, diff --git a/spring-cloud-circuitbreaker-commons/src/main/java/org/springframework/cloud/circuitbreaker/commons/ConfigBuilder.java b/spring-cloud-circuitbreaker-commons/src/main/java/org/springframework/cloud/circuitbreaker/commons/ConfigBuilder.java index 8957cb8..5a134ec 100644 --- a/spring-cloud-circuitbreaker-commons/src/main/java/org/springframework/cloud/circuitbreaker/commons/ConfigBuilder.java +++ b/spring-cloud-circuitbreaker-commons/src/main/java/org/springframework/cloud/circuitbreaker/commons/ConfigBuilder.java @@ -5,7 +5,7 @@ * 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 + * 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, diff --git a/spring-cloud-circuitbreaker-commons/src/main/java/org/springframework/cloud/circuitbreaker/commons/Customizer.java b/spring-cloud-circuitbreaker-commons/src/main/java/org/springframework/cloud/circuitbreaker/commons/Customizer.java index 6aa1641..340ba7b 100644 --- a/spring-cloud-circuitbreaker-commons/src/main/java/org/springframework/cloud/circuitbreaker/commons/Customizer.java +++ b/spring-cloud-circuitbreaker-commons/src/main/java/org/springframework/cloud/circuitbreaker/commons/Customizer.java @@ -5,7 +5,7 @@ * 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 + * 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, diff --git a/spring-cloud-circuitbreaker-commons/src/main/java/org/springframework/cloud/circuitbreaker/commons/NoFallbackAvailableException.java b/spring-cloud-circuitbreaker-commons/src/main/java/org/springframework/cloud/circuitbreaker/commons/NoFallbackAvailableException.java index dee7f51..4f000f7 100644 --- a/spring-cloud-circuitbreaker-commons/src/main/java/org/springframework/cloud/circuitbreaker/commons/NoFallbackAvailableException.java +++ b/spring-cloud-circuitbreaker-commons/src/main/java/org/springframework/cloud/circuitbreaker/commons/NoFallbackAvailableException.java @@ -5,7 +5,7 @@ * 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 + * 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, diff --git a/spring-cloud-circuitbreaker-commons/src/main/java/org/springframework/cloud/circuitbreaker/commons/ReactiveCircuitBreaker.java b/spring-cloud-circuitbreaker-commons/src/main/java/org/springframework/cloud/circuitbreaker/commons/ReactiveCircuitBreaker.java index 54c0fa6..d4bd2a3 100644 --- a/spring-cloud-circuitbreaker-commons/src/main/java/org/springframework/cloud/circuitbreaker/commons/ReactiveCircuitBreaker.java +++ b/spring-cloud-circuitbreaker-commons/src/main/java/org/springframework/cloud/circuitbreaker/commons/ReactiveCircuitBreaker.java @@ -5,7 +5,7 @@ * 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 + * 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, diff --git a/spring-cloud-circuitbreaker-commons/src/main/java/org/springframework/cloud/circuitbreaker/commons/ReactiveCircuitBreakerFactory.java b/spring-cloud-circuitbreaker-commons/src/main/java/org/springframework/cloud/circuitbreaker/commons/ReactiveCircuitBreakerFactory.java index 1a2a356..cd6bef6 100644 --- a/spring-cloud-circuitbreaker-commons/src/main/java/org/springframework/cloud/circuitbreaker/commons/ReactiveCircuitBreakerFactory.java +++ b/spring-cloud-circuitbreaker-commons/src/main/java/org/springframework/cloud/circuitbreaker/commons/ReactiveCircuitBreakerFactory.java @@ -5,7 +5,7 @@ * 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 + * 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, diff --git a/spring-cloud-circuitbreaker-dependencies/pom.xml b/spring-cloud-circuitbreaker-dependencies/pom.xml index 56e760d..5c336c7 100644 --- a/spring-cloud-circuitbreaker-dependencies/pom.xml +++ b/spring-cloud-circuitbreaker-dependencies/pom.xml @@ -1,7 +1,7 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 @@ -20,6 +20,7 @@ 0.13.1 1.2.4 + 1.5.1 @@ -44,6 +45,16 @@ spring-retry ${spring-retry.version} + + com.alibaba.csp + sentinel-core + ${sentinel.version} + + + com.alibaba.csp + sentinel-reactor-adapter + ${sentinel.version} + org.springframework.cloud spring-cloud-circuitbreaker-resilience4j @@ -59,6 +70,11 @@ spring-cloud-circuitbreaker-spring-retry ${project.version} + + org.springframework.cloud + spring-cloud-circuitbreaker-sentinel + ${project.version} + org.springframework.cloud spring-cloud-circuitbreaker-commons @@ -74,6 +90,11 @@ spring-cloud-starter-circuitbreaker-hystrix ${project.version} + + org.springframework.cloud + spring-cloud-starter-circuitbreaker-sentinel + ${project.version} + diff --git a/spring-cloud-circuitbreaker-hystrix/pom.xml b/spring-cloud-circuitbreaker-hystrix/pom.xml index 8278e9f..ca80df0 100644 --- a/spring-cloud-circuitbreaker-hystrix/pom.xml +++ b/spring-cloud-circuitbreaker-hystrix/pom.xml @@ -1,7 +1,7 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> spring-cloud-circuitbreaker org.springframework.cloud diff --git a/spring-cloud-circuitbreaker-hystrix/src/main/java/org/springframework/cloud/circuitbreaker/hystrix/AbstractHystrixConfigBuilder.java b/spring-cloud-circuitbreaker-hystrix/src/main/java/org/springframework/cloud/circuitbreaker/hystrix/AbstractHystrixConfigBuilder.java index 86926f8..766c980 100644 --- a/spring-cloud-circuitbreaker-hystrix/src/main/java/org/springframework/cloud/circuitbreaker/hystrix/AbstractHystrixConfigBuilder.java +++ b/spring-cloud-circuitbreaker-hystrix/src/main/java/org/springframework/cloud/circuitbreaker/hystrix/AbstractHystrixConfigBuilder.java @@ -5,7 +5,7 @@ * 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 + * 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, diff --git a/spring-cloud-circuitbreaker-hystrix/src/main/java/org/springframework/cloud/circuitbreaker/hystrix/HystrixCircuitBreaker.java b/spring-cloud-circuitbreaker-hystrix/src/main/java/org/springframework/cloud/circuitbreaker/hystrix/HystrixCircuitBreaker.java index d779905..74b1c47 100644 --- a/spring-cloud-circuitbreaker-hystrix/src/main/java/org/springframework/cloud/circuitbreaker/hystrix/HystrixCircuitBreaker.java +++ b/spring-cloud-circuitbreaker-hystrix/src/main/java/org/springframework/cloud/circuitbreaker/hystrix/HystrixCircuitBreaker.java @@ -5,7 +5,7 @@ * 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 + * 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, diff --git a/spring-cloud-circuitbreaker-hystrix/src/main/java/org/springframework/cloud/circuitbreaker/hystrix/HystrixCircuitBreakerAutoConfiguration.java b/spring-cloud-circuitbreaker-hystrix/src/main/java/org/springframework/cloud/circuitbreaker/hystrix/HystrixCircuitBreakerAutoConfiguration.java index 1411972..9693976 100644 --- a/spring-cloud-circuitbreaker-hystrix/src/main/java/org/springframework/cloud/circuitbreaker/hystrix/HystrixCircuitBreakerAutoConfiguration.java +++ b/spring-cloud-circuitbreaker-hystrix/src/main/java/org/springframework/cloud/circuitbreaker/hystrix/HystrixCircuitBreakerAutoConfiguration.java @@ -5,7 +5,7 @@ * 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 + * 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, diff --git a/spring-cloud-circuitbreaker-hystrix/src/main/java/org/springframework/cloud/circuitbreaker/hystrix/HystrixCircuitBreakerFactory.java b/spring-cloud-circuitbreaker-hystrix/src/main/java/org/springframework/cloud/circuitbreaker/hystrix/HystrixCircuitBreakerFactory.java index 1fce486..856a45d 100644 --- a/spring-cloud-circuitbreaker-hystrix/src/main/java/org/springframework/cloud/circuitbreaker/hystrix/HystrixCircuitBreakerFactory.java +++ b/spring-cloud-circuitbreaker-hystrix/src/main/java/org/springframework/cloud/circuitbreaker/hystrix/HystrixCircuitBreakerFactory.java @@ -5,7 +5,7 @@ * 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 + * 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, diff --git a/spring-cloud-circuitbreaker-hystrix/src/main/java/org/springframework/cloud/circuitbreaker/hystrix/ReactiveHystrixCircuitBreaker.java b/spring-cloud-circuitbreaker-hystrix/src/main/java/org/springframework/cloud/circuitbreaker/hystrix/ReactiveHystrixCircuitBreaker.java index a05f235..e5107b8 100644 --- a/spring-cloud-circuitbreaker-hystrix/src/main/java/org/springframework/cloud/circuitbreaker/hystrix/ReactiveHystrixCircuitBreaker.java +++ b/spring-cloud-circuitbreaker-hystrix/src/main/java/org/springframework/cloud/circuitbreaker/hystrix/ReactiveHystrixCircuitBreaker.java @@ -5,7 +5,7 @@ * 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 + * 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, diff --git a/spring-cloud-circuitbreaker-hystrix/src/main/java/org/springframework/cloud/circuitbreaker/hystrix/ReactiveHystrixCircuitBreakerFactory.java b/spring-cloud-circuitbreaker-hystrix/src/main/java/org/springframework/cloud/circuitbreaker/hystrix/ReactiveHystrixCircuitBreakerFactory.java index 3b75ae0..5ad4b1e 100644 --- a/spring-cloud-circuitbreaker-hystrix/src/main/java/org/springframework/cloud/circuitbreaker/hystrix/ReactiveHystrixCircuitBreakerFactory.java +++ b/spring-cloud-circuitbreaker-hystrix/src/main/java/org/springframework/cloud/circuitbreaker/hystrix/ReactiveHystrixCircuitBreakerFactory.java @@ -5,7 +5,7 @@ * 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 + * 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, diff --git a/spring-cloud-circuitbreaker-hystrix/src/test/java/org/springframework/cloud/circuitbreaker/hystrix/HystrixCircuitBreakerIntegrationTest.java b/spring-cloud-circuitbreaker-hystrix/src/test/java/org/springframework/cloud/circuitbreaker/hystrix/HystrixCircuitBreakerIntegrationTest.java index 8ff19ad..bc6f960 100644 --- a/spring-cloud-circuitbreaker-hystrix/src/test/java/org/springframework/cloud/circuitbreaker/hystrix/HystrixCircuitBreakerIntegrationTest.java +++ b/spring-cloud-circuitbreaker-hystrix/src/test/java/org/springframework/cloud/circuitbreaker/hystrix/HystrixCircuitBreakerIntegrationTest.java @@ -5,7 +5,7 @@ * 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 + * 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, diff --git a/spring-cloud-circuitbreaker-hystrix/src/test/java/org/springframework/cloud/circuitbreaker/hystrix/HystrixCircuitBreakerTest.java b/spring-cloud-circuitbreaker-hystrix/src/test/java/org/springframework/cloud/circuitbreaker/hystrix/HystrixCircuitBreakerTest.java index e465ee6..19572e8 100644 --- a/spring-cloud-circuitbreaker-hystrix/src/test/java/org/springframework/cloud/circuitbreaker/hystrix/HystrixCircuitBreakerTest.java +++ b/spring-cloud-circuitbreaker-hystrix/src/test/java/org/springframework/cloud/circuitbreaker/hystrix/HystrixCircuitBreakerTest.java @@ -5,7 +5,7 @@ * 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 + * 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, diff --git a/spring-cloud-circuitbreaker-hystrix/src/test/java/org/springframework/cloud/circuitbreaker/hystrix/ReactiveHystrixCircuitBreakerIntegrationTest.java b/spring-cloud-circuitbreaker-hystrix/src/test/java/org/springframework/cloud/circuitbreaker/hystrix/ReactiveHystrixCircuitBreakerIntegrationTest.java index 4f773b5..8968675 100644 --- a/spring-cloud-circuitbreaker-hystrix/src/test/java/org/springframework/cloud/circuitbreaker/hystrix/ReactiveHystrixCircuitBreakerIntegrationTest.java +++ b/spring-cloud-circuitbreaker-hystrix/src/test/java/org/springframework/cloud/circuitbreaker/hystrix/ReactiveHystrixCircuitBreakerIntegrationTest.java @@ -5,7 +5,7 @@ * 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 + * 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, diff --git a/spring-cloud-circuitbreaker-hystrix/src/test/java/org/springframework/cloud/circuitbreaker/hystrix/ReactiveHystrixCircuitBreakerTest.java b/spring-cloud-circuitbreaker-hystrix/src/test/java/org/springframework/cloud/circuitbreaker/hystrix/ReactiveHystrixCircuitBreakerTest.java index 9a04501..888b50c 100644 --- a/spring-cloud-circuitbreaker-hystrix/src/test/java/org/springframework/cloud/circuitbreaker/hystrix/ReactiveHystrixCircuitBreakerTest.java +++ b/spring-cloud-circuitbreaker-hystrix/src/test/java/org/springframework/cloud/circuitbreaker/hystrix/ReactiveHystrixCircuitBreakerTest.java @@ -5,7 +5,7 @@ * 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 + * 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, diff --git a/spring-cloud-circuitbreaker-resilience4j/pom.xml b/spring-cloud-circuitbreaker-resilience4j/pom.xml index ec65522..d65023d 100644 --- a/spring-cloud-circuitbreaker-resilience4j/pom.xml +++ b/spring-cloud-circuitbreaker-resilience4j/pom.xml @@ -1,7 +1,7 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> spring-cloud-circuitbreaker org.springframework.cloud @@ -60,4 +60,4 @@ - \ No newline at end of file + diff --git a/spring-cloud-circuitbreaker-resilience4j/src/main/java/org/springframework/cloud/circuitbreaker/resilience4j/ReactiveResilience4JAutoConfiguration.java b/spring-cloud-circuitbreaker-resilience4j/src/main/java/org/springframework/cloud/circuitbreaker/resilience4j/ReactiveResilience4JAutoConfiguration.java index 0612fcf..28bafb7 100644 --- a/spring-cloud-circuitbreaker-resilience4j/src/main/java/org/springframework/cloud/circuitbreaker/resilience4j/ReactiveResilience4JAutoConfiguration.java +++ b/spring-cloud-circuitbreaker-resilience4j/src/main/java/org/springframework/cloud/circuitbreaker/resilience4j/ReactiveResilience4JAutoConfiguration.java @@ -5,7 +5,7 @@ * 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 + * 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, diff --git a/spring-cloud-circuitbreaker-resilience4j/src/main/java/org/springframework/cloud/circuitbreaker/resilience4j/ReactiveResilience4JCircuitBreaker.java b/spring-cloud-circuitbreaker-resilience4j/src/main/java/org/springframework/cloud/circuitbreaker/resilience4j/ReactiveResilience4JCircuitBreaker.java index adb1f24..504227e 100644 --- a/spring-cloud-circuitbreaker-resilience4j/src/main/java/org/springframework/cloud/circuitbreaker/resilience4j/ReactiveResilience4JCircuitBreaker.java +++ b/spring-cloud-circuitbreaker-resilience4j/src/main/java/org/springframework/cloud/circuitbreaker/resilience4j/ReactiveResilience4JCircuitBreaker.java @@ -5,7 +5,7 @@ * 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 + * 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, diff --git a/spring-cloud-circuitbreaker-resilience4j/src/main/java/org/springframework/cloud/circuitbreaker/resilience4j/ReactiveResilience4JCircuitBreakerFactory.java b/spring-cloud-circuitbreaker-resilience4j/src/main/java/org/springframework/cloud/circuitbreaker/resilience4j/ReactiveResilience4JCircuitBreakerFactory.java index 0cb737c..2855fc4 100644 --- a/spring-cloud-circuitbreaker-resilience4j/src/main/java/org/springframework/cloud/circuitbreaker/resilience4j/ReactiveResilience4JCircuitBreakerFactory.java +++ b/spring-cloud-circuitbreaker-resilience4j/src/main/java/org/springframework/cloud/circuitbreaker/resilience4j/ReactiveResilience4JCircuitBreakerFactory.java @@ -5,7 +5,7 @@ * 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 + * 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, diff --git a/spring-cloud-circuitbreaker-resilience4j/src/main/java/org/springframework/cloud/circuitbreaker/resilience4j/Resilience4JAutoConfiguration.java b/spring-cloud-circuitbreaker-resilience4j/src/main/java/org/springframework/cloud/circuitbreaker/resilience4j/Resilience4JAutoConfiguration.java index ed69b27..10af473 100644 --- a/spring-cloud-circuitbreaker-resilience4j/src/main/java/org/springframework/cloud/circuitbreaker/resilience4j/Resilience4JAutoConfiguration.java +++ b/spring-cloud-circuitbreaker-resilience4j/src/main/java/org/springframework/cloud/circuitbreaker/resilience4j/Resilience4JAutoConfiguration.java @@ -5,7 +5,7 @@ * 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 + * 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, diff --git a/spring-cloud-circuitbreaker-resilience4j/src/main/java/org/springframework/cloud/circuitbreaker/resilience4j/Resilience4JCircuitBreaker.java b/spring-cloud-circuitbreaker-resilience4j/src/main/java/org/springframework/cloud/circuitbreaker/resilience4j/Resilience4JCircuitBreaker.java index 8d26498..bf22b3b 100644 --- a/spring-cloud-circuitbreaker-resilience4j/src/main/java/org/springframework/cloud/circuitbreaker/resilience4j/Resilience4JCircuitBreaker.java +++ b/spring-cloud-circuitbreaker-resilience4j/src/main/java/org/springframework/cloud/circuitbreaker/resilience4j/Resilience4JCircuitBreaker.java @@ -5,7 +5,7 @@ * 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 + * 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, diff --git a/spring-cloud-circuitbreaker-resilience4j/src/main/java/org/springframework/cloud/circuitbreaker/resilience4j/Resilience4JCircuitBreakerFactory.java b/spring-cloud-circuitbreaker-resilience4j/src/main/java/org/springframework/cloud/circuitbreaker/resilience4j/Resilience4JCircuitBreakerFactory.java index 3197455..bc798a4 100644 --- a/spring-cloud-circuitbreaker-resilience4j/src/main/java/org/springframework/cloud/circuitbreaker/resilience4j/Resilience4JCircuitBreakerFactory.java +++ b/spring-cloud-circuitbreaker-resilience4j/src/main/java/org/springframework/cloud/circuitbreaker/resilience4j/Resilience4JCircuitBreakerFactory.java @@ -5,7 +5,7 @@ * 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 + * 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, @@ -45,7 +45,7 @@ public class Resilience4JCircuitBreakerFactory extends private CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry .ofDefaults(); - private ExecutorService executorService = Executors.newSingleThreadExecutor(); + private ExecutorService executorService = Executors.newCachedThreadPool(); private Map> circuitBreakerCustomizers = new HashMap<>(); diff --git a/spring-cloud-circuitbreaker-resilience4j/src/test/java/org/springframework/cloud/circuitbreaker/resilience4j/ReactiveResilience4JCircuitBreakerIntegrationTest.java b/spring-cloud-circuitbreaker-resilience4j/src/test/java/org/springframework/cloud/circuitbreaker/resilience4j/ReactiveResilience4JCircuitBreakerIntegrationTest.java index 35bdd79..8febb2b 100644 --- a/spring-cloud-circuitbreaker-resilience4j/src/test/java/org/springframework/cloud/circuitbreaker/resilience4j/ReactiveResilience4JCircuitBreakerIntegrationTest.java +++ b/spring-cloud-circuitbreaker-resilience4j/src/test/java/org/springframework/cloud/circuitbreaker/resilience4j/ReactiveResilience4JCircuitBreakerIntegrationTest.java @@ -5,7 +5,7 @@ * 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 + * 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, diff --git a/spring-cloud-circuitbreaker-resilience4j/src/test/java/org/springframework/cloud/circuitbreaker/resilience4j/ReactiveResilience4JCircuitBreakerTest.java b/spring-cloud-circuitbreaker-resilience4j/src/test/java/org/springframework/cloud/circuitbreaker/resilience4j/ReactiveResilience4JCircuitBreakerTest.java index 734e400..fd37c81 100644 --- a/spring-cloud-circuitbreaker-resilience4j/src/test/java/org/springframework/cloud/circuitbreaker/resilience4j/ReactiveResilience4JCircuitBreakerTest.java +++ b/spring-cloud-circuitbreaker-resilience4j/src/test/java/org/springframework/cloud/circuitbreaker/resilience4j/ReactiveResilience4JCircuitBreakerTest.java @@ -5,7 +5,7 @@ * 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 + * 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, diff --git a/spring-cloud-circuitbreaker-resilience4j/src/test/java/org/springframework/cloud/circuitbreaker/resilience4j/Resilience4JCircuitBreakerIntegrationTest.java b/spring-cloud-circuitbreaker-resilience4j/src/test/java/org/springframework/cloud/circuitbreaker/resilience4j/Resilience4JCircuitBreakerIntegrationTest.java index 48bbe5e..5312ec1 100644 --- a/spring-cloud-circuitbreaker-resilience4j/src/test/java/org/springframework/cloud/circuitbreaker/resilience4j/Resilience4JCircuitBreakerIntegrationTest.java +++ b/spring-cloud-circuitbreaker-resilience4j/src/test/java/org/springframework/cloud/circuitbreaker/resilience4j/Resilience4JCircuitBreakerIntegrationTest.java @@ -5,7 +5,7 @@ * 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 + * 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, @@ -35,10 +35,14 @@ import org.springframework.cloud.circuitbreaker.commons.CircuitBreakerFactory; import org.springframework.cloud.circuitbreaker.commons.Customizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; import org.springframework.stereotype.Service; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RestController; import static org.assertj.core.api.Assertions.assertThat; @@ -84,6 +88,12 @@ public class Resilience4JCircuitBreakerIntegrationTest { verify(normalSuccessConsumer, times(1)).consumeEvent(any()); } + @Test + public void testSlowResponsesDontFailSubsequentGoodRequests() { + assertThat(service.slowOnDemand(5000)).isEqualTo("fallback"); + assertThat(service.slowOnDemand(0)).isEqualTo("normal"); + } + @Configuration @EnableAutoConfiguration @RestController @@ -100,6 +110,23 @@ public class Resilience4JCircuitBreakerIntegrationTest { return "normal"; } + @GetMapping("/slowOnDemand") + public String slowOnDemand(@RequestHeader HttpHeaders headers) { + if (headers.containsKey("delayInMilliseconds")) { + String delayString = headers.getFirst("delayInMilliseconds"); + if (delayString != null) { + try { + Thread.sleep(Integer.parseInt(delayString)); + } + catch (NumberFormatException | InterruptedException e) { + e.printStackTrace(); + } + } + } + + return "normal"; + } + @Bean public Customizer slowCustomizer() { return factory -> { @@ -146,6 +173,26 @@ public class Resilience4JCircuitBreakerIntegrationTest { t -> "fallback"); } + public String slowOnDemand(int delayInMilliseconds) { + return cbFactory.create("slow") + .run(() -> rest + .exchange("/slowOnDemand", HttpMethod.GET, + createEntityWithOptionalDelayHeader( + delayInMilliseconds), + String.class) + .getBody(), t -> "fallback"); + } + + private HttpEntity createEntityWithOptionalDelayHeader( + int delayInMilliseconds) { + HttpHeaders headers = new HttpHeaders(); + if (delayInMilliseconds > 0) { + headers.set("delayInMilliseconds", + Integer.toString(delayInMilliseconds)); + } + return new HttpEntity<>(null, headers); + } + } } diff --git a/spring-cloud-circuitbreaker-resilience4j/src/test/java/org/springframework/cloud/circuitbreaker/resilience4j/Resilience4JCircuitBreakerTest.java b/spring-cloud-circuitbreaker-resilience4j/src/test/java/org/springframework/cloud/circuitbreaker/resilience4j/Resilience4JCircuitBreakerTest.java index 225ac45..090dc1b 100644 --- a/spring-cloud-circuitbreaker-resilience4j/src/test/java/org/springframework/cloud/circuitbreaker/resilience4j/Resilience4JCircuitBreakerTest.java +++ b/spring-cloud-circuitbreaker-resilience4j/src/test/java/org/springframework/cloud/circuitbreaker/resilience4j/Resilience4JCircuitBreakerTest.java @@ -5,7 +5,7 @@ * 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 + * 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, diff --git a/spring-cloud-circuitbreaker-sentinel/pom.xml b/spring-cloud-circuitbreaker-sentinel/pom.xml new file mode 100644 index 0000000..b5de5b1 --- /dev/null +++ b/spring-cloud-circuitbreaker-sentinel/pom.xml @@ -0,0 +1,54 @@ + + + + spring-cloud-circuitbreaker + org.springframework.cloud + 0.0.1.BUILD-SNAPSHOT + + 4.0.0 + + spring-cloud-circuitbreaker-sentinel + + + + org.springframework.cloud + spring-cloud-circuitbreaker-commons + + + com.alibaba.csp + sentinel-core + + + com.alibaba.csp + sentinel-reactor-adapter + + + org.springframework.boot + spring-boot-starter-web + true + + + io.projectreactor + reactor-core + true + + + + org.springframework.boot + spring-boot-starter-webflux + test + + + org.springframework.boot + spring-boot-starter-test + test + + + io.projectreactor + reactor-test + test + + + diff --git a/spring-cloud-circuitbreaker-sentinel/src/main/java/org/springframework/cloud/circuitbreaker/sentinel/ReactiveSentinelCircuitBreaker.java b/spring-cloud-circuitbreaker-sentinel/src/main/java/org/springframework/cloud/circuitbreaker/sentinel/ReactiveSentinelCircuitBreaker.java new file mode 100644 index 0000000..b87807d --- /dev/null +++ b/spring-cloud-circuitbreaker-sentinel/src/main/java/org/springframework/cloud/circuitbreaker/sentinel/ReactiveSentinelCircuitBreaker.java @@ -0,0 +1,104 @@ +/* + * Copyright 2013-2019 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 + * + * 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 org.springframework.cloud.circuitbreaker.sentinel; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Function; + +import com.alibaba.csp.sentinel.EntryType; +import com.alibaba.csp.sentinel.adapter.reactor.EntryConfig; +import com.alibaba.csp.sentinel.adapter.reactor.SentinelReactorTransformer; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import org.springframework.cloud.circuitbreaker.commons.ReactiveCircuitBreaker; +import org.springframework.util.Assert; + +/** + * Sentinel implementation of {@link ReactiveCircuitBreaker}. + * + * @author Eric Zhao + */ +public class ReactiveSentinelCircuitBreaker implements ReactiveCircuitBreaker { + + private final String resourceName; + + private final EntryType entryType; + + private final List rules; + + public ReactiveSentinelCircuitBreaker(String resourceName, EntryType entryType, + List rules) { + Assert.hasText(resourceName, "resourceName cannot be blank"); + Assert.notNull(rules, "rules should not be null"); + this.resourceName = resourceName; + this.entryType = entryType; + this.rules = Collections.unmodifiableList(rules); + + applyToSentinelRuleManager(); + } + + public ReactiveSentinelCircuitBreaker(String resourceName, List rules) { + this(resourceName, EntryType.OUT, rules); + } + + public ReactiveSentinelCircuitBreaker(String resourceName) { + this(resourceName, EntryType.OUT, Collections.emptyList()); + } + + private void applyToSentinelRuleManager() { + if (this.rules == null || this.rules.isEmpty()) { + return; + } + Set ruleSet = new HashSet<>(DegradeRuleManager.getRules()); + for (DegradeRule rule : this.rules) { + if (rule == null) { + continue; + } + rule.setResource(resourceName); + ruleSet.add(rule); + } + DegradeRuleManager.loadRules(new ArrayList<>(ruleSet)); + } + + @Override + public Mono run(Mono toRun, Function> fallback) { + Mono toReturn = toRun.transform(new SentinelReactorTransformer<>( + new EntryConfig(resourceName, entryType))); + if (fallback != null) { + toReturn = toReturn.onErrorResume(fallback); + } + return toReturn; + } + + @Override + public Flux run(Flux toRun, Function> fallback) { + Flux toReturn = toRun.transform(new SentinelReactorTransformer<>( + new EntryConfig(resourceName, entryType))); + if (fallback != null) { + toReturn = toReturn.onErrorResume(fallback); + } + return toReturn; + } + +} diff --git a/spring-cloud-circuitbreaker-sentinel/src/main/java/org/springframework/cloud/circuitbreaker/sentinel/ReactiveSentinelCircuitBreakerAutoConfiguration.java b/spring-cloud-circuitbreaker-sentinel/src/main/java/org/springframework/cloud/circuitbreaker/sentinel/ReactiveSentinelCircuitBreakerAutoConfiguration.java new file mode 100644 index 0000000..b554e06 --- /dev/null +++ b/spring-cloud-circuitbreaker-sentinel/src/main/java/org/springframework/cloud/circuitbreaker/sentinel/ReactiveSentinelCircuitBreakerAutoConfiguration.java @@ -0,0 +1,65 @@ +/* + * Copyright 2013-2019 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 + * + * 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 org.springframework.cloud.circuitbreaker.sentinel; + +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.PostConstruct; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.cloud.circuitbreaker.commons.Customizer; +import org.springframework.cloud.circuitbreaker.commons.ReactiveCircuitBreakerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Eric Zhao + */ +@Configuration +@ConditionalOnClass(name = { "reactor.core.publisher.Mono", + "reactor.core.publisher.Flux" }) +public class ReactiveSentinelCircuitBreakerAutoConfiguration { + + @Bean + @ConditionalOnMissingBean(ReactiveCircuitBreakerFactory.class) + + public ReactiveCircuitBreakerFactory reactiveSentinelCircuitBreakerFactory() { + return new ReactiveSentinelCircuitBreakerFactory(); + } + + @Configuration + @ConditionalOnClass(name = { "reactor.core.publisher.Mono", + "reactor.core.publisher.Flux" }) + public static class ReactiveSentinelCustomizerConfiguration { + + @Autowired(required = false) + private List> customizers = new ArrayList<>(); + + @Autowired(required = false) + private ReactiveSentinelCircuitBreakerFactory factory; + + @PostConstruct + public void init() { + customizers.forEach(customizer -> customizer.customize(factory)); + } + + } + +} diff --git a/spring-cloud-circuitbreaker-sentinel/src/main/java/org/springframework/cloud/circuitbreaker/sentinel/ReactiveSentinelCircuitBreakerFactory.java b/spring-cloud-circuitbreaker-sentinel/src/main/java/org/springframework/cloud/circuitbreaker/sentinel/ReactiveSentinelCircuitBreakerFactory.java new file mode 100644 index 0000000..807fe7e --- /dev/null +++ b/spring-cloud-circuitbreaker-sentinel/src/main/java/org/springframework/cloud/circuitbreaker/sentinel/ReactiveSentinelCircuitBreakerFactory.java @@ -0,0 +1,58 @@ +/* + * Copyright 2013-2019 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 + * + * 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 org.springframework.cloud.circuitbreaker.sentinel; + +import java.util.ArrayList; +import java.util.function.Function; + +import org.springframework.cloud.circuitbreaker.commons.ReactiveCircuitBreaker; +import org.springframework.cloud.circuitbreaker.commons.ReactiveCircuitBreakerFactory; +import org.springframework.cloud.circuitbreaker.sentinel.SentinelConfigBuilder.SentinelCircuitBreakerConfiguration; +import org.springframework.util.Assert; + +/** + * Factory for {@link ReactiveSentinelCircuitBreaker}. + * + * @author Eric Zhao + */ +public class ReactiveSentinelCircuitBreakerFactory extends + ReactiveCircuitBreakerFactory { + + private Function defaultConfiguration = id -> new SentinelConfigBuilder() + .resourceName(id).rules(new ArrayList<>()).build(); + + @Override + public ReactiveCircuitBreaker create(String id) { + Assert.hasText(id, "A CircuitBreaker must have an id."); + SentinelConfigBuilder.SentinelCircuitBreakerConfiguration conf = getConfigurations() + .computeIfAbsent(id, defaultConfiguration); + return new ReactiveSentinelCircuitBreaker(id, conf.getEntryType(), + conf.getRules()); + } + + @Override + protected SentinelConfigBuilder configBuilder(String id) { + return new SentinelConfigBuilder(id); + } + + @Override + public void configureDefault( + Function defaultConfiguration) { + this.defaultConfiguration = defaultConfiguration; + } + +} diff --git a/spring-cloud-circuitbreaker-sentinel/src/main/java/org/springframework/cloud/circuitbreaker/sentinel/SentinelCircuitBreaker.java b/spring-cloud-circuitbreaker-sentinel/src/main/java/org/springframework/cloud/circuitbreaker/sentinel/SentinelCircuitBreaker.java new file mode 100644 index 0000000..4239192 --- /dev/null +++ b/spring-cloud-circuitbreaker-sentinel/src/main/java/org/springframework/cloud/circuitbreaker/sentinel/SentinelCircuitBreaker.java @@ -0,0 +1,114 @@ +/* + * Copyright 2013-2019 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 + * + * 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 org.springframework.cloud.circuitbreaker.sentinel; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; + +import com.alibaba.csp.sentinel.Entry; +import com.alibaba.csp.sentinel.EntryType; +import com.alibaba.csp.sentinel.SphU; +import com.alibaba.csp.sentinel.Tracer; +import com.alibaba.csp.sentinel.slots.block.BlockException; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; + +import org.springframework.cloud.circuitbreaker.commons.CircuitBreaker; +import org.springframework.util.Assert; + +/** + * Sentinel implementation of {@link CircuitBreaker}. + * + * @author Eric Zhao + */ +public class SentinelCircuitBreaker implements CircuitBreaker { + + private final String resourceName; + + private final EntryType entryType; + + private final List rules; + + public SentinelCircuitBreaker(String resourceName, EntryType entryType, + List rules) { + Assert.hasText(resourceName, "resourceName cannot be blank"); + Assert.notNull(rules, "rules should not be null"); + this.resourceName = resourceName; + this.entryType = entryType; + this.rules = Collections.unmodifiableList(rules); + + applyToSentinelRuleManager(); + } + + public SentinelCircuitBreaker(String resourceName, List rules) { + this(resourceName, EntryType.OUT, rules); + } + + public SentinelCircuitBreaker(String resourceName) { + this(resourceName, EntryType.OUT, Collections.emptyList()); + } + + private void applyToSentinelRuleManager() { + if (this.rules == null || this.rules.isEmpty()) { + return; + } + Set ruleSet = new HashSet<>(DegradeRuleManager.getRules()); + for (DegradeRule rule : this.rules) { + if (rule == null) { + continue; + } + rule.setResource(resourceName); + ruleSet.add(rule); + } + DegradeRuleManager.loadRules(new ArrayList<>(ruleSet)); + } + + @Override + public T run(Supplier toRun, Function fallback) { + Entry entry = null; + try { + entry = SphU.entry(resourceName, entryType); + // If the SphU.entry() does not throw `BlockException`, it means that the + // request can pass. + return toRun.get(); + } + catch (BlockException ex) { + // SphU.entry() may throw BlockException which indicates that + // the request was rejected (flow control or circuit breaking triggered). + // So it should not be counted as the business exception. + return fallback.apply(ex); + } + catch (Exception ex) { + // For other kinds of exceptions, we'll trace the exception count via + // Tracer.trace(ex). + Tracer.trace(ex); + return fallback.apply(ex); + } + finally { + // Guarantee the invocation has been completed. + if (entry != null) { + entry.exit(); + } + } + } + +} diff --git a/spring-cloud-circuitbreaker-sentinel/src/main/java/org/springframework/cloud/circuitbreaker/sentinel/SentinelCircuitBreakerAutoConfiguration.java b/spring-cloud-circuitbreaker-sentinel/src/main/java/org/springframework/cloud/circuitbreaker/sentinel/SentinelCircuitBreakerAutoConfiguration.java new file mode 100644 index 0000000..db7a968 --- /dev/null +++ b/spring-cloud-circuitbreaker-sentinel/src/main/java/org/springframework/cloud/circuitbreaker/sentinel/SentinelCircuitBreakerAutoConfiguration.java @@ -0,0 +1,65 @@ +/* + * Copyright 2013-2019 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 + * + * 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 org.springframework.cloud.circuitbreaker.sentinel; + +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.PostConstruct; + +import com.alibaba.csp.sentinel.SphU; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.cloud.circuitbreaker.commons.CircuitBreakerFactory; +import org.springframework.cloud.circuitbreaker.commons.Customizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Auto configuration for {@link SentinelCircuitBreaker}. + * + * @author Eric Zhao + */ +@Configuration +@ConditionalOnClass({ SphU.class }) +public class SentinelCircuitBreakerAutoConfiguration { + + @Bean + @ConditionalOnMissingBean(CircuitBreakerFactory.class) + public CircuitBreakerFactory sentinelCircuitBreakerFactory() { + return new SentinelCircuitBreakerFactory(); + } + + @Configuration + public static class SentinelCustomizerConfiguration { + + @Autowired(required = false) + private List> customizers = new ArrayList<>(); + + @Autowired(required = false) + private SentinelCircuitBreakerFactory factory; + + @PostConstruct + public void init() { + customizers.forEach(customizer -> customizer.customize(factory)); + } + + } + +} diff --git a/spring-cloud-circuitbreaker-sentinel/src/main/java/org/springframework/cloud/circuitbreaker/sentinel/SentinelCircuitBreakerFactory.java b/spring-cloud-circuitbreaker-sentinel/src/main/java/org/springframework/cloud/circuitbreaker/sentinel/SentinelCircuitBreakerFactory.java new file mode 100644 index 0000000..7098ffe --- /dev/null +++ b/spring-cloud-circuitbreaker-sentinel/src/main/java/org/springframework/cloud/circuitbreaker/sentinel/SentinelCircuitBreakerFactory.java @@ -0,0 +1,57 @@ +/* + * Copyright 2013-2019 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 + * + * 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 org.springframework.cloud.circuitbreaker.sentinel; + +import java.util.ArrayList; +import java.util.function.Function; + +import com.alibaba.csp.sentinel.EntryType; + +import org.springframework.cloud.circuitbreaker.commons.CircuitBreaker; +import org.springframework.cloud.circuitbreaker.commons.CircuitBreakerFactory; +import org.springframework.cloud.circuitbreaker.sentinel.SentinelConfigBuilder.SentinelCircuitBreakerConfiguration; +import org.springframework.util.Assert; + +/** + * @author Eric Zhao + */ +public class SentinelCircuitBreakerFactory extends + CircuitBreakerFactory { + + private Function defaultConfiguration = id -> new SentinelConfigBuilder() + .resourceName(id).entryType(EntryType.OUT).rules(new ArrayList<>()).build(); + + @Override + public CircuitBreaker create(String id) { + Assert.hasText(id, "A CircuitBreaker must have an id."); + SentinelConfigBuilder.SentinelCircuitBreakerConfiguration conf = getConfigurations() + .computeIfAbsent(id, defaultConfiguration); + return new SentinelCircuitBreaker(id, conf.getEntryType(), conf.getRules()); + } + + @Override + protected SentinelConfigBuilder configBuilder(String id) { + return new SentinelConfigBuilder(id); + } + + @Override + public void configureDefault( + Function defaultConfiguration) { + this.defaultConfiguration = defaultConfiguration; + } + +} diff --git a/spring-cloud-circuitbreaker-sentinel/src/main/java/org/springframework/cloud/circuitbreaker/sentinel/SentinelConfigBuilder.java b/spring-cloud-circuitbreaker-sentinel/src/main/java/org/springframework/cloud/circuitbreaker/sentinel/SentinelConfigBuilder.java new file mode 100644 index 0000000..9e7398d --- /dev/null +++ b/spring-cloud-circuitbreaker-sentinel/src/main/java/org/springframework/cloud/circuitbreaker/sentinel/SentinelConfigBuilder.java @@ -0,0 +1,112 @@ +/* + * Copyright 2013-2019 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 + * + * 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 org.springframework.cloud.circuitbreaker.sentinel; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import com.alibaba.csp.sentinel.EntryType; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; + +import org.springframework.cloud.circuitbreaker.commons.ConfigBuilder; +import org.springframework.util.Assert; + +/** + * @author Eric Zhao + */ +public class SentinelConfigBuilder implements + ConfigBuilder { + + private String resourceName; + + private EntryType entryType; + + private List rules; + + public SentinelConfigBuilder() { + } + + public SentinelConfigBuilder(String resourceName) { + this.resourceName = resourceName; + } + + public SentinelConfigBuilder resourceName(String resourceName) { + this.resourceName = resourceName; + return this; + } + + public SentinelConfigBuilder entryType(EntryType entryType) { + this.entryType = entryType; + return this; + } + + public SentinelConfigBuilder rules(List rules) { + this.rules = rules; + return this; + } + + @Override + public SentinelCircuitBreakerConfiguration build() { + Assert.hasText(resourceName, "resourceName cannot be empty"); + List rules = Optional.ofNullable(this.rules) + .orElse(new ArrayList<>()); + + EntryType entryType = Optional.ofNullable(this.entryType).orElse(EntryType.OUT); + return new SentinelCircuitBreakerConfiguration() + .setResourceName(this.resourceName).setEntryType(entryType) + .setRules(rules); + } + + public static class SentinelCircuitBreakerConfiguration { + + private String resourceName; + + private EntryType entryType; + + private List rules; + + public String getResourceName() { + return resourceName; + } + + public SentinelCircuitBreakerConfiguration setResourceName(String resourceName) { + this.resourceName = resourceName; + return this; + } + + public EntryType getEntryType() { + return entryType; + } + + public SentinelCircuitBreakerConfiguration setEntryType(EntryType entryType) { + this.entryType = entryType; + return this; + } + + public List getRules() { + return rules; + } + + public SentinelCircuitBreakerConfiguration setRules(List rules) { + this.rules = rules; + return this; + } + + } + +} diff --git a/spring-cloud-circuitbreaker-sentinel/src/main/resources/META-INF/spring.factories b/spring-cloud-circuitbreaker-sentinel/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..67343b1 --- /dev/null +++ b/spring-cloud-circuitbreaker-sentinel/src/main/resources/META-INF/spring.factories @@ -0,0 +1,3 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +org.springframework.cloud.circuitbreaker.sentinel.SentinelCircuitBreakerAutoConfiguration,\ +org.springframework.cloud.circuitbreaker.sentinel.ReactiveSentinelCircuitBreakerAutoConfiguration diff --git a/spring-cloud-circuitbreaker-sentinel/src/test/java/org/springframework/cloud/circuitbreaker/sentinel/ReactiveSentinelCircuitBreakerIntegrationTest.java b/spring-cloud-circuitbreaker-sentinel/src/test/java/org/springframework/cloud/circuitbreaker/sentinel/ReactiveSentinelCircuitBreakerIntegrationTest.java new file mode 100644 index 0000000..f4be5f2 --- /dev/null +++ b/spring-cloud-circuitbreaker-sentinel/src/test/java/org/springframework/cloud/circuitbreaker/sentinel/ReactiveSentinelCircuitBreakerIntegrationTest.java @@ -0,0 +1,211 @@ +/* + * Copyright 2013-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 + * + * 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 org.springframework.cloud.circuitbreaker.sentinel; + +import java.time.Duration; +import java.util.Collections; + +import com.alibaba.csp.sentinel.slots.block.RuleConstant; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.cloud.circuitbreaker.commons.Customizer; +import org.springframework.cloud.circuitbreaker.commons.ReactiveCircuitBreakerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.stereotype.Service; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.reactive.function.client.WebClient; + +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +/** + * @author Ryan Baxter + */ +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = RANDOM_PORT, classes = ReactiveSentinelCircuitBreakerIntegrationTest.Application.class) +@DirtiesContext +public class ReactiveSentinelCircuitBreakerIntegrationTest { + + @LocalServerPort + private int port = 0; + + @Autowired + private ReactiveSentinelCircuitBreakerIntegrationTest.Application.DemoControllerService service; + + @Before + public void setup() { + service.setPort(port); + } + + @Test + public void test() throws Exception { + StepVerifier.create(service.normal()).expectNext("normal").verifyComplete(); + StepVerifier.create(service.slow()).expectNext("slow").verifyComplete(); + StepVerifier.create(service.slow()).expectNext("slow").verifyComplete(); + StepVerifier.create(service.slow()).expectNext("slow").verifyComplete(); + StepVerifier.create(service.slow()).expectNext("slow").verifyComplete(); + StepVerifier.create(service.slow()).expectNext("slow").verifyComplete(); + + // Then in the next 5s, the fallback method should be called. + for (int i = 0; i < 5; i++) { + StepVerifier.create(service.slow()).expectNext("fallback").verifyComplete(); + Thread.sleep(1000); + } + + // Recovered. + StepVerifier.create(service.slow()).expectNext("slow").verifyComplete(); + + StepVerifier.create(service.normalFlux()).expectNext("normalflux") + .verifyComplete(); + StepVerifier.create(service.slowFlux()).expectNext("slowflux").verifyComplete(); + StepVerifier.create(service.slowFlux()).expectNext("slowflux").verifyComplete(); + StepVerifier.create(service.slowFlux()).expectNext("slowflux").verifyComplete(); + StepVerifier.create(service.slowFlux()).expectNext("slowflux").verifyComplete(); + StepVerifier.create(service.slowFlux()).expectNext("slowflux").verifyComplete(); + // Then in the next 5s, the fallback method should be called. + for (int i = 0; i < 5; i++) { + StepVerifier.create(service.slowFlux()).expectNext("flux_fallback") + .verifyComplete(); + Thread.sleep(1000); + } + + // Recovered. + StepVerifier.create(service.slowFlux()).expectNext("slowflux").verifyComplete(); + } + + @Configuration + @EnableAutoConfiguration + @RestController + protected static class Application { + + @GetMapping("/slow") + public Mono slow() { + return Mono.just("slow").delayElement(Duration.ofMillis(500)); + } + + @GetMapping("/normal") + public Mono normal() { + return Mono.just("normal"); + } + + @GetMapping("/slow_flux") + public Flux slowFlux() { + return Flux.just("slow", "flux").delayElements(Duration.ofMillis(500)); + } + + @GetMapping("normal_flux") + public Flux normalFlux() { + return Flux.just("normal", "flux"); + } + + @Bean + public Customizer slowCustomizer() { + return factory -> { + factory.configure(builder -> builder + .rules(Collections.singletonList(new DegradeRule("slow_mono") + .setGrade(RuleConstant.DEGRADE_GRADE_RT).setCount(100) + .setTimeWindow(5))), + "slow_mono"); + factory.configure(builder -> builder + .rules(Collections.singletonList(new DegradeRule("slow_flux") + .setGrade(RuleConstant.DEGRADE_GRADE_RT).setCount(100) + .setTimeWindow(5))), + "slow_flux"); + factory.configureDefault(id -> new SentinelConfigBuilder() + .resourceName(id) + .rules(Collections.singletonList(new DegradeRule(id) + .setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT) + .setCount(0.5).setTimeWindow(10))) + .build()); + }; + } + + @Service + public static class DemoControllerService { + + private int port = 0; + + private ReactiveCircuitBreakerFactory cbFactory; + + DemoControllerService(ReactiveCircuitBreakerFactory cbFactory) { + this.cbFactory = cbFactory; + } + + public Mono slow() { + return cbFactory.create("slow_mono").run( + WebClient.builder().baseUrl("http://localhost:" + port).build() + .get().uri("/slow").retrieve().bodyToMono(String.class), + t -> { + t.printStackTrace(); + return Mono.just("fallback"); + }); + } + + public Mono normal() { + return cbFactory.create("normal_mono").run( + WebClient.builder().baseUrl("http://localhost:" + port).build() + .get().uri("/normal").retrieve().bodyToMono(String.class), + t -> { + t.printStackTrace(); + return Mono.just("fallback"); + }); + } + + public Flux slowFlux() { + return cbFactory.create("slow_flux") + .run(WebClient.builder().baseUrl("http://localhost:" + port) + .build().get().uri("/slow_flux").retrieve() + .bodyToFlux(new ParameterizedTypeReference() { + }), t -> { + t.printStackTrace(); + return Flux.just("flux_fallback"); + }); + } + + public Flux normalFlux() { + return cbFactory.create("normal_flux") + .run(WebClient.builder().baseUrl("http://localhost:" + port) + .build().get().uri("/normal_flux").retrieve() + .bodyToFlux(String.class), t -> { + t.printStackTrace(); + return Flux.just("flux_fallback"); + }); + } + + public void setPort(int port) { + this.port = port; + } + + } + + } + +} diff --git a/spring-cloud-circuitbreaker-sentinel/src/test/java/org/springframework/cloud/circuitbreaker/sentinel/ReactiveSentinelCircuitBreakerTest.java b/spring-cloud-circuitbreaker-sentinel/src/test/java/org/springframework/cloud/circuitbreaker/sentinel/ReactiveSentinelCircuitBreakerTest.java new file mode 100644 index 0000000..8c4a323 --- /dev/null +++ b/spring-cloud-circuitbreaker-sentinel/src/test/java/org/springframework/cloud/circuitbreaker/sentinel/ReactiveSentinelCircuitBreakerTest.java @@ -0,0 +1,78 @@ +/* + * Copyright 2013-2019 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 + * + * 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 org.springframework.cloud.circuitbreaker.sentinel; + +import java.util.Arrays; +import java.util.Collections; + +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; +import org.junit.Test; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import org.springframework.cloud.circuitbreaker.commons.ReactiveCircuitBreaker; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Eric Zhao + */ +public class ReactiveSentinelCircuitBreakerTest { + + @Test + public void testCreateWithNullRule() { + String id = "testCreateReactiveCbWithNullRule"; + ReactiveSentinelCircuitBreaker cb = new ReactiveSentinelCircuitBreaker(id, + Collections.singletonList(null)); + assertThat(cb.run(Mono.just("foobar")).block()).isEqualTo("foobar"); + assertThat(DegradeRuleManager.hasConfig(id)).isFalse(); + } + + @Test + public void runMono() { + ReactiveCircuitBreaker cb = new ReactiveSentinelCircuitBreakerFactory() + .create("foo"); + assertThat(cb.run(Mono.just("foobar")).block()).isEqualTo("foobar"); + } + + @Test + public void runMonoWithFallback() { + ReactiveCircuitBreaker cb = new ReactiveSentinelCircuitBreakerFactory() + .create("foo"); + assertThat(cb + .run(Mono.error(new RuntimeException("boom")), t -> Mono.just("fallback")) + .block()).isEqualTo("fallback"); + } + + @Test + public void runFlux() { + ReactiveCircuitBreaker cb = new ReactiveSentinelCircuitBreakerFactory() + .create("foo"); + assertThat(cb.run(Flux.just("foobar", "hello world")).collectList().block()) + .isEqualTo(Arrays.asList("foobar", "hello world")); + } + + @Test + public void runFluxWithFallback() { + ReactiveCircuitBreaker cb = new ReactiveSentinelCircuitBreakerFactory() + .create("foo"); + assertThat(cb + .run(Flux.error(new RuntimeException("boom")), t -> Flux.just("fallback")) + .collectList().block()).isEqualTo(Arrays.asList("fallback")); + } + +} diff --git a/spring-cloud-circuitbreaker-sentinel/src/test/java/org/springframework/cloud/circuitbreaker/sentinel/SentinelCircuitBreakerIntegrationTest.java b/spring-cloud-circuitbreaker-sentinel/src/test/java/org/springframework/cloud/circuitbreaker/sentinel/SentinelCircuitBreakerIntegrationTest.java new file mode 100644 index 0000000..6c40fba --- /dev/null +++ b/spring-cloud-circuitbreaker-sentinel/src/test/java/org/springframework/cloud/circuitbreaker/sentinel/SentinelCircuitBreakerIntegrationTest.java @@ -0,0 +1,153 @@ +/* + * Copyright 2013-2019 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 + * + * 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 org.springframework.cloud.circuitbreaker.sentinel; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.alibaba.csp.sentinel.slots.block.RuleConstant; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.cloud.circuitbreaker.commons.CircuitBreakerFactory; +import org.springframework.cloud.circuitbreaker.commons.Customizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Service; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +/** + * @author Eric Zhao + */ +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = RANDOM_PORT, classes = SentinelCircuitBreakerIntegrationTest.Application.class) +@DirtiesContext +public class SentinelCircuitBreakerIntegrationTest { + + @Autowired + private Application.DemoControllerService service; + + @Test + public void testSlow() throws Exception { + // The first 5 requests should pass. + assertThat(service.slow()).isEqualTo("slow"); + assertThat(service.slow()).isEqualTo("slow"); + assertThat(service.slow()).isEqualTo("slow"); + assertThat(service.slow()).isEqualTo("slow"); + assertThat(service.slow()).isEqualTo("slow"); + + // Then in the next 10s, the fallback method should be called. + for (int i = 0; i < 10; i++) { + assertThat(service.slow()).isEqualTo("fallback"); + Thread.sleep(1000); + } + + // Recovered. + assertThat(service.slow()).isEqualTo("slow"); + } + + @Test + public void testNormal() { + assertThat(service.normal()).isEqualTo("normal"); + } + + @Before + public void setUp() { + DegradeRuleManager.loadRules(new ArrayList<>()); + } + + @Before + public void tearDown() { + DegradeRuleManager.loadRules(new ArrayList<>()); + } + + @Configuration + @EnableAutoConfiguration + @RestController + protected static class Application { + + @GetMapping("/slow") + public String slow() throws InterruptedException { + Thread.sleep(500); + return "slow"; + } + + @GetMapping("/normal") + public String normal() { + return "normal"; + } + + @Bean + public Customizer slowCustomizer() { + String slowId = "slow"; + List rules = Collections.singletonList( + new DegradeRule(slowId).setGrade(RuleConstant.DEGRADE_GRADE_RT) + .setCount(100).setTimeWindow(10)); + return factory -> { + factory.configure(builder -> builder.rules(rules), slowId); + factory.configureDefault(id -> new SentinelConfigBuilder() + .resourceName(id) + .rules(Collections.singletonList(new DegradeRule(id) + .setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT) + .setCount(0.5).setTimeWindow(10))) + .build()); + }; + } + + @Service + public static class DemoControllerService { + + private TestRestTemplate rest; + + private CircuitBreakerFactory cbFactory; + + DemoControllerService(TestRestTemplate rest, + CircuitBreakerFactory cbFactory) { + this.rest = rest; + this.cbFactory = cbFactory; + } + + public String slow() { + return cbFactory.create("slow").run( + () -> rest.getForObject("/slow", String.class), t -> "fallback"); + } + + public String normal() { + return cbFactory.create("normal").run( + () -> rest.getForObject("/normal", String.class), + t -> "fallback"); + } + + } + + } + +} diff --git a/spring-cloud-circuitbreaker-sentinel/src/test/java/org/springframework/cloud/circuitbreaker/sentinel/SentinelCircuitBreakerTest.java b/spring-cloud-circuitbreaker-sentinel/src/test/java/org/springframework/cloud/circuitbreaker/sentinel/SentinelCircuitBreakerTest.java new file mode 100644 index 0000000..645ee6c --- /dev/null +++ b/spring-cloud-circuitbreaker-sentinel/src/test/java/org/springframework/cloud/circuitbreaker/sentinel/SentinelCircuitBreakerTest.java @@ -0,0 +1,85 @@ +/* + * Copyright 2013-2019 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 + * + * 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 org.springframework.cloud.circuitbreaker.sentinel; + +import java.util.ArrayList; +import java.util.Collections; + +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; +import org.junit.After; +import org.junit.Test; + +import org.springframework.cloud.circuitbreaker.commons.CircuitBreaker; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Eric Zhao + */ +public class SentinelCircuitBreakerTest { + + @After + public void tearDown() { + // Clear the rules. + DegradeRuleManager.loadRules(new ArrayList<>()); + } + + @Test + public void testCreateDirectlyThenRun() { + // Create a circuit breaker without any circuit breaking rules. + CircuitBreaker cb = new SentinelCircuitBreaker( + "testSentinelCreateDirectlyThenRunA"); + assertThat(cb.run(() -> "Sentinel")).isEqualTo("Sentinel"); + assertThat(DegradeRuleManager.hasConfig("testSentinelCreateDirectlyThenRunA")) + .isFalse(); + + CircuitBreaker cb2 = new SentinelCircuitBreaker( + "testSentinelCreateDirectlyThenRunB", + Collections.singletonList( + new DegradeRule("testSentinelCreateDirectlyThenRunB") + .setCount(100).setTimeWindow(10))); + assertThat(cb2.run(() -> "Sentinel")).isEqualTo("Sentinel"); + assertThat(DegradeRuleManager.hasConfig("testSentinelCreateDirectlyThenRunB")) + .isTrue(); + } + + @Test + public void testCreateWithNullRule() { + String id = "testCreateCbWithNullRule"; + CircuitBreaker cb = new SentinelCircuitBreaker(id, + Collections.singletonList(null)); + assertThat(cb.run(() -> "Sentinel")).isEqualTo("Sentinel"); + assertThat(DegradeRuleManager.hasConfig(id)).isFalse(); + } + + @Test + public void testCreateFromFactoryThenRun() { + CircuitBreaker cb = new SentinelCircuitBreakerFactory().create("testSentinelRun"); + assertThat(cb.run(() -> "foobar")).isEqualTo("foobar"); + } + + @Test + public void testRunWithFallback() { + CircuitBreaker cb = new SentinelCircuitBreakerFactory() + .create("testSentinelRunWithFallback"); + assertThat(cb.run(() -> { + throw new RuntimeException("boom"); + }, t -> "fallback")).isEqualTo("fallback"); + } + +} diff --git a/spring-cloud-starter-circuitbreaker/pom.xml b/spring-cloud-starter-circuitbreaker/pom.xml index 1a7d7e5..f398e43 100644 --- a/spring-cloud-starter-circuitbreaker/pom.xml +++ b/spring-cloud-starter-circuitbreaker/pom.xml @@ -1,7 +1,7 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> spring-cloud-circuitbreaker org.springframework.cloud @@ -15,6 +15,7 @@ spring-cloud-starter-circuitbreaker-resilience4j spring-cloud-starter-circuitbreaker-hystrix + spring-cloud-starter-circuitbreaker-sentinel pom diff --git a/spring-cloud-starter-circuitbreaker/spring-cloud-starter-circuitbreaker-hystrix/pom.xml b/spring-cloud-starter-circuitbreaker/spring-cloud-starter-circuitbreaker-hystrix/pom.xml index d61ff1a..ad4b5c9 100644 --- a/spring-cloud-starter-circuitbreaker/spring-cloud-starter-circuitbreaker-hystrix/pom.xml +++ b/spring-cloud-starter-circuitbreaker/spring-cloud-starter-circuitbreaker-hystrix/pom.xml @@ -1,7 +1,7 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> spring-cloud-starter-circuitbreaker org.springframework.cloud diff --git a/spring-cloud-starter-circuitbreaker/spring-cloud-starter-circuitbreaker-resilience4j/pom.xml b/spring-cloud-starter-circuitbreaker/spring-cloud-starter-circuitbreaker-resilience4j/pom.xml index 330b588..8ca0b6a 100644 --- a/spring-cloud-starter-circuitbreaker/spring-cloud-starter-circuitbreaker-resilience4j/pom.xml +++ b/spring-cloud-starter-circuitbreaker/spring-cloud-starter-circuitbreaker-resilience4j/pom.xml @@ -1,7 +1,7 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> spring-cloud-starter-circuitbreaker org.springframework.cloud diff --git a/spring-cloud-starter-circuitbreaker/spring-cloud-starter-circuitbreaker-sentinel/pom.xml b/spring-cloud-starter-circuitbreaker/spring-cloud-starter-circuitbreaker-sentinel/pom.xml new file mode 100644 index 0000000..3c00eba --- /dev/null +++ b/spring-cloud-starter-circuitbreaker/spring-cloud-starter-circuitbreaker-sentinel/pom.xml @@ -0,0 +1,24 @@ + + + + spring-cloud-starter-circuitbreaker + org.springframework.cloud + 0.0.1.BUILD-SNAPSHOT + + 4.0.0 + + spring-cloud-starter-circuitbreaker-sentinel + + + + org.springframework.cloud + spring-cloud-starter + + + org.springframework.cloud + spring-cloud-circuitbreaker-sentinel + + + \ No newline at end of file diff --git a/spring-cloud-starter-circuitbreaker/spring-cloud-starter-circuitbreaker-sentinel/src/main/resources/META-INF/spring.provides b/spring-cloud-starter-circuitbreaker/spring-cloud-starter-circuitbreaker-sentinel/src/main/resources/META-INF/spring.provides new file mode 100644 index 0000000..d9eda04 --- /dev/null +++ b/spring-cloud-starter-circuitbreaker/spring-cloud-starter-circuitbreaker-sentinel/src/main/resources/META-INF/spring.provides @@ -0,0 +1 @@ +provides: spring-cloud-circuitbreaker-sentinel \ No newline at end of file