diff --git a/.circleci/config.yml b/.circleci/config.yml index 44439fd8..8fbf11a2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -24,7 +24,7 @@ jobs: - ~/.m2 - run: name: "Running build" - command: ./mvnw -Pspring clean install -U -nsu --batch-mode -Dmaven.test.redirectTestOutputToFile=true -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn + command: ./mvnw -Pspring -Pdocs clean install -U -nsu --batch-mode -Dmaven.test.redirectTestOutputToFile=true -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn - run: name: "Aggregate test results" when: always diff --git a/README-zh.md b/README-zh.md index 747faab4..52ef0214 100644 --- a/README-zh.md +++ b/README-zh.md @@ -8,28 +8,33 @@ Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。 ## 主要功能 -* **服务限流降级**:默认支持 Servlet、RestTemplate、Dubbo 和 RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控。 +* **服务限流降级**:默认支持 Servlet、Feign、RestTemplate、Dubbo 和 RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控。 * **服务注册与发现**:适配 Spring Cloud 服务注册与发现标准,默认集成了 Ribbon 的支持。 * **分布式配置管理**:支持分布式系统中的外部化配置,配置更改时自动刷新。 +* **消息驱动能力**:基于 Spring Cloud Stream 为微服务应用构建消息驱动能力。 * **阿里云对象存储**:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。 更多功能请参考 [Roadmap](https://github.com/spring-cloud-incubator/spring-cloud-alibaba/blob/master/Roadmap-zh.md)。 -## 组件: +## 组件 **[Sentinel](https://github.com/alibaba/Sentinel)**:把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。 **[Nacos](https://github.com/alibaba/Nacos)**:一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。 +**[RocketMQ](https://rocketmq.apache.org/)**:一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务。 + +**[AliCloud ACM](https://www.aliyun.com/product/acm)**:一款在分布式架构环境中对应用配置进行集中管理和推送的应用配置中心产品。 + **[AliCloud OSS](https://www.aliyun.com/product/oss)**: 阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。 更多组件请参考 [Roadmap](https://github.com/spring-cloud-incubator/spring-cloud-alibaba/blob/master/Roadmap-zh.md)。 ## 如何构建 -* master 分支对应的是 Spring Boot 2.x,最低支持 JDK 1.8。 -* 1.x 分支对应的是 Spring Boot 1.x,最低支持 JDK 1.7。 +* master 分支对应的是 Spring Cloud Finchley,最低支持 JDK 1.8。 +* 1.x 分支对应的是 Spring Cloud Edgware,最低支持 JDK 1.7。 Spring Cloud 使用 Maven 来构建,最快的使用方式是将本项目clone到本地,然后执行以下命令: @@ -83,6 +88,8 @@ Example 列表: [Nacos Discovery Example](https://github.com/spring-cloud-incubator/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/readme-zh.md) +[RocketMQ Example](https://github.com/spring-cloud-incubator/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-examples/rocketmq-example/readme-zh.md) + [AliCloud OSS Example](https://github.com/spring-cloud-incubator/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-examples/oss-example/readme-zh.md) [AliCloud ANS Example](https://github.com/spring-cloud-incubator/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-examples/ans-example/ans-provider-example/readme-zh.md) diff --git a/README.md b/README.md index e81d27e8..8317707d 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ See the [中文文档](https://github.com/spring-cloud-incubator/spring-cloud-al Spring Cloud Alibaba provides a one-stop solution for distributed application development. It contains all the components required to develop distributed applications, making it easy for you to develop your applications using Spring Cloud. -With Spring Cloud Alibaba,you only need to add some annotations and a small amount of configurations to connect Spring Cloud applications to the distributed solutions of Alibaba, and build a distributed application system with Alibaba middleware. +With Spring Cloud Alibaba, you only need to add some annotations and a small amount of configurations to connect Spring Cloud applications to the distributed solutions of Alibaba, and build a distributed application system with Alibaba middleware. ## Features @@ -14,6 +14,7 @@ With Spring Cloud Alibaba,you only need to add some annotations and a small am * **Flow control and service degradation**:Flow control for HTTP services is supported by default. You can also customize flow control and service degradation rules using annotations. The rules can be changed dynamically. * **Service registration and discovery**:Service can be registered and clients can discover the instances using Spring-managed beans, auto integration Ribbon. * **Distributed configuration**:support for externalized configuration in a distributed system, auto refresh when configuration changes. +* **Event-driven**:support for building highly scalable event-driven microservices connected with shared messaging systems. * **AliCloud Object Storage**:massive, secure, low-cost, and highly reliable cloud storage services. Support for storing and accessing any type of data in any application, anytime, anywhere. For more features, please refer to [Roadmap](https://github.com/spring-cloud-incubator/spring-cloud-alibaba/blob/master/Roadmap.md). @@ -23,6 +24,10 @@ For more features, please refer to [Roadmap](https://github.com/spring-cloud-inc **[Nacos](https://github.com/alibaba/Nacos)**: an easy-to-use dynamic service discovery, configuration and service management platform for building cloud native applications. +**[RocketMQ](https://rocketmq.apache.org/)**:a distributed messaging and streaming platform with low latency, high performance and reliability, trillion-level capacity and flexible scalability. + +**[AliCloud ACM](https://www.aliyun.com/product/acm)**:an application configuration center that enables you to centralize the management of application configurations, and accomplish real-time configuration push in a distributed environment. + **[AliCloud OSS](https://www.aliyun.com/product/oss)**: An encrypted and secure cloud storage service which stores, processes and accesses massive amounts of data from anywhere in the world. For more features please refer to [Roadmap](https://github.com/spring-cloud-incubator/spring-cloud-alibaba/blob/master/Roadmap.md). @@ -30,7 +35,7 @@ For more features please refer to [Roadmap](https://github.com/spring-cloud-incu ## How to build * **master branch**: Corresponds to Spring Boot 2.x. JDK 1.8 or later versions are supported. -* **1.x branch**: Corresponds to Spring Boot 1.x,JDK 1.7 or later versions are supported. +* **1.x branch**: Corresponds to Spring Boot 1.x, JDK 1.7 or later versions are supported. Spring Cloud uses Maven for most build-related activities, and you should be able to get off the ground quite quickly by cloning the project you are interested in and typing: @@ -40,7 +45,7 @@ Spring Cloud uses Maven for most build-related activities, and you should be abl ## How to Use ### Add maven dependency -Version 0.2.0.RELEASE is compatible with the Spring Boot 2.0.x line. Version 0.1.0.RELEASE is compatible with the Spring Boot 1.x line. +Version 0.2.0.RELEASE is compatible with the Spring Cloud Finchley. Version 0.1.0.RELEASE is compatible with the Spring Cloud Edgware. These artifacts are available from Maven Central and Spring Release repository via BOM: @@ -95,6 +100,8 @@ Examples: [Nacos Discovery Example](https://github.com/spring-cloud-incubator/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/readme.md) +[RocketMQ Example](https://github.com/spring-cloud-incubator/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-examples/rocketmq-example/readme.md) + [AliCloud OSS Example](https://github.com/spring-cloud-incubator/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-examples/oss-example/readme.md) ## Version control guidelines @@ -104,8 +111,8 @@ As the interfaces and annotations of Spring Boot 1 and Spring Boot 2 have been c * 0.1.x for Spring Boot 1 * 0.2.x for Spring Boot 2 -During the incubation period,the version management of the project will follow these rules: -* Functional updates will be reflected in the 3rd number of the version, for example, the next version of 0.1.0 will be 0.1.1。 +During the incubation period, the version management of the project will follow these rules: +* Functional updates will be reflected in the 3rd number of the version, for example, the next version of 0.1.0 will be 0.1.1. ## Contact Us diff --git a/spring-cloud-alibaba-dependencies/pom.xml b/spring-cloud-alibaba-dependencies/pom.xml index 4d2b1ce2..b966c312 100644 --- a/spring-cloud-alibaba-dependencies/pom.xml +++ b/spring-cloud-alibaba-dependencies/pom.xml @@ -19,7 +19,7 @@ 1.3.0-GA 3.1.0 - 0.5.0 + 0.6.0 1.0.8 0.1.1 4.0.1 @@ -64,6 +64,7 @@ ${acm.version} + com.alibaba.nacos nacos-client @@ -89,6 +90,11 @@ sentinel-core ${sentinel.version} + + com.alibaba.csp + sentinel-parameter-flow-control + ${sentinel.version} + com.alibaba.csp sentinel-datasource-extension @@ -170,6 +176,11 @@ spring-cloud-alibaba-nacos-config ${project.version} + + org.springframework.cloud + spring-cloud-alibaba-nacos-config-server + ${project.version} + org.springframework.cloud spring-cloud-alicloud-acm @@ -214,6 +225,11 @@ spring-cloud-starter-alibaba-nacos-config ${project.version} + + org.springframework.cloud + spring-cloud-starter-alibaba-nacos-config-server + ${project.version} + org.springframework.cloud diff --git a/spring-cloud-alibaba-docs/src/main/asciidoc-zh/acm.adoc b/spring-cloud-alibaba-docs/src/main/asciidoc-zh/acm.adoc index 4824e321..3473575d 100644 --- a/spring-cloud-alibaba-docs/src/main/asciidoc-zh/acm.adoc +++ b/spring-cloud-alibaba-docs/src/main/asciidoc-zh/acm.adoc @@ -161,7 +161,7 @@ spring.application.name=acm-config spring.cloud.nacos.config.file-extension=yaml #显示的声明使用的文件扩展名 ---- -其中 ${deploy.env}变量的值可以在启动应用时通过-Ddeploy.env=*****来动态指定。比如现在在轻量版配置中心上新增了一个dataid为:acm-config-develop.yaml的基础配置,如下所示: +其中 ${deploy.env}变量的值可以在启动应用时通过-Ddeploy.env=来动态指定。比如现在在轻量版配置中心上新增了一个dataid为:acm-config-develop.yaml的基础配置,如下所示: [source,subs="normal"] ---- diff --git a/spring-cloud-alibaba-docs/src/main/asciidoc-zh/nacos-config.adoc b/spring-cloud-alibaba-docs/src/main/asciidoc-zh/nacos-config.adoc index 77fc9a06..1cd2e71a 100644 --- a/spring-cloud-alibaba-docs/src/main/asciidoc-zh/nacos-config.adoc +++ b/spring-cloud-alibaba-docs/src/main/asciidoc-zh/nacos-config.adoc @@ -242,7 +242,7 @@ public class ProviderApplication { [source,subs="normal"] ---- -in develop-evn enviroment; user name :nacos-config-yaml-update; age: 68 +in develop-env enviroment; user name :nacos-config-yaml-update; age: 68 2018-11-02 15:34:25.013 INFO 33014 --- [ Thread-11] ConfigServletWebServerApplicationContext : Closing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@6f1c29b7: startup date [Fri Nov 02 15:33:57 CST 2018]; parent: org.springframework.context.annotation.AnnotationConfigApplicationContext@63355449 ---- @@ -275,12 +275,15 @@ in product-env enviroment; user name :nacos-config-yaml-update; age: 68 ---- -NOTE: 此案例中我们通过 `spring.profiles.active=****` 的方式写死在配置文件中,而在真正的项目实施过程中这个变量的值是需要不同环境而有不同的值。这个时候通常的做法是通过 `-Dspring.profiles.active=****` 参数指定其配置来达到环境间灵活的切换。 +NOTE: 此案例中我们通过 `spring.profiles.active=` 的方式写死在配置文件中,而在真正的项目实施过程中这个变量的值是需要不同环境而有不同的值。这个时候通常的做法是通过 `-Dspring.profiles.active=` 参数指定其配置来达到环境间灵活的切换。 === 支持自定义 namespace 的配置 +首先看一下 Nacos 的 Namespace 的概念, https://nacos.io/zh-cn/docs/concepts.html[Nacos 概念] -在没有明确指定 `${spring.cloud.nacos.config.namespace}` 配置的情况下, 默认使用的是 Nacos 上 Public 这个namespae。在我们日常业务的开发过程中,经常需要自定义自己的 namespace,那这个时候可以通过以下配置来实现: +[quote] +用于进行租户粒度的配置隔离。不同的命名空间下,可以存在相同的 Group 或 Data ID 的配置。Namespace 的常用场景之一是不同环境的配置的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等。 +在没有明确指定 `${spring.cloud.nacos.config.namespace}` 配置的情况下, 默认使用的是 Nacos 上 Public 这个namespae。如果需要使用自定义的命名空间,可以通过以下配置来实现: [source,properties] ---- spring.cloud.nacos.config.namespace=b3404bc0-d7dc-4855-b519-570ed34b62d7 @@ -290,7 +293,7 @@ NOTE: 该配置必须放在 bootstrap.properties 文件中。此外 `spring.clou === 支持自定义 Group 的配置 -在没有明确指定 `${spring.cloud.nacos.config.group}` 配置的情况下, 默认使用的是 DEFAULT_GROUP 。在我们日常业务的开发过程中,经常需要自定义自己的 Group,那这个时候可以通过以下配置来实现: +在没有明确指定 `${spring.cloud.nacos.config.group}` 配置的情况下, 默认使用的是 DEFAULT_GROUP 。如果需要自定义自己的 Group,可以通过以下配置来实现: [source,properties] ---- diff --git a/spring-cloud-alibaba-docs/src/main/asciidoc-zh/sentinel.adoc b/spring-cloud-alibaba-docs/src/main/asciidoc-zh/sentinel.adoc index e8c575ac..ed1b0d83 100644 --- a/spring-cloud-alibaba-docs/src/main/asciidoc-zh/sentinel.adoc +++ b/spring-cloud-alibaba-docs/src/main/asciidoc-zh/sentinel.adoc @@ -100,7 +100,53 @@ spring: ### Feign 支持 -DOING +Sentinel 适配了 https://github.com/OpenFeign/feign[Feign] 组件。如果想使用,除了引入 `sentinel-starter` 的依赖外还需要 3 个步骤: + +* 配置文件打开 sentinel 对 feign 的支持:`feign.sentinel.enabled=true` +* 加入 `feign starter` 依赖触发 `sentinel starter` 的配置类生效: +```xml + + org.springframework.cloud + spring-cloud-starter-openfeign + +``` +* `feign` 内部的 `loadbalance` 功能依赖 Netflix 的 `ribbon` 模块(如果不使用 `loadbalance` 功能可不引入): +```xml + + org.springframework.cloud + spring-cloud-starter-netflix-ribbon + +``` + +这是一个 `FeignClient` 对应的接口: + +```java +@FeignClient(name = "service-provider", fallback = EchoServiceFallback.class, configuration = FeignConfiguration.class) +public interface EchoService { + @RequestMapping(value = "/echo/{str}", method = RequestMethod.GET) + String echo(@PathVariable("str") String str); +} + +class FeignConfiguration { + @Bean + public EchoServiceFallback echoServiceFallback() { + return new EchoServiceFallback(); + } +} + +class EchoServiceFallback implements EchoService { + @Override + public String echo(@PathVariable("str") String str) { + return "echo fallback"; + } +} +``` + +NOTE: Feign 对应的接口中的资源名策略定义:httpmethod:http://requesturl + +`EchoService` 接口中方法 `echo` 对应的资源名为 `GET:http://service-provider/echo/{str}`。 + +请注意:`@FeignClient` 注解中的所有属性,Sentinel 都做了兼容。 ### RestTemplate 支持 @@ -124,10 +170,11 @@ public RestTemplate restTemplate() { NOTE: 以 `https://www.taobao.com/test` 这个 url 为例。对应的资源名有两种粒度,分别是 `https://www.taobao.com:80` 以及 `https://www.taobao.com:80/test` - ### 动态数据源支持 -*在版本 0.2.0.RELEASE 或 0.1.0.RELEASE 之前*,要在 Spring Cloud Alibaba Sentinel 下使用动态数据源,需要3个步骤: +#### 在版本 0.2.0.RELEASE 或 0.1.0.RELEASE 之前 + +需要3个步骤才可完成数据源的配置: * 配置文件中定义数据源信息。比如使用文件: @@ -173,7 +220,9 @@ private ReadableDataSource dataSource; [Sentinel Starter] load 3 flow rules ``` -*在版本 0.2.0.RELEASE 或 0.1.0.RELEASE 之后*,要在 Spring Cloud Alibaba Sentinel 下使用动态数据源,只需要1个步骤: +#### 在版本 0.2.0.RELEASE 或 0.1.0.RELEASE 之后 + +只需要1个步骤就可完成数据源的配置: * 直接在 `application.properties` 配置文件中配置数据源信息即可 @@ -236,6 +285,16 @@ NOTE: 默认情况下,xml 格式是不支持的。需要添加 `jackson-datafo ### More +下表显示当应用的 `ApplicationContext` 中存在对应的Bean的类型时,会进行的一些操作: + +:frame: topbot +[width="60%",options="header"] +|==== +^|存在Bean的类型 ^|操作 ^|作用 +|`UrlCleaner`|`WebCallbackManager.setUrlCleaner(urlCleaner)`|资源清理(资源(比如将满足 /foo/:id 的 URL 都归到 /foo/* 资源下)) +|`UrlBlockHandler`|`WebCallbackManager.setUrlBlockHandler(urlBlockHandler)`|自定义限流处理逻辑 +|==== + 下表显示 Spring Cloud Alibaba Sentinel 的所有配置信息: :frame: topbot @@ -244,14 +303,16 @@ NOTE: 默认情况下,xml 格式是不支持的。需要添加 `jackson-datafo ^|配置项 ^|含义 ^|默认值 |`spring.cloud.sentinel.enabled`|Sentinel自动化配置是否生效|true |`spring.cloud.sentinel.eager`|取消Sentinel控制台懒加载|false -|`spring.cloud.sentinel.charset`|metric文件字符集|UTF-8 |`spring.cloud.sentinel.transport.port`|应用与Sentinel控制台交互的端口,应用本地会起一个该端口占用的HttpServer|8721 |`spring.cloud.sentinel.transport.dashboard`|Sentinel 控制台地址| |`spring.cloud.sentinel.transport.heartbeatIntervalMs`|应用与Sentinel控制台的心跳间隔时间| |`spring.cloud.sentinel.filter.order`|Servlet Filter的加载顺序。Starter内部会构造这个filter|Integer.MIN_VALUE |`spring.cloud.sentinel.filter.spring.url-patterns`|数据类型是数组。表示Servlet Filter的url pattern集合|/* +|`spring.cloud.sentinel.metric.charset`|metric文件字符集|UTF-8 |`spring.cloud.sentinel.metric.fileSingleSize`|Sentinel metric 单个文件的大小| |`spring.cloud.sentinel.metric.fileTotalCount`|Sentinel metric 总文件数量| +|`spring.cloud.sentinel.log.dir`|Sentinel 日志文件所在的目录| +|`spring.cloud.sentinel.log.switch-pid`|Sentinel 日志文件名是否需要带上pid|false |`spring.cloud.sentinel.servlet.blockPage`| 自定义的跳转 URL,当请求被限流时会自动跳转至设定好的 URL | |`spring.cloud.sentinel.flow.coldFactor`| https://github.com/alibaba/Sentinel/wiki/%E9%99%90%E6%B5%81---%E5%86%B7%E5%90%AF%E5%8A%A8[冷启动因子] |3 |==== diff --git a/spring-cloud-alibaba-examples/ans-example/ans-consumer-feign-example/src/main/resources/application.properties b/spring-cloud-alibaba-examples/ans-example/ans-consumer-feign-example/src/main/resources/application.properties index 542653d9..74babdc1 100644 --- a/spring-cloud-alibaba-examples/ans-example/ans-consumer-feign-example/src/main/resources/application.properties +++ b/spring-cloud-alibaba-examples/ans-example/ans-consumer-feign-example/src/main/resources/application.properties @@ -1,4 +1,4 @@ server.port=18083 # The following configuration can be omitted. -spring.cloud.alicloud.ans.server.list=127.0.0.1 -spring.cloud.alicloud.ans.server.port=8080 \ No newline at end of file +spring.cloud.alicloud.ans.server-list=127.0.0.1 +spring.cloud.alicloud.ans.server-port=8080 \ No newline at end of file diff --git a/spring-cloud-alibaba-examples/ans-example/ans-consumer-ribbon-example/src/main/resources/application.properties b/spring-cloud-alibaba-examples/ans-example/ans-consumer-ribbon-example/src/main/resources/application.properties index 315a9033..d9b7ed46 100644 --- a/spring-cloud-alibaba-examples/ans-example/ans-consumer-ribbon-example/src/main/resources/application.properties +++ b/spring-cloud-alibaba-examples/ans-example/ans-consumer-ribbon-example/src/main/resources/application.properties @@ -1,4 +1,4 @@ server.port=18082 # The following configuration can be omitted. -spring.cloud.alicloud.ans.server.list=127.0.0.1 -spring.cloud.alicloud.ans.server.port=8080 \ No newline at end of file +spring.cloud.alicloud.ans.server-list=127.0.0.1 +spring.cloud.alicloud.ans.server-port=8080 \ No newline at end of file diff --git a/spring-cloud-alibaba-examples/ans-example/ans-provider-example/src/main/resources/application.properties b/spring-cloud-alibaba-examples/ans-example/ans-provider-example/src/main/resources/application.properties index 2fb389e2..e5f6cdef 100644 --- a/spring-cloud-alibaba-examples/ans-example/ans-provider-example/src/main/resources/application.properties +++ b/spring-cloud-alibaba-examples/ans-example/ans-provider-example/src/main/resources/application.properties @@ -1,4 +1,4 @@ spring.application.name=ans-provider server.port=18081 -spring.cloud.alicloud.ans.server.list=127.0.0.1 -spring.cloud.alicloud.ans.server.port=8080 \ No newline at end of file +spring.cloud.alicloud.ans.server-list=127.0.0.1 +spring.cloud.alicloud.ans.server-port=8080 \ No newline at end of file diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/pom.xml b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/pom.xml index 2699f7c3..11c588bd 100644 --- a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/pom.xml +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/pom.xml @@ -40,6 +40,11 @@ spring-cloud-starter-openfeign + + org.springframework.cloud + spring-cloud-starter-alibaba-sentinel + + diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ConsumerApplication.java b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ConsumerApplication.java index 86be39c4..5dfd4eba 100644 --- a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ConsumerApplication.java +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ConsumerApplication.java @@ -2,6 +2,7 @@ package org.springframework.cloud.alibaba.cloud.examples; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.alibaba.cloud.examples.ConsumerApplication.EchoService; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.openfeign.EnableFeignClients; @@ -10,6 +11,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.client.RestTemplate; /** @@ -30,9 +32,40 @@ public class ConsumerApplication { SpringApplication.run(ConsumerApplication.class, args); } - @FeignClient(name = "service-provider") + @FeignClient(name = "service-provider", fallback = EchoServiceFallback.class, configuration = FeignConfiguration.class) public interface EchoService { @RequestMapping(value = "/echo/{str}", method = RequestMethod.GET) String echo(@PathVariable("str") String str); + + @RequestMapping(value = "/divide", method = RequestMethod.GET) + String divide(@RequestParam("a") Integer a, @RequestParam("b") Integer b); + + @RequestMapping(value = "/notFound", method = RequestMethod.GET) + String notFound(); + } + +} + +class FeignConfiguration { + @Bean + public EchoServiceFallback echoServiceFallback() { + return new EchoServiceFallback(); + } +} + +class EchoServiceFallback implements EchoService { + @Override + public String echo(@PathVariable("str") String str) { + return "echo fallback"; + } + + @Override + public String divide(@RequestParam Integer a, @RequestParam Integer b) { + return "divide fallback"; + } + + @Override + public String notFound() { + return "notFound fallback"; } } diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/TestController.java b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/TestController.java index 5186f2d8..55c751c7 100644 --- a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/TestController.java +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/TestController.java @@ -6,6 +6,7 @@ import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @@ -29,6 +30,16 @@ public class TestController { String.class); } + @RequestMapping(value = "/notFound-feign", method = RequestMethod.GET) + public String notFound() { + return echoService.notFound(); + } + + @RequestMapping(value = "/divide-feign", method = RequestMethod.GET) + public String divide(@RequestParam Integer a, @RequestParam Integer b) { + return echoService.divide(a, b); + } + @RequestMapping(value = "/echo-feign/{str}", method = RequestMethod.GET) public String feign(@PathVariable String str) { return echoService.echo(str); diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/resources/application.properties b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/resources/application.properties index 1df160fc..fbc9736e 100644 --- a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/resources/application.properties +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/resources/application.properties @@ -1,4 +1,12 @@ spring.application.name=service-consumer server.port=18083 management.endpoints.web.exposure.include=* -spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 \ No newline at end of file +spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 + +feign.sentinel.enabled=true + +spring.cloud.sentinel.transport.dashboard=localhost:8080 +spring.cloud.sentinel.eager=true + +spring.cloud.sentinel.datasource.ds1.file.file=classpath: flowrule.json +spring.cloud.sentinel.datasource.ds1.file.data-type=json \ No newline at end of file diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/resources/flowrule.json b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/resources/flowrule.json new file mode 100644 index 00000000..3dd01162 --- /dev/null +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/resources/flowrule.json @@ -0,0 +1,10 @@ +[ + { + "resource": "GET:http://service-provider/echo/{str}", + "controlBehavior": 0, + "count": 1, + "grade": 1, + "limitApp": "default", + "strategy": 0 + } +] \ No newline at end of file diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-provider-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ProviderApplication.java b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-provider-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ProviderApplication.java index 169bccd6..843f5cbe 100644 --- a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-provider-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ProviderApplication.java +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-provider-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ProviderApplication.java @@ -6,6 +6,7 @@ import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** @@ -25,5 +26,10 @@ public class ProviderApplication { public String echo(@PathVariable String string) { return "hello Nacos Discovery " + string; } + + @RequestMapping(value = "/divide", method = RequestMethod.GET) + public String divide(@RequestParam Integer a, @RequestParam Integer b) { + return String.valueOf(a / b); + } } } diff --git a/spring-cloud-alibaba-examples/rocketmq-example/readme-zh.md b/spring-cloud-alibaba-examples/rocketmq-example/readme-zh.md index 7bb2472f..5d2dd9ae 100644 --- a/spring-cloud-alibaba-examples/rocketmq-example/readme-zh.md +++ b/spring-cloud-alibaba-examples/rocketmq-example/readme-zh.md @@ -75,7 +75,7 @@ spring.cloud.stream.bindings.input.group=test-group ### 下载并启动 RocketMQ -在接入 RocketMQ Binder 之前,首先需要启动 RocketMQ 的 Name Server 和 Broker。 +**在接入 RocketMQ Binder 之前,首先需要启动 RocketMQ 的 Name Server 和 Broker。** 1. 下载[RocketMQ最新的二进制文件](https://www.apache.org/dyn/closer.cgi?path=rocketmq/4.3.2/rocketmq-all-4.3.2-bin-release.zip),并解压 diff --git a/spring-cloud-alibaba-examples/rocketmq-example/readme.md b/spring-cloud-alibaba-examples/rocketmq-example/readme.md new file mode 100644 index 00000000..7bdc2fbd --- /dev/null +++ b/spring-cloud-alibaba-examples/rocketmq-example/readme.md @@ -0,0 +1,271 @@ +# RocketMQ Example + +## Project Instruction + +This example illustrates how to use RocketMQ Binder implement pub/sub messages for Spring Cloud applications. + +[RocketMQ](https://rocketmq.apache.org/) is a distributed messaging and streaming platform with low latency, high performance and reliability, trillion-level capacity and flexible scalability. + +Before we start the demo, let's look at Spring Cloud Stream. + +Spring Cloud Stream is a framework for building message-driven microservice applications. Spring Cloud Stream builds upon Spring Boot to create standalone, production-grade Spring applications and uses Spring Integration to provide connectivity to message brokers. It provides opinionated configuration of middleware from several vendors, introducing the concepts of persistent publish-subscribe semantics, consumer groups, and partitions. + +There are two concepts in Spring Cloud Stream: Binder 和 Binding. + +* Binder: A strategy interface used to bind an app interface to a logical name. + +Binder Implementations includes `KafkaMessageChannelBinder` of kafka, `RabbitMessageChannelBinder` of RabbitMQ and `RocketMQMessageChannelBinder` of `RocketMQ`. + +* Binding: Including Input Binding and Output Binding. + +Binding is Bridge between the external messaging systems and application provided Producers and Consumers of messages. + +This is a overview of Spring Cloud Stream. + +![](https://docs.spring.io/spring-cloud-stream/docs/current/reference/htmlsingle/images/SCSt-overview.png) + +## Demo + +### Integration with RocketMQ Binder + +Before we start the demo, let's learn how to Integration with RocketMQ Binder to a Spring Cloud application. + +**Note: This section is to show you how to connect to Sentinel. The configurations have been completed in the following example, so you don't need modify the code any more.** + +1. Add dependency spring-cloud-starter-stream-rocketmq in the pom.xml file in your Spring Cloud project. + +```xml + + org.springframework.cloud + spring-cloud-starter-stream-rocketmq + +``` + +2. Configure Input and Output Binding and cooperate with `@EnableBinding` annotation + +```java +@SpringBootApplication +@EnableBinding({ Source.class, Sink.class }) +public class RocketMQApplication { + public static void main(String[] args) { + SpringApplication.run(RocketMQApplication.class, args); + } +} +``` + +Configure Binding: +```properties +# configure the nameserver of rocketmq +spring.cloud.stream.rocketmq.binder.namesrv-addr=127.0.0.1:9876 +# configure the output binding named output +spring.cloud.stream.bindings.output.destination=test-topic +spring.cloud.stream.bindings.output.content-type=application/json +# configure the input binding named input +spring.cloud.stream.bindings.input.destination=test-topic +spring.cloud.stream.bindings.input.content-type=application/json +spring.cloud.stream.bindings.input.group=test-group + +``` + +3. pub/sub messages + +### Download and Startup RocketMQ + +You should startup Name Server and Broker before using RocketMQ Binder. + +1. Download [RocketMQ](https://www.apache.org/dyn/closer.cgi?path=rocketmq/4.3.2/rocketmq-all-4.3.2-bin-release.zip) and unzip it. + +2. Startup Name Server + +```bash +sh bin/mqnamesrv +``` + +3. Startup Broker + +```bash +sh bin/mqbroker -n localhost:9876 +``` + +4. Create topic: test-topic + +```bash +sh bin/mqadmin updateTopic -n localhost:9876 -c DefaultCluster -t test-topic +``` + +### Start Application + +1. Add necessary configurations to file `/src/main/resources/application.properties`. + +```properties +spring.application.name=rocketmq-example +server.port=28081 +``` + +2. Start the application in IDE or by building a fatjar. + + 1. Start in IDE: Find main class `RocketMQApplication`, and execute the main method. + 2. Build a fatjar: Execute command `mvn clean package` to build a fatjar, and run command `java -jar rocketmq-example.jar` to start the application. + + +### Message Handle + +Using the binding named output and sent messages to `test-topic` topic. + +And using two input bindings to subscribe messages. + +* input1: subscribe the message of `test-topic` topic and consume ordered messages(all messages should in the same MessageQueue if you want to consuming ordered messages). + +* input2: subscribe the message of `test-topic` topic and consume concurrent messages which tags is `tagStr`, the thread number in pool is 20 in Consumer side. + +see the configuration below: + +```properties +spring.cloud.stream.rocketmq.binder.namesrv-addr=127.0.0.1:9876 + +spring.cloud.stream.bindings.output.destination=test-topic +spring.cloud.stream.bindings.output.content-type=application/json + +spring.cloud.stream.bindings.input1.destination=test-topic +spring.cloud.stream.bindings.input1.content-type=text/plain +spring.cloud.stream.bindings.input1.group=test-group1 +spring.cloud.stream.rocketmq.bindings.input1.consumer.orderly=true + +spring.cloud.stream.bindings.input2.destination=test-topic +spring.cloud.stream.bindings.input2.content-type=text/plain +spring.cloud.stream.bindings.input2.group=test-group2 +spring.cloud.stream.rocketmq.bindings.input2.consumer.orderly=false +spring.cloud.stream.rocketmq.bindings.input2.consumer.tags=tagStr +spring.cloud.stream.bindings.input2.consumer.concurrency=20 + +``` + +#### Pub Messages + +Using MessageChannel to send messages: + +```java +public class ProducerRunner implements CommandLineRunner { + @Autowired + private MessageChannel output; + @Override + public void run(String... args) throws Exception { + Map headers = new HashMap<>(); + headers.put(MessageConst.PROPERTY_TAGS, "tagStr"); + Message message = MessageBuilder.createMessage(msg, new MessageHeaders(headers)); + output.send(message); + } +} +``` + +Or you can using the native API of RocketMQ to send messages: + +```java +public class RocketMQProducer { + DefaultMQProducer producer = new DefaultMQProducer("producer_group"); + producer.setNamesrvAddr("127.0.0.1:9876"); + producer.start(); + + Message msg = new Message("test-topic", "tagStr", "message from rocketmq producer".getBytes()); + producer.send(msg); +} +``` + +#### Sub Messages + +Using `@StreamListener` to receive messages: + +```java +@Service +public class ReceiveService { + + @StreamListener("input1") + public void receiveInput1(String receiveMsg) { + System.out.println("input1 receive: " + receiveMsg); + } + + @StreamListener("input2") + public void receiveInput2(String receiveMsg) { + System.out.println("input2 receive: " + receiveMsg); + } + +} +``` + +## Endpoint + +Add dependency `spring-cloud-starter-stream-rocketmq` to your pom.xml file, and configure your endpoint security strategy. + +* Spring Boot1.x: Add configuration `management.security.enabled=false` +* Spring Boot2.x: Add configuration `management.endpoints.web.exposure.include=*` + +To view the endpoint information, visit the following URLS: +* Spring Boot1.x: Sentinel Endpoint URL is http://127.0.0.1:18083/rocketmq_binder. +* Spring Boot2.x: Sentinel Endpoint URL is http://127.0.0.1:18083/actuator/rocketmq-binder. + +Endpoint will metrics some data like last send timestamp, sending or receive message successfully times or unsuccessfully times. + +```json +{ + "runtime": { + "lastSend.timestamp": 1542786623915 + }, + "metrics": { + "scs-rocketmq.consumer.test-topic.totalConsumed": { + "count": 11 + }, + "scs-rocketmq.consumer.test-topic.totalConsumedFailures": { + "count": 0 + }, + "scs-rocketmq.producer.test-topic.totalSentFailures": { + "count": 0 + }, + "scs-rocketmq.consumer.test-topic.consumedPerSecond": { + "count": 11, + "fifteenMinuteRate": 0.012163847780107841, + "fiveMinuteRate": 0.03614605351360527, + "meanRate": 0.3493213353657594, + "oneMinuteRate": 0.17099243039490175 + }, + "scs-rocketmq.producer.test-topic.totalSent": { + "count": 5 + }, + "scs-rocketmq.producer.test-topic.sentPerSecond": { + "count": 5, + "fifteenMinuteRate": 0.005540151995103271, + "fiveMinuteRate": 0.01652854617838251, + "meanRate": 0.10697493212602836, + "oneMinuteRate": 0.07995558537067671 + }, + "scs-rocketmq.producer.test-topic.sentFailuresPerSecond": { + "count": 0, + "fifteenMinuteRate": 0.0, + "fiveMinuteRate": 0.0, + "meanRate": 0.0, + "oneMinuteRate": 0.0 + }, + "scs-rocketmq.consumer.test-topic.consumedFailuresPerSecond": { + "count": 0, + "fifteenMinuteRate": 0.0, + "fiveMinuteRate": 0.0, + "meanRate": 0.0, + "oneMinuteRate": 0.0 + } + } +} +``` + +Note: You should add [metrics-core dependency](https://mvnrepository.com/artifact/io.dropwizard.metrics/metrics-core) if you want to see metrics data. endpoint will show warning information if you don't add that dependency: + +```json +{ + "warning": "please add metrics-core dependency, we use it for metrics" +} +``` + +## More + +For more information about RocketMQ, see [RocketMQ Project](https://rocketmq.apache.org). + +If you have any ideas or suggestions for Spring Cloud RocketMQ Binder, please don't hesitate to tell us by submitting github issues. + diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ServiceApplication.java b/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ServiceApplication.java index 2f258d7b..c05c01af 100644 --- a/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ServiceApplication.java +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ServiceApplication.java @@ -1,12 +1,13 @@ package org.springframework.cloud.alibaba.cloud.examples; -import com.alibaba.csp.sentinel.datasource.Converter; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.alibaba.sentinel.annotation.SentinelProtect; +import org.springframework.cloud.alibaba.sentinel.annotation.SentinelRestTemplate; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; +import com.alibaba.csp.sentinel.datasource.Converter; + /** * @author xiaojing */ @@ -14,7 +15,7 @@ import org.springframework.web.client.RestTemplate; public class ServiceApplication { @Bean - @SentinelProtect(blockHandler = "handleException", blockHandlerClass = ExceptionUtil.class) + @SentinelRestTemplate(blockHandler = "handleException", blockHandlerClass = ExceptionUtil.class) public RestTemplate restTemplate() { return new RestTemplate(); } @@ -25,9 +26,9 @@ public class ServiceApplication { } @Bean - public Converter myConverter() { - return new JsonFlowRuleListConverter(); - } + public Converter myConverter() { + return new JsonFlowRuleListConverter(); + } public static void main(String[] args) { SpringApplication.run(ServiceApplication.class, args); diff --git a/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/NacosDiscoveryClient.java b/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/NacosDiscoveryClient.java index 519b2512..d7707584 100644 --- a/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/NacosDiscoveryClient.java +++ b/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/NacosDiscoveryClient.java @@ -48,7 +48,7 @@ public class NacosDiscoveryClient implements DiscoveryClient { public List getInstances(String serviceId) { try { List instances = discoveryProperties.namingServiceInstance() - .getAllInstances(serviceId); + .selectInstances(serviceId, true); return hostToServiceInstanceList(instances, serviceId); } catch (Exception e) { @@ -77,9 +77,7 @@ public class NacosDiscoveryClient implements DiscoveryClient { List instances, String serviceId) { List result = new ArrayList(instances.size()); for (Instance instance : instances) { - if (instance.isHealthy()) { - result.add(hostToServiceInstance(instance, serviceId)); - } + result.add(hostToServiceInstance(instance, serviceId)); } return result; } diff --git a/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/NacosDiscoveryProperties.java b/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/NacosDiscoveryProperties.java index 20a16433..c4d7e0e3 100644 --- a/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/NacosDiscoveryProperties.java +++ b/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/NacosDiscoveryProperties.java @@ -306,15 +306,15 @@ public class NacosDiscoveryProperties { this.secretKey = secretKey; } - public String getNamingLoadCacheAtStart() { - return namingLoadCacheAtStart; - } + public String getNamingLoadCacheAtStart() { + return namingLoadCacheAtStart; + } - public void setNamingLoadCacheAtStart(String namingLoadCacheAtStart) { - this.namingLoadCacheAtStart = namingLoadCacheAtStart; - } + public void setNamingLoadCacheAtStart(String namingLoadCacheAtStart) { + this.namingLoadCacheAtStart = namingLoadCacheAtStart; + } - @Override + @Override public String toString() { return "NacosDiscoveryProperties{" + "serverAddr='" + serverAddr + '\'' + ", endpoint='" + endpoint + '\'' + ", namespace='" + namespace + '\'' @@ -323,7 +323,8 @@ public class NacosDiscoveryProperties { + ", metadata=" + metadata + ", registerEnabled=" + registerEnabled + ", ip='" + ip + '\'' + ", networkInterface='" + networkInterface + '\'' + ", port=" + port + ", secure=" + secure + ", accessKey='" + accessKey - + ", namingLoadCacheAtStart=" + namingLoadCacheAtStart + '\'' + ", secretKey='" + secretKey + '\'' + '}'; + + ", namingLoadCacheAtStart=" + namingLoadCacheAtStart + '\'' + + ", secretKey='" + secretKey + '\'' + '}'; } public void overrideFromEnv(Environment env) { @@ -372,7 +373,7 @@ public class NacosDiscoveryProperties { properties.put(ACCESS_KEY, accessKey); properties.put(SECRET_KEY, secretKey); properties.put(CLUSTER_NAME, clusterName); - properties.put(NAMING_LOAD_CACHE_AT_START,namingLoadCacheAtStart); + properties.put(NAMING_LOAD_CACHE_AT_START, namingLoadCacheAtStart); try { namingService = NacosFactory.createNamingService(properties); diff --git a/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/ribbon/NacosServerList.java b/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/ribbon/NacosServerList.java index ac33d6fd..74dda2de 100644 --- a/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/ribbon/NacosServerList.java +++ b/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/ribbon/NacosServerList.java @@ -57,7 +57,7 @@ public class NacosServerList extends AbstractServerList { private List getServers() { try { List instances = discoveryProperties.namingServiceInstance() - .getAllInstances(serviceId); + .selectInstances(serviceId, true); return instancesToServerList(instances); } catch (Exception e) { @@ -70,9 +70,7 @@ public class NacosServerList extends AbstractServerList { private List instancesToServerList(List instances) { List result = new ArrayList<>(instances.size()); for (Instance instance : instances) { - if (instance.isHealthy()) { - result.add(new NacosServer(instance)); - } + result.add(new NacosServer(instance)); } return result; diff --git a/spring-cloud-alibaba-sentinel-datasource/pom.xml b/spring-cloud-alibaba-sentinel-datasource/pom.xml index 5d7d8515..2a77bf5a 100644 --- a/spring-cloud-alibaba-sentinel-datasource/pom.xml +++ b/spring-cloud-alibaba-sentinel-datasource/pom.xml @@ -18,6 +18,13 @@ com.alibaba.csp sentinel-datasource-extension + true + + + + com.alibaba.csp + sentinel-parameter-flow-control + true diff --git a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/converter/JsonConverter.java b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/converter/JsonConverter.java index 0247ba80..591d61bd 100644 --- a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/converter/JsonConverter.java +++ b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/converter/JsonConverter.java @@ -18,6 +18,7 @@ import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; +import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; import com.alibaba.csp.sentinel.slots.system.SystemRule; import com.fasterxml.jackson.core.JsonProcessingException; @@ -32,6 +33,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; * @see DegradeRule * @see SystemRule * @see AuthorityRule + * @see ParamFlowRule * @see ObjectMapper */ public class JsonConverter implements Converter> { @@ -48,7 +50,7 @@ public class JsonConverter implements Converter> { public List convert(String source) { List ruleList = new ArrayList<>(); if (StringUtils.isEmpty(source)) { - logger.info( + logger.warn( "Sentinel JsonConverter can not convert rules because source is empty"); return ruleList; } @@ -68,7 +70,7 @@ public class JsonConverter implements Converter> { List rules = Arrays.asList(convertFlowRule(itemJson), convertDegradeRule(itemJson), convertSystemRule(itemJson), - convertAuthorityRule(itemJson)); + convertAuthorityRule(itemJson), convertParamFlowRule(itemJson)); List convertRuleList = rules.stream() .filter(rule -> !ObjectUtils.isEmpty(rule)) @@ -101,8 +103,6 @@ public class JsonConverter implements Converter> { throw new RuntimeException( "Sentinel JsonConverter convert error: " + e.getMessage(), e); } - logger.info("Sentinel JsonConverter convert {} rules: {}", ruleList.size(), - ruleList); return ruleList; } @@ -154,4 +154,15 @@ public class JsonConverter implements Converter> { return rule; } + private ParamFlowRule convertParamFlowRule(String json) { + ParamFlowRule rule = null; + try { + rule = objectMapper.readValue(json, ParamFlowRule.class); + } + catch (Exception e) { + // ignore + } + return rule; + } + } diff --git a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/converter/XmlConverter.java b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/converter/XmlConverter.java index b4a95a99..6ff3f8bb 100644 --- a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/converter/XmlConverter.java +++ b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/converter/XmlConverter.java @@ -18,6 +18,7 @@ import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; +import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; import com.alibaba.csp.sentinel.slots.system.SystemRule; import com.fasterxml.jackson.core.JsonProcessingException; @@ -32,7 +33,7 @@ import com.fasterxml.jackson.dataformat.xml.XmlMapper; * @see DegradeRule * @see SystemRule * @see AuthorityRule - * @see XmlMapper + * @see ParamFlowRule */ public class XmlConverter implements Converter> { @@ -48,7 +49,7 @@ public class XmlConverter implements Converter> { public List convert(String source) { List ruleList = new ArrayList<>(); if (StringUtils.isEmpty(source)) { - logger.info( + logger.warn( "Sentinel XmlConverter can not convert rules because source is empty"); return ruleList; } @@ -68,7 +69,7 @@ public class XmlConverter implements Converter> { List rules = Arrays.asList(convertFlowRule(itemXml), convertDegradeRule(itemXml), convertSystemRule(itemXml), - convertAuthorityRule(itemXml)); + convertAuthorityRule(itemXml), convertParamFlowRule(itemXml)); List convertRuleList = rules.stream() .filter(rule -> !ObjectUtils.isEmpty(rule)) @@ -101,8 +102,6 @@ public class XmlConverter implements Converter> { throw new RuntimeException( "Sentinel XmlConverter convert error: " + e.getMessage(), e); } - logger.info("Sentinel XmlConverter convert {} rules: {}", ruleList.size(), - ruleList); return ruleList; } @@ -154,4 +153,15 @@ public class XmlConverter implements Converter> { return rule; } + private ParamFlowRule convertParamFlowRule(String json) { + ParamFlowRule rule = null; + try { + rule = xmlMapper.readValue(json, ParamFlowRule.class); + } + catch (Exception e) { + // ignore + } + return rule; + } + } diff --git a/spring-cloud-alibaba-sentinel/pom.xml b/spring-cloud-alibaba-sentinel/pom.xml index b4f63c9f..f2068eb3 100644 --- a/spring-cloud-alibaba-sentinel/pom.xml +++ b/spring-cloud-alibaba-sentinel/pom.xml @@ -35,6 +35,18 @@ sentinel-dubbo-adapter + + org.springframework.cloud + spring-cloud-starter-openfeign + provided + true + + + + com.alibaba.csp + sentinel-parameter-flow-control + + org.springframework.cloud spring-cloud-alibaba-sentinel-datasource diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelProperties.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelProperties.java index fb3cba2e..d827222e 100644 --- a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelProperties.java +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelProperties.java @@ -26,6 +26,7 @@ import org.springframework.cloud.alibaba.sentinel.datasource.config.DataSourcePr import org.springframework.core.Ordered; import com.alibaba.csp.sentinel.config.SentinelConfig; +import com.alibaba.csp.sentinel.log.LogBase; import com.alibaba.csp.sentinel.transport.config.TransportConfig; /** @@ -48,11 +49,6 @@ public class SentinelProperties { */ private boolean enabled = true; - /** - * charset when sentinel write or search metric file {@link SentinelConfig#CHARSET} - */ - private String charset = "UTF-8"; - /** * configurations about datasource, like 'nacos', 'apollo', 'file', 'zookeeper' */ @@ -91,6 +87,12 @@ public class SentinelProperties { @NestedConfigurationProperty private Flow flow = new Flow(); + /** + * sentinel log configuration {@link LogBase} + */ + @NestedConfigurationProperty + private Log log = new Log(); + public boolean isEager() { return eager; } @@ -107,14 +109,6 @@ public class SentinelProperties { this.flow = flow; } - public String getCharset() { - return charset; - } - - public void setCharset(String charset) { - this.charset = charset; - } - public Transport getTransport() { return transport; } @@ -163,6 +157,14 @@ public class SentinelProperties { this.datasource = datasource; } + public Log getLog() { + return log; + } + + public void setLog(Log log) { + this.log = log; + } + public static class Flow { /** @@ -208,6 +210,12 @@ public class SentinelProperties { */ private String fileTotalCount; + /** + * charset when sentinel write or search metric file + * {@link SentinelConfig#CHARSET} + */ + private String charset = "UTF-8"; + public String getFileSingleSize() { return fileSingleSize; } @@ -223,6 +231,14 @@ public class SentinelProperties { public void setFileTotalCount(String fileTotalCount) { this.fileTotalCount = fileTotalCount; } + + public String getCharset() { + return charset; + } + + public void setCharset(String charset) { + this.charset = charset; + } } public static class Transport { @@ -299,4 +315,34 @@ public class SentinelProperties { } } + public static class Log { + + /** + * sentinel log base dir + */ + private String dir; + + /** + * distinguish the log file by pid number + */ + private boolean switchPid = false; + + public String getDir() { + return dir; + } + + public void setDir(String dir) { + this.dir = dir; + } + + public boolean isSwitchPid() { + return switchPid; + } + + public void setSwitchPid(boolean switchPid) { + this.switchPid = switchPid; + } + + } + } diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/annotation/SentinelProtect.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/annotation/SentinelRestTemplate.java similarity index 79% rename from spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/annotation/SentinelProtect.java rename to spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/annotation/SentinelRestTemplate.java index 6586c8d3..da1a3c26 100644 --- a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/annotation/SentinelProtect.java +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/annotation/SentinelRestTemplate.java @@ -16,7 +16,11 @@ package org.springframework.cloud.alibaba.sentinel.annotation; -import java.lang.annotation.*; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; /** * @author fangjian @@ -24,7 +28,7 @@ import java.lang.annotation.*; @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented -public @interface SentinelProtect { +public @interface SentinelRestTemplate { String blockHandler() default ""; diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelAutoConfiguration.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelAutoConfiguration.java index 093cab8b..53347f7b 100644 --- a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelAutoConfiguration.java +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelAutoConfiguration.java @@ -21,7 +21,6 @@ import java.util.Optional; import javax.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -41,6 +40,7 @@ import com.alibaba.csp.sentinel.adapter.servlet.config.WebServletConfig; import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.init.InitExecutor; +import com.alibaba.csp.sentinel.log.LogBase; import com.alibaba.csp.sentinel.transport.config.TransportConfig; import com.alibaba.csp.sentinel.util.AppNameUtil; @@ -71,7 +71,6 @@ public class SentinelAutoConfiguration { @PostConstruct private void init() { - if (StringUtils.isEmpty(System.getProperty(AppNameUtil.APP_NAME)) && StringUtils.hasText(projectName)) { System.setProperty(AppNameUtil.APP_NAME, projectName); @@ -93,8 +92,9 @@ public class SentinelAutoConfiguration { properties.getTransport().getHeartbeatIntervalMs()); } if (StringUtils.isEmpty(System.getProperty(SentinelConfig.CHARSET)) - && StringUtils.hasText(properties.getCharset())) { - System.setProperty(SentinelConfig.CHARSET, properties.getCharset()); + && StringUtils.hasText(properties.getMetric().getCharset())) { + System.setProperty(SentinelConfig.CHARSET, + properties.getMetric().getCharset()); } if (StringUtils .isEmpty(System.getProperty(SentinelConfig.SINGLE_METRIC_FILE_SIZE)) @@ -113,10 +113,19 @@ public class SentinelAutoConfiguration { System.setProperty(SentinelConfig.COLD_FACTOR, properties.getFlow().getColdFactor()); } - if (StringUtils.hasText(properties.getServlet().getBlockPage())) { WebServletConfig.setBlockPage(properties.getServlet().getBlockPage()); } + if (StringUtils.isEmpty(System.getProperty(LogBase.LOG_DIR)) + && StringUtils.hasText(properties.getLog().getDir())) { + System.setProperty(LogBase.LOG_DIR, properties.getLog().getDir()); + } + if (StringUtils.isEmpty(System.getProperty(LogBase.LOG_NAME_USE_PID)) + && properties.getLog().isSwitchPid()) { + System.setProperty(LogBase.LOG_NAME_USE_PID, + String.valueOf(properties.getLog().isSwitchPid())); + } + urlBlockHandlerOptional.ifPresent(WebCallbackManager::setUrlBlockHandler); urlCleanerOptional.ifPresent(WebCallbackManager::setUrlCleaner); @@ -146,26 +155,22 @@ public class SentinelAutoConfiguration { } @Bean("sentinel-json-converter") - public JsonConverter jsonConverter( - @Qualifier("sentinel-object-mapper") ObjectMapper objectMapper) { - return new JsonConverter(objectMapper); + public JsonConverter jsonConverter() { + return new JsonConverter(objectMapper()); } - @Bean("sentinel-object-mapper") - public ObjectMapper objectMapper() { + private ObjectMapper objectMapper() { return new ObjectMapper(); } @ConditionalOnClass(XmlMapper.class) protected static class SentinelXmlConfiguration { @Bean("sentinel-xml-converter") - public XmlConverter xmlConverter( - @Qualifier("sentinel-xml-mapper") XmlMapper xmlMapper) { - return new XmlConverter(xmlMapper); + public XmlConverter xmlConverter() { + return new XmlConverter(xmlMapper()); } - @Bean("sentinel-xml-mapper") - public XmlMapper xmlMapper() { + private XmlMapper xmlMapper() { return new XmlMapper(); } } diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelBeanPostProcessor.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelBeanPostProcessor.java index 0932ac85..d9e9cd7e 100644 --- a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelBeanPostProcessor.java +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelBeanPostProcessor.java @@ -25,17 +25,17 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor; import org.springframework.beans.factory.support.RootBeanDefinition; -import org.springframework.cloud.alibaba.sentinel.annotation.SentinelProtect; +import org.springframework.cloud.alibaba.sentinel.annotation.SentinelRestTemplate; import org.springframework.context.ApplicationContext; import org.springframework.core.type.StandardMethodMetadata; import org.springframework.util.StringUtils; import org.springframework.web.client.RestTemplate; /** - * PostProcessor handle @SentinelProtect Annotation, add interceptor for RestTemplate + * PostProcessor handle @SentinelRestTemplate Annotation, add interceptor for RestTemplate * * @author Jim - * @see SentinelProtect + * @see SentinelRestTemplate * @see SentinelProtectInterceptor */ public class SentinelBeanPostProcessor implements MergedBeanDefinitionPostProcessor { @@ -43,16 +43,16 @@ public class SentinelBeanPostProcessor implements MergedBeanDefinitionPostProces @Autowired private ApplicationContext applicationContext; - private ConcurrentHashMap cache = new ConcurrentHashMap<>(); + private ConcurrentHashMap cache = new ConcurrentHashMap<>(); @Override public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class beanType, String beanName) { if (checkSentinelProtect(beanDefinition, beanType)) { - SentinelProtect sentinelProtect = ((StandardMethodMetadata) beanDefinition + SentinelRestTemplate sentinelRestTemplate = ((StandardMethodMetadata) beanDefinition .getSource()).getIntrospectedMethod() - .getAnnotation(SentinelProtect.class); - cache.put(beanName, sentinelProtect); + .getAnnotation(SentinelRestTemplate.class); + cache.put(beanName, sentinelRestTemplate); } } @@ -61,26 +61,26 @@ public class SentinelBeanPostProcessor implements MergedBeanDefinitionPostProces return beanType == RestTemplate.class && beanDefinition.getSource() instanceof StandardMethodMetadata && ((StandardMethodMetadata) beanDefinition.getSource()) - .isAnnotated(SentinelProtect.class.getName()); + .isAnnotated(SentinelRestTemplate.class.getName()); } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (cache.containsKey(beanName)) { - // add interceptor for each RestTemplate with @SentinelProtect annotation + // add interceptor for each RestTemplate with @SentinelRestTemplate annotation StringBuilder interceptorBeanName = new StringBuilder(); - SentinelProtect sentinelProtect = cache.get(beanName); + SentinelRestTemplate sentinelRestTemplate = cache.get(beanName); interceptorBeanName .append(StringUtils.uncapitalize( SentinelProtectInterceptor.class.getSimpleName())) .append("_") - .append(sentinelProtect.blockHandlerClass().getSimpleName()) - .append(sentinelProtect.blockHandler()).append("_") - .append(sentinelProtect.fallbackClass().getSimpleName()) - .append(sentinelProtect.fallback()); + .append(sentinelRestTemplate.blockHandlerClass().getSimpleName()) + .append(sentinelRestTemplate.blockHandler()).append("_") + .append(sentinelRestTemplate.fallbackClass().getSimpleName()) + .append(sentinelRestTemplate.fallback()); RestTemplate restTemplate = (RestTemplate) bean; - registerBean(interceptorBeanName.toString(), sentinelProtect); + registerBean(interceptorBeanName.toString(), sentinelRestTemplate); SentinelProtectInterceptor sentinelProtectInterceptor = applicationContext .getBean(interceptorBeanName.toString(), SentinelProtectInterceptor.class); @@ -90,13 +90,13 @@ public class SentinelBeanPostProcessor implements MergedBeanDefinitionPostProces } private void registerBean(String interceptorBeanName, - SentinelProtect sentinelProtect) { + SentinelRestTemplate sentinelRestTemplate) { // register SentinelProtectInterceptor bean DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext .getAutowireCapableBeanFactory(); BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder .genericBeanDefinition(SentinelProtectInterceptor.class); - beanDefinitionBuilder.addConstructorArgValue(sentinelProtect); + beanDefinitionBuilder.addConstructorArgValue(sentinelRestTemplate); BeanDefinition interceptorBeanDefinition = beanDefinitionBuilder .getRawBeanDefinition(); beanFactory.registerBeanDefinition(interceptorBeanName, diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelProtectInterceptor.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelProtectInterceptor.java index 844a5416..2d18beef 100644 --- a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelProtectInterceptor.java +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelProtectInterceptor.java @@ -22,7 +22,7 @@ import java.net.URI; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.cloud.alibaba.sentinel.annotation.SentinelProtect; +import org.springframework.cloud.alibaba.sentinel.annotation.SentinelRestTemplate; import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestInterceptor; @@ -37,19 +37,19 @@ import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException; import com.alibaba.csp.sentinel.util.StringUtil; /** - * Interceptor using by SentinelProtect and SentinelProtectInterceptor + * Interceptor using by SentinelRestTemplate * * @author fangjian */ public class SentinelProtectInterceptor implements ClientHttpRequestInterceptor { - private static final Logger logger = LoggerFactory + private static final Logger logger = LoggerFactory .getLogger(SentinelProtectInterceptor.class); - private SentinelProtect sentinelProtect; + private SentinelRestTemplate sentinelRestTemplate; - public SentinelProtectInterceptor(SentinelProtect sentinelProtect) { - this.sentinelProtect = sentinelProtect; + public SentinelProtectInterceptor(SentinelRestTemplate sentinelRestTemplate) { + this.sentinelRestTemplate = sentinelRestTemplate; } @Override @@ -92,15 +92,16 @@ public class SentinelProtectInterceptor implements ClientHttpRequestInterceptor Object[] args = new Object[] { ex }; // handle degrade if (isDegradeFailure(ex)) { - Method method = extractFallbackMethod(sentinelProtect.fallback(), - sentinelProtect.fallbackClass()); + Method method = extractFallbackMethod(sentinelRestTemplate.fallback(), + sentinelRestTemplate.fallbackClass()); if (method != null) { method.invoke(null, args); } } // handle block - Method blockHandler = extractBlockHandlerMethod(sentinelProtect.blockHandler(), - sentinelProtect.blockHandlerClass()); + Method blockHandler = extractBlockHandlerMethod( + sentinelRestTemplate.blockHandler(), + sentinelRestTemplate.blockHandlerClass()); if (blockHandler != null) { blockHandler.invoke(null, args); } diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelEndpoint.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelEndpoint.java index f20098c2..03b6c0f8 100644 --- a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelEndpoint.java +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelEndpoint.java @@ -32,6 +32,8 @@ import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; +import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; +import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager; import com.alibaba.csp.sentinel.slots.system.SystemRule; import com.alibaba.csp.sentinel.slots.system.SystemRuleManager; @@ -58,10 +60,12 @@ public class SentinelEndpoint { List flowRules = FlowRuleManager.getRules(); List degradeRules = DegradeRuleManager.getRules(); List systemRules = SystemRuleManager.getRules(); + List paramFlowRules = ParamFlowRuleManager.getRules(); result.put("properties", sentinelProperties); result.put("FlowRules", flowRules); result.put("DegradeRules", degradeRules); result.put("SystemRules", systemRules); + result.put("ParamFlowRule", paramFlowRules); result.put("datasources", new HashMap()); dataSourceHandler.getDataSourceBeanNameList().forEach(dataSourceBeanName -> { ReadableDataSource dataSource = applicationContext.getBean(dataSourceBeanName, diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelContractHolder.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelContractHolder.java new file mode 100644 index 00000000..6056e800 --- /dev/null +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelContractHolder.java @@ -0,0 +1,55 @@ +/* + * 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 org.springframework.cloud.alibaba.sentinel.feign; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import feign.Contract; +import feign.MethodMetadata; + +/** + * + * Using static field {@link SentinelContractHolder#metadataMap} to hold + * {@link MethodMetadata} data + * + * @author Jim + */ +public class SentinelContractHolder implements Contract { + + private final Contract delegate; + + /** + * map key is constructed by ClassFullName + configKey. configKey is constructed by + * {@link feign.Feign#configKey} + */ + public final static Map metadataMap = new HashMap(); + + public SentinelContractHolder(Contract delegate) { + this.delegate = delegate; + } + + @Override + public List parseAndValidatateMetadata(Class targetType) { + List metadatas = delegate.parseAndValidatateMetadata(targetType); + metadatas.forEach(metadata -> metadataMap + .put(targetType.getName() + metadata.configKey(), metadata)); + return metadatas; + } + +} diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelFeign.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelFeign.java new file mode 100644 index 00000000..863c0d4a --- /dev/null +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelFeign.java @@ -0,0 +1,149 @@ +/* + * 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 org.springframework.cloud.alibaba.sentinel.feign; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.util.Map; + +import org.springframework.beans.BeansException; +import org.springframework.cloud.openfeign.FeignContext; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.util.ReflectionUtils; + +import feign.Contract; +import feign.Feign; +import feign.InvocationHandlerFactory; +import feign.Target; +import feign.hystrix.FallbackFactory; +import feign.hystrix.HystrixFeign; + +/** + * {@link Feign.Builder} like {@link HystrixFeign.Builder} + * + * @author Jim + */ +public class SentinelFeign { + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder extends Feign.Builder + implements ApplicationContextAware { + + private Contract contract = new Contract.Default(); + + private ApplicationContext applicationContext; + + private FeignContext feignContext; + + @Override + public Feign.Builder invocationHandlerFactory( + InvocationHandlerFactory invocationHandlerFactory) { + throw new UnsupportedOperationException(); + } + + @Override + public Builder contract(Contract contract) { + this.contract = contract; + return this; + } + + @Override + public Feign build() { + super.invocationHandlerFactory(new InvocationHandlerFactory() { + @Override + public InvocationHandler create(Target target, + Map dispatch) { + // using reflect get fallback and fallbackFactory properties from + // FeignClientFactoryBean because FeignClientFactoryBean is a package + // level class, we can not use it in our package + Object feignClientFactoryBean = Builder.this.applicationContext + .getBean("&" + target.type().getName()); + + Class fallback = (Class) getFieldValue(feignClientFactoryBean, + "fallback"); + Class fallbackFactory = (Class) getFieldValue(feignClientFactoryBean, + "fallbackFactory"); + String name = (String) getFieldValue(feignClientFactoryBean, "name"); + + Object fallbackInstance; + FallbackFactory fallbackFactoryInstance; + // check fallback and fallbackFactory properties + if (void.class != fallback) { + fallbackInstance = getFromContext(name, "fallback", fallback, + target.type()); + return new SentinelInvocationHandler(target, dispatch, + new FallbackFactory.Default(fallbackInstance)); + } + if (void.class != fallbackFactory) { + fallbackFactoryInstance = (FallbackFactory) getFromContext(name, + "fallbackFactory", fallbackFactory, + FallbackFactory.class); + return new SentinelInvocationHandler(target, dispatch, + fallbackFactoryInstance); + } + return new SentinelInvocationHandler(target, dispatch); + } + + private Object getFromContext(String name, String type, + Class fallbackType, Class targetType) { + Object fallbackInstance = feignContext.getInstance(name, + fallbackType); + if (fallbackInstance == null) { + throw new IllegalStateException(String.format( + "No %s instance of type %s found for feign client %s", + type, fallbackType, name)); + } + + if (!targetType.isAssignableFrom(fallbackType)) { + throw new IllegalStateException(String.format( + "Incompatible %s instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s", + type, fallbackType, targetType, name)); + } + return fallbackInstance; + } + }); + + super.contract(new SentinelContractHolder(contract)); + return super.build(); + } + + private Object getFieldValue(Object instance, String fieldName) { + Field field = ReflectionUtils.findField(instance.getClass(), fieldName); + field.setAccessible(true); + try { + return field.get(instance); + } + catch (IllegalAccessException e) { + // ignore + } + return null; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) + throws BeansException { + this.applicationContext = applicationContext; + feignContext = this.applicationContext.getBean(FeignContext.class); + } + } + +} diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelFeignAutoConfiguration.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelFeignAutoConfiguration.java new file mode 100644 index 00000000..1ed20e1b --- /dev/null +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelFeignAutoConfiguration.java @@ -0,0 +1,45 @@ +/* + * 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 org.springframework.cloud.alibaba.sentinel.feign; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; + +import com.alibaba.csp.sentinel.SphU; + +import feign.Feign; + +/** + * @author Jim + */ +@Configuration +@ConditionalOnClass({ SphU.class, Feign.class }) +public class SentinelFeignAutoConfiguration { + + @Bean + @Scope("prototype") + @ConditionalOnMissingBean + @ConditionalOnProperty(name = "feign.sentinel.enabled") + public Feign.Builder feignSentinelBuilder() { + return SentinelFeign.builder(); + } + +} diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelInvocationHandler.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelInvocationHandler.java new file mode 100644 index 00000000..809879cc --- /dev/null +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelInvocationHandler.java @@ -0,0 +1,172 @@ +/* + * 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 org.springframework.cloud.alibaba.sentinel.feign; + +import static feign.Util.checkNotNull; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.LinkedHashMap; +import java.util.Map; + +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.context.ContextUtil; +import com.alibaba.csp.sentinel.slots.block.BlockException; + +import feign.Feign; +import feign.InvocationHandlerFactory.MethodHandler; +import feign.MethodMetadata; +import feign.Target; +import feign.hystrix.FallbackFactory; + +/** + * {@link InvocationHandler} handle invocation that protected by Sentinel + * + * @author Jim + */ +public class SentinelInvocationHandler implements InvocationHandler { + + private final Target target; + private final Map dispatch; + + private FallbackFactory fallbackFactory; + private Map fallbackMethodMap; + + SentinelInvocationHandler(Target target, Map dispatch, + FallbackFactory fallbackFactory) { + this.target = checkNotNull(target, "target"); + this.dispatch = checkNotNull(dispatch, "dispatch"); + this.fallbackFactory = fallbackFactory; + this.fallbackMethodMap = toFallbackMethod(dispatch); + } + + SentinelInvocationHandler(Target target, Map dispatch) { + this.target = checkNotNull(target, "target"); + this.dispatch = checkNotNull(dispatch, "dispatch"); + } + + @Override + public Object invoke(final Object proxy, final Method method, final Object[] args) + throws Throwable { + if ("equals".equals(method.getName())) { + try { + Object otherHandler = args.length > 0 && args[0] != null + ? Proxy.getInvocationHandler(args[0]) + : null; + return equals(otherHandler); + } + catch (IllegalArgumentException e) { + return false; + } + } + else if ("hashCode".equals(method.getName())) { + return hashCode(); + } + else if ("toString".equals(method.getName())) { + return toString(); + } + + Object result; + MethodHandler methodHandler = this.dispatch.get(method); + // only handle by HardCodedTarget + if (target instanceof Target.HardCodedTarget) { + Target.HardCodedTarget hardCodedTarget = (Target.HardCodedTarget) target; + MethodMetadata methodMetadata = SentinelContractHolder.metadataMap + .get(method.getDeclaringClass().getName() + + Feign.configKey(method.getDeclaringClass(), method)); + // resource default is HttpMethod:protocol://url + String resourceName = methodMetadata.template().method().toUpperCase() + ":" + + hardCodedTarget.url() + methodMetadata.template().url(); + Entry entry = null; + try { + ContextUtil.enter(resourceName); + entry = SphU.entry(resourceName, EntryType.OUT, 1, args); + result = methodHandler.invoke(args); + } + catch (Throwable ex) { + // fallback handle + if (!BlockException.isBlockException(ex)) { + Tracer.trace(ex); + } + if (fallbackFactory != null) { + try { + Object fallbackResult = fallbackMethodMap.get(method) + .invoke(fallbackFactory.create(ex), args); + return fallbackResult; + } + catch (IllegalAccessException e) { + // shouldn't happen as method is public due to being an interface + throw new AssertionError(e); + } + catch (InvocationTargetException e) { + throw new AssertionError(e.getCause()); + } + } + else { + // throw exception if fallbackFactory is null + throw ex; + } + } + finally { + if (entry != null) { + entry.exit(); + } + ContextUtil.exit(); + } + } + else { + // other target type using default strategy + result = methodHandler.invoke(args); + } + + return result; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof SentinelInvocationHandler) { + SentinelInvocationHandler other = (SentinelInvocationHandler) obj; + return target.equals(other.target); + } + return false; + } + + @Override + public int hashCode() { + return target.hashCode(); + } + + @Override + public String toString() { + return target.toString(); + } + + static Map toFallbackMethod(Map dispatch) { + Map result = new LinkedHashMap<>(); + for (Method method : dispatch.keySet()) { + method.setAccessible(true); + result.put(method, method); + } + return result; + } + +} diff --git a/spring-cloud-alibaba-sentinel/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-alibaba-sentinel/src/main/resources/META-INF/additional-spring-configuration-metadata.json index b3e40d08..88fe9e5a 100644 --- a/spring-cloud-alibaba-sentinel/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-cloud-alibaba-sentinel/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -12,12 +12,6 @@ "defaultValue": false, "description": "earlier initialize heart-beat when the spring container starts when the transport dependency is on classpath, the configuration is effective." }, - { - "name": "spring.cloud.sentinel.charset", - "type": "java.lang.String", - "defaultValue": "UTF-8", - "description": "charset when sentinel write or search metric file." - }, { "name": "spring.cloud.sentinel.transport.port", "type": "java.lang.String", @@ -40,6 +34,12 @@ "defaultValue": "Integer.MIN_VALUE", "description": "sentinel filter chain order, will be set to FilterRegistrationBean." }, + { + "name": "spring.cloud.sentinel.metric.charset", + "type": "java.lang.String", + "defaultValue": "UTF-8", + "description": "charset when sentinel write or search metric file." + }, { "name": "spring.cloud.sentinel.metric.fileSingleSize", "type": "java.lang.String", @@ -50,6 +50,17 @@ "type": "java.lang.String", "description": "the total metric file count." }, + { + "name": "spring.cloud.sentinel.log.dir", + "type": "java.lang.String", + "description": "log base directory." + }, + { + "name": "spring.cloud.sentinel.log.switch-pid", + "type": "java.lang.Boolean", + "defaultValue": false, + "description": "log file should with pid." + }, { "name": "spring.cloud.sentinel.servlet.blockPage", "type": "java.lang.String", diff --git a/spring-cloud-alibaba-sentinel/src/main/resources/META-INF/spring.factories b/spring-cloud-alibaba-sentinel/src/main/resources/META-INF/spring.factories index ae146be4..702bb5bb 100644 --- a/spring-cloud-alibaba-sentinel/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-alibaba-sentinel/src/main/resources/META-INF/spring.factories @@ -1,4 +1,5 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.cloud.alibaba.sentinel.SentinelWebAutoConfiguration,\ org.springframework.cloud.alibaba.sentinel.endpoint.SentinelEndpointAutoConfiguration,\ -org.springframework.cloud.alibaba.sentinel.custom.SentinelAutoConfiguration +org.springframework.cloud.alibaba.sentinel.custom.SentinelAutoConfiguration,\ +org.springframework.cloud.alibaba.sentinel.feign.SentinelFeignAutoConfiguration diff --git a/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/SentinelAutoConfigurationTests.java b/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/SentinelAutoConfigurationTests.java index 647fde93..1ba2c170 100644 --- a/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/SentinelAutoConfigurationTests.java +++ b/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/SentinelAutoConfigurationTests.java @@ -22,7 +22,7 @@ import org.junit.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.boot.web.servlet.FilterRegistrationBean; -import org.springframework.cloud.alibaba.sentinel.annotation.SentinelProtect; +import org.springframework.cloud.alibaba.sentinel.annotation.SentinelRestTemplate; import org.springframework.cloud.alibaba.sentinel.custom.SentinelAutoConfiguration; import org.springframework.cloud.alibaba.sentinel.custom.SentinelBeanPostProcessor; import org.springframework.cloud.alibaba.sentinel.custom.SentinelProtectInterceptor; @@ -92,13 +92,13 @@ public class SentinelAutoConfigurationTests { static class SentinelTestConfiguration { @Bean - @SentinelProtect + @SentinelRestTemplate RestTemplate restTemplate() { return new RestTemplate(); } @Bean - @SentinelProtect(blockHandlerClass = ExceptionUtil.class, blockHandler = "handleException") + @SentinelRestTemplate(blockHandlerClass = ExceptionUtil.class, blockHandler = "handleException") RestTemplate restTemplateWithBlockClass() { return new RestTemplate(); } diff --git a/spring-cloud-starter-alibaba/pom.xml b/spring-cloud-starter-alibaba/pom.xml index 8726971a..404431f3 100644 --- a/spring-cloud-starter-alibaba/pom.xml +++ b/spring-cloud-starter-alibaba/pom.xml @@ -13,6 +13,7 @@ Spring Cloud Alibaba Starters spring-cloud-starter-alibaba-nacos-config + spring-cloud-starter-alibaba-nacos-config-server spring-cloud-starter-alibaba-nacos-discovery spring-cloud-starter-alibaba-sentinel spring-cloud-starter-stream-rocketmq diff --git a/spring-cloud-starter-alibaba/spring-cloud-starter-alibaba-nacos-config-server/pom.xml b/spring-cloud-starter-alibaba/spring-cloud-starter-alibaba-nacos-config-server/pom.xml new file mode 100644 index 00000000..f5dae039 --- /dev/null +++ b/spring-cloud-starter-alibaba/spring-cloud-starter-alibaba-nacos-config-server/pom.xml @@ -0,0 +1,20 @@ + + 4.0.0 + + + org.springframework.cloud + spring-cloud-starter-alibaba + 0.2.1.BUILD-SNAPSHOT + + spring-cloud-starter-alibaba-nacos-config-server + Spring Cloud Starter Alibaba Nacos Config Server + + + + org.springframework.cloud + spring-cloud-alibaba-nacos-config + + + +