博彩公司排名-澳门博彩十大公司排名-澳门正规博彩十大网址_梅子先生 关注Spring Cloud、Docker、微服务架构。 2019-09-01T15:45:30.986Z / 周立 Hexo Spring Cloud Sleuth使用ELK收集&分析日志 /spring-cloud/sleuth-elk/ 2019-09-01T15:55:39.000Z 2019-09-01T15:45:30.986Z

TIPS

本文基于Spring Cloud Greenwich SR2,理论兼容Spring Cloud所有版本。

应用整合

  • 加依赖:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
    </dependency>
    <dependency>
    <groupId>net.logstash.logback</groupId>
    <artifactId>logstash-logback-encoder</artifactId>
    <version>6.1</version>
    </dependency>

    注意, logstash-logback-encoder版本务必和Logback兼容,否则会导致应用启动不起来,而且不会打印任何日志!可前往 https://github.com/logstash/logstash-logback-encoder 查看和Logback的兼容性。

  • resources 目录下创建配置文件:logback-spring.xml ,内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>

    <springProperty scope="context" name="springAppName" source="spring.application.name"/>
    <!-- Example for logging into the build folder of your project -->
    <property name="LOG_FILE" value="/Users/reno/Desktop/未命名文件夹/elk/logs/${springAppName}"/>

    <!-- You can override this to have a custom pattern -->
    <property name="CONSOLE_LOG_PATTERN"
    value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>

    <!-- Appender to log to console -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
    <!-- Minimum logging level to be presented in the console logs-->
    <level>DEBUG</level>
    </filter>
    <encoder>
    <pattern>${CONSOLE_LOG_PATTERN}</pattern>
    <charset>utf8</charset>
    </encoder>
    </appender>

    <!-- Appender to log to file -->
    <appender name="flatfile" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${LOG_FILE}</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    <fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.gz</fileNamePattern>
    <maxHistory>7</maxHistory>
    </rollingPolicy>
    <encoder>
    <pattern>${CONSOLE_LOG_PATTERN}</pattern>
    <charset>utf8</charset>
    </encoder>
    </appender>

    <!-- Appender to log to file in a JSON format -->
    <appender name="logstash" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${LOG_FILE}.json</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    <fileNamePattern>${LOG_FILE}.json.%d{yyyy-MM-dd}.gz</fileNamePattern>
    <maxHistory>7</maxHistory>
    </rollingPolicy>
    <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
    <providers>
    <timestamp>
    <timeZone>UTC</timeZone>
    </timestamp>
    <pattern>
    <pattern>
    {
    "severity": "%level",
    "service": "${springAppName:-}",
    "trace": "%X{X-B3-TraceId:-}",
    "span": "%X{X-B3-SpanId:-}",
    "parent": "%X{X-B3-ParentSpanId:-}",
    "exportable": "%X{X-Span-Export:-}",
    "pid": "${PID:-}",
    "thread": "%thread",
    "class": "%logger{40}",
    "rest": "%message"
    }
    </pattern>
    </pattern>
    </providers>
    </encoder>
    </appender>

    <root level="INFO">
    <appender-ref ref="console"/>
    <!-- uncomment this to have also JSON logs -->
    <appender-ref ref="logstash"/>
    <!--<appender-ref ref="flatfile"/>-->
    </root>
    </configuration>
  • 新建 bootstrap.yml ,并将application.yml 中的以下属性移到bootstrap.yml 中。

    1
    2
    3
    spring:
    application:
    name: user-center

    由于上面的 logback-spring.xml 含有变量(例如 springAppName ),故而 spring.application.name 属性必须设置在 bootstrap.yml 文件中,否则,logback-spring.xml 将无法正确读取属性。

测试

  • 启动应用

  • 日志会打印到 /Users/reno/Desktop/未命名文件夹/elk/logs/目录中 ,并且文件名称为 user-center.json ,内容类似如下:

    1
    2
    3
    4
    5
    {"@timestamp":"2019-08-29T02:38:42.468Z","severity":"DEBUG","service":"microservice-provider-user","trace":"5cf9479e966fb5ec","span":"5cf9479e966fb5ec","parent":"","exportable":"false","pid":"13144","thread":"http-nio-8000-exec-1","class":"o.s.w.s.m.m.a.RequestResponseBodyMethodProcessor","rest":"Using 'application/json;q=0.8', given [text/html, application/xhtml+xml, image/webp, image/apng, application/signed-exchange;v=b3, application/xml;q=0.9, */*;q=0.8] and supported [application/json, application/*+json, application/json, application/*+json]"}
    {"@timestamp":"2019-08-29T02:38:42.469Z","severity":"DEBUG","service":"microservice-provider-user","trace":"5cf9479e966fb5ec","span":"5cf9479e966fb5ec","parent":"","exportable":"false","pid":"13144","thread":"http-nio-8000-exec-1","class":"o.s.w.s.m.m.a.RequestResponseBodyMethodProcessor","rest":"Writing [Optional[User(id=1, username=account1, name=张三, age=20, balance=100.00)]]"}
    {"@timestamp":"2019-08-29T02:38:42.491Z","severity":"DEBUG","service":"microservice-provider-user","trace":"5cf9479e966fb5ec","span":"5cf9479e966fb5ec","parent":"","exportable":"false","pid":"13144","thread":"http-nio-8000-exec-1","class":"o.s.o.j.s.OpenEntityManagerInViewInterceptor","rest":"Closing JPA EntityManager in OpenEntityManagerInViewInterceptor"}
    {"@timestamp":"2019-08-29T02:38:42.492Z","severity":"DEBUG","service":"microservice-provider-user","trace":"5cf9479e966fb5ec","span":"5cf9479e966fb5ec","parent":"","exportable":"false","pid":"13144","thread":"http-nio-8000-exec-1","class":"o.s.web.servlet.DispatcherServlet","rest":"Completed 200 OK"}
    {"@timestamp":"2019-08-29T02:38:58.141Z","severity":"ERROR","service":"microservice-provider-user","trace":"","span":"","parent":"","exportable":"","pid":"13144","thread":"ThreadPoolTaskScheduler-1","class":"o.s.c.alibaba.nacos.discovery.NacosWatch","rest":"Error watching Nacos Service change"}

    下面,只需要让Logstash收集到这个JSON文件,就可以在Kibana上检索日志啦!

ELK搭建

简单起见,本文使用Docker搭建ELK;其他搭建方式,请看官自行百度,比较简单,但很耗时。

  • 创建 docker-compose.yml 文件,内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    version: '3'
    services:
    elasticsearch:
    image: elasticsearch:7.3.1
    environment:
    discovery.type: single-node
    ports:
    - "9200:9200"
    - "9300:9300"
    logstash:
    image: logstash:7.3.1
    command: logstash -f /etc/logstash/conf.d/logstash.conf
    volumes:
    # 挂载logstash配置文件
    - ./config:/etc/logstash/conf.d
    - /Users/reno/Desktop/未命名文件夹/elk/logs/:/opt/build/
    ports:
    - "5000:5000"
    kibana:
    image: kibana:7.3.1
    environment:
    - ELASTICSEARCH_URL=http://elasticsearch:9200
    ports:
    - "5601:5601"

    需要注意,上面的 /Users/reno/Desktop/未命名文件夹/elk/logs/ 需要改成你应用的打印路径。

  • 在docker-compose.yml文件所在目录创建 config/logstash.conf ,内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    input {
    file {
    codec => json
    path => "/opt/build/*.json" # 改成你项目打印的json日志文件。
    }
    }
    filter {
    grok {
    match => { "message" => "%{TIMESTAMP_ISO8601:timestamp}s+%{LOGLEVEL:severity}s+[%{DATA:service},%{DATA:trace},%{DATA:span},%{DATA:exportable}]s+%{DATA:pid}s+---s+[%{DATA:thread}]s+%{DATA:class}s+:s+%{GREEDYDATA:rest}" }
    }
    }
    output {
    elasticsearch {
    hosts => "elasticsearch:9200" # 改成你的Elasticsearch地址
    }
    }
  • 启动ELK

    1
    docker-compose up

测试Sleuth & ELK

  • 访问你微服务的API,让它生成一些日志(如果产生日志比较少,可将 org.springframework 包的日志级别设为 debug

  • 访问 http://localhost:5601 (Kibana地址),可看到类似如下的界面,按照如图配置Kibana。

  • 图片描述

  • 图片描述

  • 输入条件,即可分析日志:

    图片描述

原理分析

原理比较简单:

  • 让Sleuth打印JSON格式的日志;
  • 然后在Logstash的配置文件中,配置grok语法,解析并收集JSON格式的日志,并存储到Elasticsearch中去;
  • Kibana可视化分析日志。
]]>
<blockquote> <p><strong>TIPS</strong></p> <p>本文基于Spring Cloud Greenwich SR2,理论兼容Spring Cloud所有版本。</p> </blockquote> <h2 id="应用整合"><a href="#
手把手使用SonarQube分析、改善项目代码质量 /other/sonar/ 2019-08-29T15:54:00.000Z 2019-08-31T12:58:24.775Z

TIPS

本文基于SonarQube 7.9.1,理论支持6.0及更高版本。

SonarQube是一个开源的代码质量管理系统,可用来快速定位代码中的Bug、漏洞以及不优雅的代码。它支持几乎所有的常见编程语言,例如Java、JavaScript、TypeScript、Kotlin、Ruby、Go, Scala等。并且还有插件机制,利用插件,可以让SonarQube更加强大,例如可以整合Findbugs、PMD、Checkstyle等。可以说,SonarQube是一款提升项目代码质量必备的根据。

本文手把手搭建、使用SonarQube。

下载

前往 https://www.sonarqube.org/downloads/ ,按照如图说明下载即可。建议下载 LTS 版本,以便获得长期的维护与支持。

Sonar官方网站

系统需求

  • X64的操作系统
  • JDK(对于7.9.x,那么需要JDK 11或更高版;对于6.x - 7.8.x,需要JDK 8或更高版本)
  • 2G内存

其他需求详见:https://docs.sonarqube.org/7.9/requirements/requirements/

TIPS

  • 《其他需求》建议大家参照一下,里面探讨如何修改Linux文件描述符限制等说明;
  • 上面贴的是是7.9版的链接,如果你使用的是其他版本,只需将版本名称改掉即可,例如改为7.8即可查看7.8.x的需求。

安装与启动

  • 解压压缩包

  • 将目录切换到SonarQube的 /bin 目录,可看到类似如下的目录结构:

    1
    2
    3
    4
    5
    ├── bin
    │ ├── jsw-license
    │ ├── linux-x86-64
    │ ├── macosx-universal-64
    │ └── windows-x86-64
  • 根据你的操作系统,切换到响应目录。例如,您的机器是macOS ,则可切换到 macosx-universal-64 目录。

  • 执行如下命令即可启动SonarQube。

    1
    ./sonar.sh start

    当然,该shell还有其他命令,可输入 ./sonar.sh --help 或者 ./sonar.sh 查阅。

  • 稍等片刻,访问 http://localhost:9000/ 即可看到类似如下的界面,说明安装成功。

    Sonar首页

  • 停止SonarQube,只需执行 ./sonar.sh stop 即可。
  • 如需重启,只需执行 ./sonar.sh restart 即可。

管理员登录

访问:http://localhost:9000

账号:admin

密码:admin

生产环境可用

默认情况下,SonarQube使用的是H2数据库,这是一款非常流行的嵌入式数据库。但生产环境中,SonarQube并不建议使用H2。SonarQube支持多种数据库,例如Qracle、PostgreSQL、SQL Server等。下面,我们以PostgreSQL为例,让SonarQube使用PostgreSQL存储数据。

TIPS

支持的数据库及数据库版本请前往这篇文档查看,避免SonarQube不支持你的数据库版本以及注意点。

https://docs.sonarqube.org/7.9/requirements/requirements/

举个例子:SonarQube 7.9要求使用PostgreSQL 9.3-9.6或者PostgreSQL 10,并且必须配置使用UTF-8

搭建PostgreSQL

简单起见,我用Docker搭建PostgreSQL。

1
2
3
4
5
6
7
8
9
10
11
12
version: '3.1'

services:
postgres:
image: postgres:10
restart: always
environment:
- POSTGRES_USER=itmuch
- POSTGRES_PASSWORD=itmuch
- POSTGRES_DB=sonar
ports:
- "5432:5432"

修改SonarQube配置

  • 修改配置文件:$SONARQUBE_HOME/conf/sonar.properties

  • 找到类似如下的内容:

    1
    2
    3
    #----- PostgreSQL 9.3 or greater
    # By default the schema named "public" is used. It can be overridden with the parameter "currentSchema".
    #sonar.jdbc.url=jdbc:postgresql://localhost/sonarqube?currentSchema=my_schema

    在这行下面,添加如下内容:

    1
    2
    3
    sonar.jdbc.url=jdbc:postgresql://localhost/sonar?currentSchema=public
    sonar.jdbc.username=itmuch
    sonar.jdbc.password=itmuch

    这里,数据库地址、账号、密码根据你的需求修改。

  • 执行 ./sonar.sh restart ,重启SonarQube。观察PostgreSQL,可以发现,此时SonarQube会自动在PostgreSQL数据库中建表并插入初始化数据。

  • 类似的方式,你也可以为你的SonarQube配置其他数据库。

整合Maven

方法一:全局配置

  • 在Maven的全局配置文件: $MAVEN_PATH/conf/settings.xml (也可能是.m2/settings.xml 看你是怎么配置Maven的)中添加如下内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <profile>
    <id>sonar</id>
    <activation>
    <activeByDefault>true</activeByDefault>
    </activation>
    <properties>
    <sonar.jdbc.url>jdbc:postgresql://localhost/sonar?currentSchema=public</sonar.jdbc.url>
    <sonar.jdbc.driver>org.postgresql.Driver</sonar.jdbc.driver>
    <sonar.jdbc.username>itmuch</sonar.jdbc.username>
    <sonar.jdbc.password>itmuch</sonar.jdbc.password>
    <sonar.host.url>http://127.0.0.1:9000</sonar.host.url>
    </properties>
    </profile>
  • 到Maven项目的根目录执行如下命令,即可使用SonarQube分析项目:

    1
    mvn sonar:sonar -Dsonar.java.binaries=target/sonar

    等待片刻后,项目构建成功:

    1
    2
    3
    4
    5
    6
    7
    [INFO] Spring Cloud YES ................................... SUCCESS [ 12.431 s]
    [INFO] turbine-stream-server .............................. SKIPPED
    [INFO] zuul-server ........................................ SKIPPED
    [INFO] hystrix-dashboard .................................. SKIPPED
    [INFO] commons ............................................ SKIPPED
    [INFO] ms-content-sample-mybatis .......................... SKIPPED
    [INFO] ms-consumer-sample ................................. SKIPPED
  • 此时,再次访问 http://localhost:9000 ,即可看到类似如下的界面:

    分析结果

    如右上角所示,此时可以看到SonarQube已经为我们分析了一个项目,该项目有1个Bug、2个脆弱点、31个代码味道问题。点击项目名称(图中的 Spring Cloud YES)即可看到详情,可以根据SonarQube给我们的提示进行修正、重构。

方法二:直接命令行控制

  • 右上角头像 - My Account - Security页中,在 Generate New Token 中填入你的Token名称,并点击Generate 按钮。

  • 点击按钮后,将会看到生成的Token,例如 62b615f477557f98bc60b396c2b4ca2793afbdea

  • 使用如下命令,即可使用Sonar分析项目。

    1
    2
    3
    4
    mvn sonar:sonar 
    -Dsonar.host.url=http://localhost:9000
    -Dsonar.login=62b615f477557f98bc60b396c2b4ca2793afbdea
    -Dsonar.java.binaries=target/sonar

插件安装

SonarQube有一个强大的插件机制。以安装汉化插件为例——

  • 按照图示进行操作:

    安装插件

  • 点击 Install 按钮后,将会弹出重启SonarQube的提示,点击即可重启。重启后,可看到类似如下的界面

    汉化

  • 类似的方式,也可为SonarQube安装其他插件。

]]>
<blockquote> <p><strong>TIPS</strong></p> <p>本文基于SonarQube 7.9.1,理论支持6.0及更高版本。</p> </blockquote> <p>SonarQube是一个开源的代码质量管理系统,可用来快速定位代码中的Bug、漏
分享:如何生成漂亮的静态文档说明页 /other/doc-generate/ 2019-08-26T15:09:49.000Z 2019-08-31T12:55:04.882Z 最近经常被问 /https://t.itmuch.com/doc.html 文档页是怎么制作的,考虑到步骤略复杂,写篇手记总结下吧。

TIPS

/https://t.itmuch.com/doc.html 是个人在慕课网视频《 面向未来微服务:Spring Cloud Alibaba从入门到进阶 》的实战项目配套文档。

效果

效果

总体步骤

  • 整合Swagger,生成Swagger描述端点 /v2/api-docs
  • 使用 swagger2markup-maven-plugin ,将 /v2/api-docs 生成ASCIIDOC文件;
  • 使用 asciidoctor-maven-plugin ,将ASCIIDOC文件转换成HTML;
  • 部署

整合Swagger

TIPS

Swagger的使用非常简单,本文不展开探讨了,各位看官自行百度一下用法吧。

常用注解:

  • @Api
  • @ApiOperation
  • @ApiModel
  • @ApiModelProperty
  • 加依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    <!-- swagger -->
    <!-- 之所以要排除,是因为如果不排除会报NumberFormatException的警告。 -->
    <!-- 参考:https://github.com/springfox/springfox/issues/2265-->
    <dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
    <exclusions>
    <exclusion>
    <groupId>io.swagger</groupId>
    <artifactId>swagger-annotations</artifactId>
    </exclusion>
    <exclusion>
    <groupId>io.swagger</groupId>
    <artifactId>swagger-models</artifactId>
    </exclusion>
    </exclusions>
    </dependency>
    <dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.9.2</version>
    </dependency>
    <dependency>
    <groupId>io.swagger</groupId>
    <artifactId>swagger-annotations</artifactId>
    <version>1.5.21</version>
    </dependency>
    <dependency>
    <groupId>io.swagger</groupId>
    <artifactId>swagger-models</artifactId>
    <version>1.5.21</version>
    </dependency>
  • 配置Swagger(按照自己的需要配置,下面的配置代码仅供参考)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    /**
    * @author itmuch.com
    */
    @Configuration
    @EnableSwagger2
    public class SwaggerConfiguration {
    /**
    * swagger 信息
    *
    * @return 页面信息
    */
    private ApiInfo apiInfo() {
    return new ApiInfoBuilder()
    .title("ITMuch API")
    .description("ITMuch API")
    .termsOfServiceUrl("")
    .version("1.0.0")
    .contact(new Contact("", "", "")).build();
    }

    @Bean
    public Docket customImplementation() {
    return new Docket(DocumentationType.SWAGGER_2)
    .select()
    .apis(RequestHandlerSelectors.basePackage("com.itmuch"))
    .paths(PathSelectors.any())
    .build()
    .apiInfo(this.apiInfo());
    //.globalOperationParameters(parameters);
    }
    }
  • 为接口Swagger注解

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    @RestController
    @RequestMapping("/notices")
    @RequiredArgsConstructor(onConstructor = @__(@Autowired))
    @Api(tags = "公告相关接口", description = "公告相关接口")
    public class NoticeController {
    /**
    * 查询最新的一条公告
    *
    * @return 公告列表
    */
    @GetMapping("/newest")
    @ApiOperation(value = "查询最新的一条公告", notes = "用于:公告")
    public Notice findNewest() {
    return new Notice();
    }
    }


    @AllArgsConstructor
    @NoArgsConstructor
    @Builder
    @Data
    @ApiModel("公告")
    public class Notice {
    /**
    * ID
    */
    @ApiModelProperty("id")
    private Integer id;

    /**
    * 公告内容
    */
    @ApiModelProperty("公告内容")
    private String content;
    ...
    }
  • 这样,应用启动完成后,就会有一个 /v2/api-docs 端点,描述了你的API的信息。

生成ASCIIDOC

在pom.xml中添加如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<build>
<plugins>
<plugin>
<groupId>io.github.swagger2markup</groupId>
<artifactId>swagger2markup-maven-plugin</artifactId>
<version>1.3.1</version>
<configuration>
<!-- api-docs访问url -->
<swaggerInput>http://localhost:8080/v2/api-docs</swaggerInput>
<!-- 生成为单个文档,输出路径 -->
<outputFile>src/docs/asciidoc/generated/all</outputFile>
<config>
<!-- ascii格式文档 -->
<swagger2markup.markupLanguage>ASCIIDOC</swagger2markup.markupLanguage>
<swagger2markup.pathsGroupedBy>TAGS</swagger2markup.pathsGroupedBy>
</config>
</configuration>
</plugin>
...

swagger2markup-maven-plugin 插件的作用是读取 http://localhost:8080/v2/api-docs 的信息,生成ASCIIDOC文档。当然你也可以生成其他格式,比如Markdown等等。

这款插件还有很多使用姿势,详见 https://github.com/Swagger2Markup/swagger2markup-maven-plugin

生成HTML

下面,只需要将ASCIIDOC转换成html就OK了,在pom.xml中添加如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<build>
<plugins>
<plugin>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
<version>1.5.6</version>
<configuration>
<!-- asciidoc文档输入路径 -->
<sourceDirectory>src/docs/asciidoc/generated</sourceDirectory>
<!-- html文档输出路径 -->
<outputDirectory>src/docs/asciidoc/html</outputDirectory>
<backend>html</backend>
<sourceHighlighter>coderay</sourceHighlighter>
<!-- html文档格式参数 -->
<attributes>
<doctype>book</doctype>
<toc>left</toc>
<toclevels>3</toclevels>
<numbered></numbered>
<hardbreaks></hardbreaks>
<sectlinks></sectlinks>
<sectanchors></sectanchors>
</attributes>
</configuration>
</plugin>

asciidoctor-maven-plugin 插件同样也有很多姿势,详见:https://github.com/asciidoctor/asciidoctor-maven-plugin

生成的文件在 src/docs/asciidoc/html (看你插件上面的配置哈),然后你就可以弄个NGINX部署了。

使用

  • 启动应用
  • 执行 mvn swagger2markup:convertSwagger2markup 生成ASCIIDOC
  • 执行 mvn asciidoctor:process-asciidoc 生成html
]]>
<p>最近经常被问 <a href="/https://t.itmuch.com/doc.html" target="_blank" rel="noopener">/https://t.itmuch.com/doc.html</a> 文档页是怎么制作的,考虑到步骤略复杂,写篇手记总结
解决Spring Cloud Alibaba/Spring Cloud整合Zipkin之后的报错问题 /spring-cloud-alibaba/spring-cloud-alibaba-zipkin-nacos-exception/ 2019-08-25T05:51:49.000Z 2019-08-24T16:00:17.930Z

TIPS

  • 本文服务发现组件以Nacos为例。
  • 本文基于 Spring Cloud Greenwich SR1

问题复现

依赖

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>

配置

1
2
3
4
5
6
spring:
zipkin:
base-url: http://localhost:9411/
sleuth:
sampler:
probability: 1.0

结果

只要你的API被调用,应用就会疯狂报类似如下的异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2019-08-24 23:10:25.330 ERROR [user-center,,,] 48628 --- [.naming.updater] com.alibaba.nacos.client.naming          : [NA] failed to update serviceName: DEFAULT_GROUP@@localhost

java.lang.IllegalStateException: failed to req API:/nacos/v1/ns/instance/list after all servers([localhost:8848]) tried: failed to req API:http://localhost:8848/nacos/v1/ns/instance/list. code:404 msg: service not found: DEFAULT_GROUP@@localhost
at com.alibaba.nacos.client.naming.net.NamingProxy.reqAPI(NamingProxy.java:380) ~[nacos-client-1.0.0.jar:na]
at com.alibaba.nacos.client.naming.net.NamingProxy.reqAPI(NamingProxy.java:304) ~[nacos-client-1.0.0.jar:na]
at com.alibaba.nacos.client.naming.net.NamingProxy.queryList(NamingProxy.java:217) ~[nacos-client-1.0.0.jar:na]
at com.alibaba.nacos.client.naming.core.HostReactor.updateServiceNow(HostReactor.java:273) ~[nacos-client-1.0.0.jar:na]
at com.alibaba.nacos.client.naming.core.HostReactor$UpdateTask.run(HostReactor.java:318) [nacos-client-1.0.0.jar:na]
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [na:1.8.0_201]
at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266) [na:1.8.0_201]
at java.util.concurrent.FutureTask.run(FutureTask.java) [na:1.8.0_201]
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) [na:1.8.0_201]
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) [na:1.8.0_201]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_201]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_201]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_201]

原因:

Spring Cloud把 http://localhost:9411/ 当作了服务发现组件里面的服务名称;于是,Nacos Client尝试从Nacos Server寻找一个名为 localhost:9411 的服务…这个服务根本不存在啊,于是就疯狂报异常(因为Nacos Client本地定时任务,刷新本地服务发现缓存)

解决方案

知道原因之后,要想解决很简单:

  • 让Spring Cloud 正确识别 http://localhost:9411/ ,当成一个URL,而不要当做服务名。
  • 把Zipkin Server注册到Nacos

谈谈Zipkin Server注册到服务发现组件

Zipkin Server 官方并不支持注册到服务发现组件!!!!在 https://github.com/openzipkin/zipkin/issues/2540 里面,官方人员解答得很清楚了:

@nkorange thank you for reaching out. We have had several users ask about service discovery with Zipkin Server over the years (see #1870) and less ask about dynamic/external configuration. We don’t currently support any service discovery as a first-class feature. There is a workaround to use Eureka mentioned in #1870. So unless a user is using that, or is building a custom Zipkin Server (which we don’t officially support), there should be no service discovery.

If you want to come chat with us on Gitter (https://gitter.im/openzipkin/zipkin) about next steps, please do.

详见 We don't currently support any service discovery as a first-class feature.

如果你想要注册到服务发现组件,那么可以参考 https://github.com/openzipkin/zipkin/issues/1870 说明自己改造Zipkin Server让其注册到服务发现组件,但这样做不会得到任何官方的技术支持!!

所以,将Zipkin Server注册到Nacos或者其他服务发现组件,不是最优解。我们的解决方案就演变成了:让Spring Cloud 正确识别 http://localhost:9411/ ,当成一个URL,而不要当做服务名。这一种解决方案。

配置走你!

1
2
3
4
spring:
zipkin:
base-url: http://localhost:9411/
discovery-client-enabled: false

从这个配置的注释( 代码见 org.springframework.cloud.sleuth.zipkin2.ZipkinProperties#discoveryClientEnabled )来看,只要设置成false,那么就会把 http://localhost:9411/ 当成一个URL,而不是服务名称了。

你以为已经牛逼了,然而当你去测试的时候,发现 然并卵 ,一点效果都没有。

Spring Cloud Sleuth Bug、解决方案与Pull Request

问题代码在这里:org.springframework.cloud.sleuth.zipkin2.sender.ZipkinRestTemplateSenderConfiguration.DiscoveryClientZipkinUrlExtractorConfiguration.ZipkinClientNoOpConfiguration ,里面是这么玩的:

1
2
3
4
5
@Configuration
@ConditionalOnProperty(value = "spring.zipkin.discoveryClientEnabled", havingValue = "false")
static class ZipkinClientNoOpConfiguration {
...
}

当你看到这个代码的时候,就应该知道解决方案啦!那就是你得把配置修改为:

1
2
3
4
spring:
zipkin:
base-url: http://localhost:9411/
discoveryClientEnabled: false

就可以了!

这其实是Spring Cloud Sleuth子项目 spring-cloud-sleuth-zipkin 的一个Bug!

  • 相关的Issue在:https://github.com/spring-cloud/spring-cloud-sleuth/issues/1376
  • 解决的Pul Request在:https://github.com/spring-cloud/spring-cloud-sleuth/pull/1379代码已经合并了,在 Spring Cloud Greenwich SR3 版本中会修正!

简单总结一下:

  • 如果你使用的是Greenwich SR3之前的版本,务必使用 spring.zipkin.discoveryClientEnabled = false ,否则配置不生效!!
  • 如果你使用的是Greenwich SR3及更高版本,可使用 discovery-client-enabled 或者 discoveryClientEnabled

为什么Zipkin Server不支持服务发现,Spring Cloud还弄个服务发现的配置?

我暂时没有找到Spring Cloud这样做的意图。我的猜想:是因为早期Spring Cloud是使用自己编写代码实现Zipkin Server的(从Finchley开始,改成了直接用Zipkin官方现成的jar包启动),这种方式可以注册到你想要的服务发现组件上(因为本质上,Zipkin Server就是个基于Spring Boot的应用嘛)。

一点吐槽

个人觉得Spring Cloud这部分的设计不太优雅,如果让我来设计的话,我应该会这么设计:

URL模式:

1
2
3
spring:
zipkin:
base-url: http://localhost:9411/

服务名称的模式:

1
2
3
spring:
zipkin:
base-url: lb://zipkin-server/

这样带来的好处:

  • scheme(http://lb:// )就可以区分出是具体URL还是微服务名称了,无需配置 discoveryClientEnabled
  • lb:// 协议和Spring Cloud Gateway形成了呼应,学习成本也比较低,使用体验更加一致

不过工作比较忙,只有创意,暂时没有时间提交PR…哈哈,所以…吐槽完还是得继续忍着啊…

]]>
<blockquote> <p><strong>TIPS</strong></p> <ul> <li>本文服务发现组件以Nacos为例。</li> <li>本文基于 <code>Spring Cloud Greenwich SR1</code> </li> </ul> </blo
Zipkin Server下载与搭建 /spring-cloud/zipkin-server-install/ 2019-08-23T16:54:49.000Z 2019-08-24T13:46:14.362Z

TIPS

  • 本文基于Zipkin Server 2.12.9编写,理论支持Zipkin 2.0及更高版本。
  • Zipkin Server的API兼容性(微服务通过集成reporter模块,从而Zipkin Server通信)非常好,对于Spring Cloud Greenwich,Zipkin Server只需安装2.x即可。

方式1:使用Zipkin官方的Shell下载

TIPS

如下命令可下载最新版本。

1
curl -sSL https://zipkin.io/quickstart.sh | bash -s

下载下来的文件名为 zipin.jar

方式2:到Maven中央仓库下载

TIPS

如下地址可下载最新版本。

访问如下地址即可:

1
https://search.maven.org/remote_content?g=io.zipkin.java&a=zipkin-server&v=LATEST&c=exec

下载下来的文件名为 zipkin-server-2.12.9-exec.jar

方式3:使用百度盘地址下载

提供2.12.9版本。

1
链接:https://pan.baidu.com/s/1HXjzNDpzin6fXGrZPyQeWQ  密码:aon2

启动Zipkin Server

  • 使用如下命令,即可启动Zipkin Server

    1
    java -jar 你的jar包
  • 访问http://localhost:9411 即可看到Zipkin Server的首页。

    zipkin-server

]]>
<blockquote> <p><strong>TIPS</strong></p> <ul> <li>本文基于Zipkin Server 2.12.9编写,理论支持Zipkin 2.0及更高版本。</li> <li>Zipkin Server的<strong>API兼容性(微服务
JWT操作工具类分享 /other/jwt-util/ 2019-08-11T17:09:49.000Z 2019-08-12T06:48:55.493Z 分享一下个人操作 JWT 的工具类。基于 jjwt 库,这是一个Java圈子最流行的 JWT 操作库。

TIPS

  • 加依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.10.7</version>
    </dependency>
    <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.10.7</version>
    <scope>runtime</scope>
    </dependency>
    <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.10.7</version>
    <scope>runtime</scope>
    </dependency>
  • 工具类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    @Slf4j
    @RequiredArgsConstructor
    @SuppressWarnings("WeakerAccess")
    @Component
    public class JwtOperator {
    /**
    * 秘钥
    * - 默认aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrsssttt
    */
    @Value("${secret:aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrsssttt}")
    private String secret;
    /**
    * 有效期,单位秒
    * - 默认2周
    */
    @Value("${expire-time-in-second:1209600}")
    private Long expirationTimeInSecond;

    /**
    * 从token中获取claim
    *
    * @param token token
    * @return claim
    */
    public Claims getClaimsFromToken(String token) {
    try {
    return Jwts.parser()
    .setSigningKey(this.secret.getBytes())
    .parseClaimsJws(token)
    .getBody();
    } catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | IllegalArgumentException e) {
    log.error("token解析错误", e);
    throw new IllegalArgumentException("Token invalided.");
    }
    }

    /**
    * 获取token的过期时间
    *
    * @param token token
    * @return 过期时间
    */
    public Date getExpirationDateFromToken(String token) {
    return getClaimsFromToken(token)
    .getExpiration();
    }

    /**
    * 判断token是否过期
    *
    * @param token token
    * @return 已过期返回true,未过期返回false
    */
    private Boolean isTokenExpired(String token) {
    Date expiration = getExpirationDateFromToken(token);
    return expiration.before(new Date());
    }

    /**
    * 计算token的过期时间
    *
    * @return 过期时间
    */
    private Date getExpirationTime() {
    return new Date(System.currentTimeMillis() + this.expirationTimeInSecond * 1000);
    }

    /**
    * 为指定用户生成token
    *
    * @param claims 用户信息
    * @return token
    */
    public String generateToken(Map<String, Object> claims) {
    Date createdTime = new Date();
    Date expirationTime = this.getExpirationTime();


    byte[] keyBytes = secret.getBytes();
    SecretKey key = Keys.hmacShaKeyFor(keyBytes);

    return Jwts.builder()
    .setClaims(claims)
    .setIssuedAt(createdTime)
    .setExpiration(expirationTime)
    // 你也可以改用你喜欢的算法
    // 支持的算法详见:https://github.com/jwtk/jjwt#features
    .signWith(key, SignatureAlgorithm.HS256)
    .compact();
    }

    /**
    * 判断token是否非法
    *
    * @param token token
    * @return 未过期返回true,否则返回false
    */
    public Boolean validateToken(String token) {
    return !isTokenExpired(token);
    }

    public static void main(String[] args) {
    // 1. 初始化
    JwtOperator jwtOperator = new JwtOperator();
    jwtOperator.expirationTimeInSecond = 1209600L;
    jwtOperator.secret = "aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrsssttt";

    // 2.设置用户信息
    HashMap<String, Object> objectObjectHashMap = Maps.newHashMap();
    objectObjectHashMap.put("id", "1");

    // 测试1: 生成token
    String token = jwtOperator.generateToken(objectObjectHashMap);
    // 会生成类似该字符串的内容: eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjEiLCJpYXQiOjE1NjU1ODk4MTcsImV4cCI6MTU2Njc5OTQxN30.27_QgdtTg4SUgxidW6ALHFsZPgMtjCQ4ZYTRmZroKCQ
    System.out.println(token);

    // 将我改成上面生成的token!!!
    String someToken = "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjEiLCJpYXQiOjE1NjU1ODk4MTcsImV4cCI6MTU2Njc5OTQxN30.27_QgdtTg4SUgxidW6ALHFsZPgMtjCQ4ZYTRmZroKCQ";
    // 测试2: 如果能token合法且未过期,返回true
    Boolean validateToken = jwtOperator.validateToken(someToken);
    System.out.println(validateToken);

    // 测试3: 获取用户信息
    Claims claims = jwtOperator.getClaimsFromToken(someToken);
    System.out.println(claims);

    // 将我改成你生成的token的第一段(以.为边界)
    String encodedHeader = "eyJhbGciOiJIUzI1NiJ9";
    // 测试4: 解密Header
    byte[] header = Base64.decodeBase64(encodedHeader.getBytes());
    System.out.println(new String(header));

    // 将我改成你生成的token的第二段(以.为边界)
    String encodedPayload = "eyJpZCI6IjEiLCJpYXQiOjE1NjU1ODk1NDEsImV4cCI6MTU2Njc5OTE0MX0";
    // 测试5: 解密Payload
    byte[] payload = Base64.decodeBase64(encodedPayload.getBytes());
    System.out.println(new String(payload));

    // 测试6: 这是一个被篡改的token,因此会报异常,说明JWT是安全的
    jwtOperator.validateToken("eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjEiLCJpYXQiOjE1NjU1ODk3MzIsImV4cCI6MTU2Njc5OTMzMn0.nDv25ex7XuTlmXgNzGX46LqMZItVFyNHQpmL9UQf-aUx");
    }
    }
  • 写配置

    1
    2
    3
    4
    jwt:
    secret: 秘钥
    # 有效期,单位秒,默认2周
    expire-time-in-second: 1209600
  • 使用:

    1
    2
    3
    4
    @Autowired
    private JwtOperator jwtOperator;

    // ...
]]>
<p>分享一下个人操作 <code>JWT</code> 的工具类。基于 <code>jjwt</code> 库,这是一个Java圈子最流行的 <code>JWT</code> 操作库。</p> <blockquote> <p><strong>TIPS</strong></p>
Spring Cloud Gateway限流详解 /spring-cloud-gateway/spring-cloud-rate-limit/ 2019-08-11T04:45:20.000Z 2019-08-11T04:53:15.665Z Spring Cloud Gatway内置的 RequestRateLimiterGatewayFilterFactory 提供限流的能力,基于令牌桶算法实现。目前,它内置的 RedisRateLimiter ,依赖Redis存储限流配置,以及统计数据。当然你也可以实现自己的RateLimiter,只需实现 org.springframework.cloud.gateway.filter.ratelimit.RateLimiter 接口,或者继承 org.springframework.cloud.gateway.filter.ratelimit.AbstractRateLimiter

漏桶算法

想象有一个水桶,水桶以一定的速度出水(以一定速率消费请求),当水流速度过大水会溢出(访问速率超过响应速率,就直接拒绝)。

漏桶算法的两个变量:

  • 水桶漏洞的大小:rate
  • 最多可以存多少的水:burst

令牌桶算法

系统按照恒定间隔向水桶里加入令牌(Token),如果桶满了的话,就不加了。每个请求来的时候,会拿走1个令牌,如果没有令牌可拿,那么就拒绝服务。

TIPS

  • Redis Rate Limiter的实现基于这篇文章: Stripe
  • Spring官方引用的令牌桶算法文章: Token Bucket Algorithm ,有兴趣可以看看。

写代码

  • 加依赖:

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
    </dependency>
  • 写配置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    spring:
    cloud:
    gateway:
    routes:
    - id: after_route
    uri: lb://user-center
    predicates:
    - TimeBetween=上午0:00,下午11:59
    filters:
    - AddRequestHeader=X-Request-Foo, Bar
    - name: RequestRateLimiter
    args:
    # 令牌桶每秒填充平均速率
    redis-rate-limiter.replenishRate: 1
    # 令牌桶的上限
    redis-rate-limiter.burstCapacity: 2
    # 使用SpEL表达式从Spring容器中获取Bean对象
    key-resolver: "#{@pathKeyResolver}"
    redis:
    host: 127.0.0.1
    port: 6379
  • 写代码:按照X限流,就写一个针对X的KeyResolver。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Configuration
    public class Raonfiguration {
    /**
    * 按照Path限流
    *
    * @return key
    */
    @Bean
    public KeyResolver pathKeyResolver() {
    return exchange -> Mono.just(
    exchange.getRequest()
    .getPath()
    .toString()
    );
    }
    }
  • 这样,限流规则即可作用在路径上。

    1
    2
    3
    例如:
    # 访问:http://${GATEWAY_URL}/users/1,对于这个路径,它的redis-rate-limiter.replenishRate = 1,redis-rate-limiter.burstCapacity = 2;
    # 访问:http://${GATEWAY_URL}/shares/1,对这个路径,它的redis-rate-limiter.replenishRate = 1,redis-rate-limiter.burstCapacity = 2;

测试

持续高速访问某个路径,速度过快时,返回 HTTP ERROR 429

拓展

你也可以实现针对用户的限流:

1
2
3
4
@Bean
public KeyResolver userKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("user"));
}

针对来源IP的限流:

1
2
3
4
5
6
7
8
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest()
.getHeaders()
.getFirst("X-Forwarded-For")
);
}

相关文章

]]>
<p>Spring Cloud Gatway内置的 <code>RequestRateLimiterGatewayFilterFactory</code> 提供限流的能力,基于令牌桶算法实现。目前,它内置的 <code>RedisRateLimiter</code> ,依赖Red
Spring Cloud Gateway排错、调试技巧总结 /spring-cloud-gateway/spring-cloud-gateway-debug/ 2019-08-10T17:45:20.000Z 2019-08-10T17:14:55.812Z 本文总结Spring Cloud Gateway的排错、调试技巧。欢迎留言补充!

第一式:Actuator监控端点

借助Actuator的监控端点,可分析全局过滤器、过滤器工厂、路由详情。详见:Spring Cloud Gateway监控

第二式:日志

加日志,按需将如下包的日志级别设置成 debugtrace ,总有一款对你有用。

  • org.springframework.cloud.gateway
  • org.springframework.http.server.reactive
  • org.springframework.web.reactive
  • org.springframework.boot.autoconfigure.web
  • reactor.netty
  • redisratelimiter

配置示例:

1
2
3
logging:
level:
org.springframework.cloud.gateway: trace

第三式:Wiretap【从Greenwich SR3及更高版本才会支持】

Reactor Netty HttpClient 以及 HttpServer 可启用 Wiretap 。将reactor.netty 包设置成 debugtrace ,然后设置如下属性:

  • spring.cloud.gateway.httpserver.wiretap=true
  • spring.cloud.gateway.httpclient.wiretap=true

分别开启HttpServer及HttpClient的Wiretap。

然后,就可以分析日志啦。

TIPS

]]>
<p>本文总结Spring Cloud Gateway的排错、调试技巧。<strong>欢迎留言补充!</strong></p> <h2 id="第一式:Actuator监控端点"><a href="#第一式:Actuator监控端点" class="headerlink" ti
Spring Cloud Gateway监控 /spring-cloud-gateway/spring-cloud-gateway-actuator/ 2019-08-10T15:55:20.000Z 2019-08-10T16:20:49.640Z 欢迎加入Spring Cloud Gateway监控豪华套餐——

只要为Spring Cloud Gateway添加Spring Boot Actuator( spring-boot-starter-actuator )的依赖,并将 gateway 端点暴露,即可获得若干监控端点,监控 & 操作Spring Cloud Gateway的方方面面。

1
2
3
4
5
management:
endpoints:
web:
exposure:
include: gateway

监控端点一览表:

TIPS
以下所有端点都挂在/actuator/gateway/ 下面。
例如:routes 的全路径是 /actuator/gateway/routes ,以此类推。

ID HTTP Method Description
globalfilters GET 展示所有的全局过滤器
routefilters GET 展示所有的过滤器工厂(GatewayFilter factories)
refresh POST【无消息体】 清空路由缓存
routes GET 展示路由列表
routes/{id} GET 展示指定id的路由的信息
routes/{id} POST【消息体如下】 新增一个路由
routes/{id} DELETE【无消息体】 删除一个路由

其中,要想动态添加路由配置,只需发送POST请求,消息体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"predicates": [
{
"name": "Path",
"args": {
"_genkey_0": "/test"
}
}
],
"filters": [
{
"name": "AddRequestHeader",
"args": {
"_genkey_0": "X-Request-Foo",
"_genkey_1": "Bar"
}
},
{
"name": "PreLog",
"args": {
"_genkey_0": "a",
"_genkey_1": "b"
}
}
],
"uri": "https://www.itmuch.com",
"order": 0
}

TIPS

技巧:消息体其实是有规律的,你可以先在配置文件中配置一个路由规则,然后访问 ${GATEWAY_URL}/actuator/gateway/routes 端点,每个路由id的对应段落,就是你的消息体啦。

如使用 POSTMAN 测试,可配置如下:

Postman

操作完成后,可再次访问 ${GATEWAY_URL}/actuator/gateway/routes 端点,可以看到,新的路由已被动态添加了。

TIPS

如果没有实时生效,使用refresh端点刷新一下路由信息即可。

]]>
<p>欢迎加入Spring Cloud Gateway监控豪华套餐——</p> <p>只要为Spring Cloud Gateway添加Spring Boot Actuator( <code>spring-boot-starter-actuator</code> )的依赖,并将
Spring Cloud Gateway-全局过滤器(Global Filters) /spring-cloud-gateway/global-filter/ 2019-08-10T15:45:20.000Z 2019-08-10T15:08:43.243Z

TIPS

本文基于Spring Cloud Gateway SR2,理论适配Spring Cloud Gateway SR1以及更高版本。

本文详细探讨Spring Cloud Gateway内置的全局过滤器。包括:
1 Combined Global Filter and GatewayFilter Ordering
2 Forward Routing Filter
3 LoadBalancerClient Filter
4 Netty Routing Filter
5 Netty Write Response Filter
6 RouteToRequestUrl Filter
7 Websocket Routing Filter
8 Gateway Metrics Filter
9 Marking An Exchange As Routed

GlobalFilter 接口和 GatewayFilter 有一样的接口定义,只不过, GlobalFilter 会作用于所有路由。

TIPS

官方声明:GlobalFilter的接口定义以及用法在未来的版本可能会发生变化。

个人判断:GlobalFilter可用于生产;如果有自定义GlobalFilter的需求,理论上也可放心使用——未来即使接口定义以及使用方式发生变化,应该也是平滑过渡的(比如Zuul的Fallback,原先叫ZuulFallbackProvider,后来改叫FallbackProvider,中间就有段时间新旧使用方式都支持,后面才逐步废弃老的使用方式)。

1 Combined Global Filter and GatewayFilter Ordering

当请求到来时,Filtering Web Handler 处理器会添加所有 GlobalFilter 实例和匹配的 GatewayFilter 实例到过滤器链中。

过滤器链会使用 org.springframework.core.Ordered 注解所指定的顺序,进行排序。Spring Cloud Gateway区分了过滤器逻辑执行的”pre”和”post”阶段,所以优先级高的过滤器将会在pre阶段最先执行,优先级最低的过滤器则在post阶段最后执行。

TIPS

数值越小越靠前执行,记得这一点就OK了。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Bean
@Order(-1)
public GlobalFilter a() {
return (exchange, chain) -> {
log.info("first pre filter");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
log.info("third post filter");
}));
};
}

@Bean
@Order(0)
public GlobalFilter b() {
return (exchange, chain) -> {
log.info("second pre filter");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
log.info("second post filter");
}));
};
}

@Bean
@Order(1)
public GlobalFilter c() {
return (exchange, chain) -> {
log.info("third pre filter");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
log.info("first post filter");
}));
};
}

执行结果:

1
2
3
4
5
6
first pre filter
second pre filter
third pre filter
first post filter
second post filter
third post filter

2 Forward Routing Filter

ForwardRoutingFilter 会查看exchange的属性 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的值(一个URI),如果该值l的scheme是 forward,比如:forward://localendpoint,则它会使用Spirng的DispatcherHandler 处理该请求。请求URL的路径部分,会被forward URL中的路径覆盖。未修改的原始URL,会被追加到 ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR 属性中。

TIPS

这段文档太学术了,讲解了LoadBalancerClientFilter 的实现原理,对使用者来说,意义不大;对使用者来说,只要知道这个Filter是用来做本地forward就OK了。

建议:如对原理感兴趣的,建议直接研究源码,源码比官方文档好理解。

3 LoadBalancerClient Filter

LoadBalancerClientFilter 会查看exchange的属性 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的值(一个URI),如果该值的scheme是 lb,比如:lb://myservice ,它将会使用Spring Cloud的LoadBalancerClient 来将 myservice 解析成实际的host和port,并替换掉 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的内容。原始地址会追加到 ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR 中。该过滤器还会查看 ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR 属性,如果发现该属性的值是 lb ,也会执行相同逻辑。

示例:

1
2
3
4
5
6
7
8
spring:
cloud:
gateway:
routes:
- id: myRoute
uri: lb://service
predicates:
- Path=/service/**

默认情况下,如果无法在 LoadBalancer 找到指定服务的实例,那么会返回503(对应如上的例子,找不到service实例,就返回503);可使用 spring.cloud.gateway.loadbalancer.use404=true 让其返回404。

LoadBalancer 返回的 ServiceInstanceisSecure 的值,会覆盖请求的scheme。举个例子,如果请求打到Gateway上使用的是 HTTPS ,但 ServiceInstanceisSecure 是false,那么下游收到的则是HTTP请求,反之亦然。然而,如果该路由指定了 GATEWAY_SCHEME_PREFIX_ATTR 属性,那么前缀将会被剥离,并且路由URL中的scheme会覆盖 ServiceInstance 的配置

TIPS

这段文档太学术了,讲解了LoadBalancerClientFilter 的实现原理,对使用者来说,意义不大;对使用者来说,其实只要知道这个Filter是用来整合Ribbon的就OK了

建议:如对原理感兴趣的,建议直接研究源码,源码比官方文档好理解。

4 Netty Routing Filter

如果 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的值的scheme是 httphttps ,则运行Netty Routing Filter 。它使用Netty HttpClient 向下游发送代理请求。获得的响应将放在exchange的 ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR 属性中,以便在后面的filter中使用。(有一个实验性的过滤器: WebClientHttpRoutingFilter 可实现相同功能,但无需Netty)

5 Netty Write Response Filter

如果exchange中的 ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR 属性中有 HttpClientResponse ,则运行 NettyWriteResponseFilter 。该过滤器在所有其他过滤器执行完成后执行,并将代理响应协会网关的客户端侧。(有一个实验性的过滤器: WebClientWriteResponseFilter 可实现相同功能,但无需Netty)

6 RouteToRequestUrl Filter

如果exchange中的 ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR 属性中有一个 Route 对象,则运行 RouteToRequestUrlFilter 。它根据请求URI创建一个新URI,但会使用该 Route 对象的URI属性进行更新。新URI放到exchange的 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 属性中。

如果URI具有scheme前缀,例如 lb:ws://serviceid ,该 lb scheme将从URI中剥离,并放到 ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR 中,方便后面的过滤器使用。

7 Websocket Routing Filter

如果exchange中的 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 属性的值的scheme是 ws或者 wss ,则运行Websocket Routing Filter。它底层使用Spring Web Socket将Websocket请求转发到下游。

可为URI添加 lb 前缀实现负载均衡,例如 lb:ws://serviceid

如果你使用 SockJS 所谓普通http的后备,则应配置正常的HTTP路由以及Websocket路由。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring:
cloud:
gateway:
routes:
# SockJS route
- id: websocket_sockjs_route
uri: http://localhost:3001
predicates:
- Path=/websocket/info/**
# Normwal Websocket route
- id: websocket_route
uri: ws://localhost:3001
predicates:
- Path=/websocket/**

8 Gateway Metrics Filter

要启用Gateway Metrics,需添加 spring-boot-starter-actuator 依赖。然后,只要spring.cloud.gateway.metrics.enabled 的值不是false,就会运行Gateway Metrics Filter。此过滤器添加名为 gateway.requests 的时序度量(timer metric),其中包含以下标记:

  • routeId:路由ID
  • routeUri:API将路由到的URI
  • outcome:由 HttpStatus.Series 博彩公司排名
  • status:返回给客户端的Http Status
  • httpStatusCode:返回给客户端的请求的Http Status
  • httpMethod:请求所使用的Http方法

这些指标暴露在 /actuator/metrics/gateway.requests 端点中,并且可以轻松与Prometheus整合,从而创建一个 Grafana dashboard

TIPS

Prometheus是一款监控工具,Grafana是一款监控可视化工具;Spring Boot Actuator可与这两款工具进行整合。关于整合,笔者写过手把手的博客,有兴趣可以看一下:

Spring Boot 2.x监控数据可视化(Actuator + Prometheus + Grafana手把手)

9 Marking An Exchange As Routed

在网关路由 ServerWebExchange 后,它将通过在exchange添加一个 gatewayAlreadyRouted 属性,从而将exchange标记为 routed 。一旦请求被标记为 routed ,其他路由过滤器将不会再次路由请求,而是直接跳过。您可以使用便捷方法将exchange标记为 routed ,或检查exchange是否是 routed

  • ServerWebExchangeUtils.isAlreadyRouted 检查是否已被路由
  • ServerWebExchangeUtils.setAlreadyRouted 设置routed状态

TIPS

简单来说,就是网关通过 gatewayAlreadyRouted 属性表示这个请求已经转发过了,而无需其他过滤器重复路由。从而防止重复的路由操作。

参考文档

]]>
<blockquote> <p><strong>TIPS</strong></p> <p>本文基于Spring Cloud Gateway SR2,理论适配Spring Cloud Gateway SR1以及更高版本。</p> </blockquote> <p>本文详细探讨Spr
Spring Cloud Gateway-过滤器工厂详解(GatewayFilter Factories) /spring-cloud-gateway/gateway-filter-factory/ 2019-08-10T14:45:20.000Z 2019-08-10T13:07:24.543Z

TIPS

本文基于 Spring Cloud Greenwich SR2 ,理论支持 Spring Cloud Greenwich SR1 ,其中的新特性标注出来了。

这一节来探讨Spring Cloud Gateway内置的Filter工厂。包括:

1 AddRequestHeader GatewayFilter Factory
2 AddRequestParameter GatewayFilter Factory
3 AddResponseHeader GatewayFilter Factory
4 DedupeResponseHeader GatewayFilter Factory
5 Hystrix GatewayFilter Factory
6 FallbackHeaders GatewayFilter Factory
7 PrefixPath GatewayFilter Factory
8 PreserveHostHeader GatewayFilter Factory
9 RequestRateLimiter GatewayFilter Factory
10 RedirectTo GatewayFilter Factory
11 RemoveHopByHopHeadersFilter GatewayFilter Factory
12 RemoveRequestHeader GatewayFilter Factory
13 RemoveResponseHeader GatewayFilter Factory
14 RewritePath GatewayFilter Factory
15 RewriteResponseHeader GatewayFilter Factory
16 SaveSession GatewayFilter Factory
17 SecureHeaders GatewayFilter Factory
18 SetPath GatewayFilter Factory
19 SetResponseHeader GatewayFilter Factory
20 SetStatus GatewayFilter Factory
21 StripPrefix GatewayFilter Factory
22 Retry GatewayFilter Factory
23 RequestSize GatewayFilter Factory
24 Modify Request Body GatewayFilter Factory
25 Modify Response Body GatewayFilter Factory
26 Default Filters

技巧

  • 断点打在 org.springframework.cloud.gateway.filter.NettyRoutingFilter#filter ,就可以调试Gateway转发的具体细节了。

  • 添加如下配置,可观察到一些请求细节:

    1
    2
    3
    4
    5
    6
    logging:
    level:
    org.springframework.cloud.gateway: trace
    org.springframework.http.server.reactive: debug
    org.springframework.web.reactive: debug
    reactor.ipc.netty: debug

1 AddRequestHeader GatewayFilter Factory

1
2
3
4
5
6
7
8
spring:
cloud:
gateway:
routes:
- id: add_request_header_route
uri: https://example.org
filters:
- AddRequestHeader=X-Request-Foo, Bar

为原始请求添加名为 X-Request-Foo ,值为 Bar 的请求头。

2 AddRequestParameter GatewayFilter Factory

1
2
3
4
5
6
7
8
spring:
cloud:
gateway:
routes:
- id: add_request_parameter_route
uri: https://example.org
filters:
- AddRequestParameter=foo, bar

为原始请求添加请求参数 foo=bar

3 AddResponseHeader GatewayFilter Factory

1
2
3
4
5
6
7
8
spring:
cloud:
gateway:
routes:
- id: add_response_header_route
uri: https://example.org
filters:
- AddResponseHeader=X-Response-Foo, Bar

添加名为 X-Request-Foo ,值为 Bar 的响应头。

4 DedupeResponseHeader GatewayFilter Factory

TIPS

Spring Cloud Greenwich SR2提供的新特性,低于这个版本无法使用。

强烈建议阅读一下类org.springframework.cloud.gateway.filter.factory.DedupeResponseHeaderGatewayFilterFactory上的注释,比官方文档写得还好。

1
2
3
4
5
6
7
8
spring:
cloud:
gateway:
routes:
- id: dedupe_response_header_route
uri: https://example.org
filters:
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin, RETAIN_FIRST

剔除重复的响应头。

举个例子:

我们在Gateway以及微服务上都设置了CORS(解决跨域)header,如果不做任何配置,请求 -> 网关 -> 微服务,获得的响应就是这样的:

1
2
Access-Control-Allow-Credentials: true, true
Access-Control-Allow-Origin: https://musk.mars, https://musk.mars

也就是Header重复了。要想把这两个Header去重,只需设置成如下即可。

1
2
filters:
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin

也就是说,想要去重的Header如果有多个,用空格分隔即可;

去重策略:

1
2
3
RETAIN_FIRST: 默认值,保留第一个值
RETAIN_LAST: 保留最后一个值
RETAIN_UNIQUE: 保留所有唯一值,以它们第一次出现的顺序保留

5 Hystrix GatewayFilter Factory

TIPS

Hystrix是Spring Cloud第一代中的容错组件,不过已经进入维护模式(相关文章:Spring Cloud Netflix项目进入维护模式之我见 ),未来,Hystrix会被Spring Cloud移除掉,取而代之的是Alibaba Sentinel/Resilience4J

所以本文不做详细探讨了,但Gateway整合Hystrix其实包含了很多姿势。请感兴趣的同学自行前往官方文档了解详情:https://cloud.spring.io/spring-cloud-static/Greenwich.SR2/single/spring-cloud.html#hystrix

1
2
3
4
5
6
7
8
spring:
cloud:
gateway:
routes:
- id: hystrix_route
uri: https://example.org
filters:
- Hystrix=myCommandName

6 FallbackHeaders GatewayFilter Factory

TIPS

也是对Hystrix的支持,不做详细探讨了,请感兴趣的同学自行前往官方文档了解详情:https://cloud.spring.io/spring-cloud-static/Greenwich.SR2/single/spring-cloud.html#fallback-headers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
spring:
cloud:
gateway:
routes:
- id: ingredients
uri: lb://ingredients
predicates:
- Path=//ingredients/**
filters:
- name: Hystrix
args:
name: fetchIngredients
fallbackUri: forward:/fallback
- id: ingredients-fallback
uri: http://localhost:9994
predicates:
- Path=/fallback
filters:
- name: FallbackHeaders
args:
executionExceptionTypeHeaderName: Test-Header

7 PrefixPath GatewayFilter Factory

1
2
3
4
5
6
7
8
spring:
cloud:
gateway:
routes:
- id: prefixpath_route
uri: https://example.org
filters:
- PrefixPath=/mypath

为匹配的路由添加前缀。例如:访问${GATEWAY_URL}/hello 会转发到https://example.org/mypath/hello

8 PreserveHostHeader GatewayFilter Factory

1
2
3
4
5
6
7
8
spring:
cloud:
gateway:
routes:
- id: preserve_host_route
uri: https://example.org
filters:
- PreserveHostHeader

如果不设置,那么名为 Host 的Header由Http Client控制;如果设置了,那么会设置一个请求属性(preserveHostHeader=true),路由过滤器会检查从而去判断是否要发送原始的、名为Host的Header。

9 RequestRateLimiter GatewayFilter Factory

TIPS

在视频Spring Cloud Gateway一章,限流一节会详细讲解。也可阅读官方文档 https://cloud.spring.io/spring-cloud-static/Greenwich.SR2/single/spring-cloud.html#_requestratelimiter_gatewayfilter_factory

1
2
3
4
5
6
7
8
9
10
11
spring:
cloud:
gateway:
routes:
- id: requestratelimiter_route
uri: https://example.org
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20

10 RedirectTo GatewayFilter Factory

1
2
3
4
5
6
7
8
9
spring:
cloud:
gateway:
routes:
- id: prefixpath_route
uri: https://example.org
filters:
# 配置成HTTP状态码, URL的形式
- RedirectTo=302,
  • HTTP状态码应该是HTTP状态码300序列,例如301
  • URL必须是合法的URL,并且该值会作为名为 Location 的Header。

上面配置表达的意思是: ${GATEWAY_URL}/hello 会重定向到 https://ecme.org/hello ,并且携带一个 Location: 的Header。

11 RemoveHopByHopHeadersFilter GatewayFilter Factory

1
spring.cloud.gateway.filter.remove-hop-by-hop.headers: Connection,Keep-Alive

移除转发请求的Header,多个用 , 分隔。默认情况下,移除如下Header。这些Header是由 IETF 组织规定的。

  • Connection
  • Keep-Alive
  • Proxy-Authenticate
  • Proxy-Authorization
  • TE
  • Trailer
  • Transfer-Encoding
  • Upgrade

12 RemoveRequestHeader GatewayFilter Factory

1
2
3
4
5
6
7
8
spring:
cloud:
gateway:
routes:
- id: removerequestheader_route
uri: https://example.org
filters:
- RemoveRequestHeader=X-Request-Foo

为原始请求删除名为 X-Request-Foo 的请求头。

13 RemoveResponseHeader GatewayFilter Factory

1
2
3
4
5
6
7
8
spring:
cloud:
gateway:
routes:
- id: removeresponseheader_route
uri: https://example.org
filters:
- RemoveResponseHeader=X-Response-Foo

删除名为 X-Request-Foo 的响应头。

14 RewritePath GatewayFilter Factory

1
2
3
4
5
6
7
8
9
10
11
spring:
cloud:
gateway:
routes:
- id: rewritepath_route
uri: https://example.org
predicates:
- Path=/foo/**
filters:
# 配置成原始路径正则, 重写后的路径的正则
- RewritePath=/foo/(?<segment>.*), /${segment}

重写请求路径。如上配置,访问 /foo/bar 会将路径改为/bar 再转发,也就是会转发到 https://example.org/bar 。需要注意的是,由于YAML语法,需用$ 替换 $

15 RewriteResponseHeader GatewayFilter Factory

1
2
3
4
5
6
7
8
spring:
cloud:
gateway:
routes:
- id: rewriteresponseheader_route
uri: https://example.org
filters:
- RewriteResponseHeader=X-Response-Foo, password=[^&]+, password=***

如果名为 X-Response-Foo 的响应头的内容是/42?user=ford&password=omg!what&flag=true,则会被修改为/42?user=ford&password=***&flag=true

16 SaveSession GatewayFilter Factory

1
2
3
4
5
6
7
8
9
10
spring:
cloud:
gateway:
routes:
- id: save_session
uri: https://example.org
predicates:
- Path=/foo/**
filters:
- SaveSession

在转发到后端微服务请求之前,强制执行 WebSession::save 操作。用在那种像 Spring Session 延迟数据存储(笔者注:数据不是立刻持久化)的,并希望在请求转发前确保session状态保存情况。

如果你将Spring SecutirySpring Session集成使用,并想确保安全信息都传到下游机器,你就需要配置这个filter。

17 SecureHeaders GatewayFilter Factory

添加一系列起安全作用的响应头。Spring Cloud Gateway参考了这篇博客的建议:https://blog.appcanary.com/2017/http-security-headers.html

默认会添加如下Header(包括值):

  • X-Xss-Protection:1; mode=block
  • Strict-Transport-Security:max-age=631138519
  • X-Frame-Options:DENY
  • X-Content-Type-Options:nosniff
  • Referrer-Policy:no-referrer
  • Content-Security-Policy:default-src 'self' https:; font-src 'self' https: data:; img-src 'self' https: data:; object-src 'none'; script-src https:; style-src 'self' https: 'unsafe-inline'
  • X-Download-Options:noopen
  • X-Permitted-Cross-Domain-Policies:none

如果你想修改这些Header的值,可使用如下配置:

前缀:spring.cloud.gateway.filter.secure-headers

上面的header对应的后缀:

  • xss-protection-header
  • strict-transport-security
  • frame-options
  • content-type-options
  • referrer-policy
  • content-security-policy
  • download-options
  • permitted-cross-domain-policies

例如:spring.cloud.gateway.filter.secure-headers.xss-protection-header: 你想要的值

如果想禁用某些Header,可使用如下配置:spring.cloud.gateway.filter.secure-headers.disable ,多个用 , 分隔。例如:spring.cloud.gateway.filter.secure-headers.disable=frame-options,download-options

18 SetPath GatewayFilter Factory

1
2
3
4
5
6
7
8
9
10
spring:
cloud:
gateway:
routes:
- id: setpath_route
uri: https://example.org
predicates:
- Path=/foo/{segment}
filters:
- SetPath=/{segment}

采用路径template参数,通过请求路径的片段的模板化,来达到操作修改路径的母的,运行多个路径片段模板化。

如上配置,访问${GATEWAY_PATH}/foo/bar ,则对于后端微服务的路径会修改为 /bar

19 SetResponseHeader GatewayFilter Factory

1
2
3
4
5
6
7
8
spring:
cloud:
gateway:
routes:
- id: setresponseheader_route
uri: http://example.org
filters:
- SetResponseHeader=X-Response-Foo, Bar

如果后端服务响应带有名为 X-Response-Foo 的响应头,则将值改为替换成 Bar

20 SetStatus GatewayFilter Factory

1
2
3
4
5
6
7
8
9
10
11
12
spring:
cloud:
gateway:
routes:
- id: setstatusstring_route
uri: http://example.org
filters:
- SetStatus=BAD_REQUEST
- id: setstatusint_route
uri: http://example.org
filters:
- SetStatus=401

修改响应的状态码,值可以是数字,也可以是字符串。但一定要是Spring HttpStatus 枚举类中的值。如上配置,两种方式都可以返回HTTP状态码401。

21 StripPrefix GatewayFilter Factory

1
2
3
4
5
6
7
8
9
10
spring:
cloud:
gateway:
routes:
- id: nameRoot
uri: http://nameservice
predicates:
- Path=/name/**
filters:
- StripPrefix=2

数字表示要截断的路径的数量。如上配置,如果请求的路径为 /name/bar/foo ,则路径会修改为/foo ,也就是会截断2个路径。

22 Retry GatewayFilter Factory

1
2
3
4
5
6
7
8
9
10
11
12
13
spring:
cloud:
gateway:
routes:
- id: retry_test
uri: http://localhost:8080/flakey
predicates:
- Host=*.retry.com
filters:
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY

针对不同的响应做重试,可配置如下参数:

  • retries: 重试次数
  • statuses: 需要重试的状态码,取值在 org.springframework.http.HttpStatus
  • methods: 需要重试的请求方法,取值在 org.springframework.http.HttpMethod
  • series: HTTP状态码系列,取值在 org.springframework.http.HttpStatus.Series

23 RequestSize GatewayFilter Factory

1
2
3
4
5
6
7
8
9
10
11
12
13
spring:
cloud:
gateway:
routes:
- id: request_size_route
uri: http://localhost:8080/upload
predicates:
- Path=/upload
filters:
- name: RequestSize
args:
# 单位字节
maxSize: 5000000

为后端服务设置收到的最大请求包大小。如果请求大小超过设置的值,则返回 413 Payload Too Large 。默认值是5M

24 Modify Request Body GatewayFilter Factory

TIPS

该过滤器处于 BETA 状态,未来API可能会变化,生产环境请慎用。

可用于在Gateway将请求发送给后端微服务之前,修改请求体内容。该过滤器只能通过代码配置,不支持在配置文件设置。示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
return builder.routes()
.route("rewrite_request_obj", r -> r.host("*.rewriterequestobj.org")
.filters(f -> f.prefixPath("/httpbin")
.modifyRequestBody(String.class, Hello.class, MediaType.APPLICATION_JSON_VALUE,
(exchange, s) -> return Mono.just(new Hello(s.toUpperCase())))).uri(uri))
.build();
}

static class Hello {
String message;

public Hello() { }

public Hello(String message) {
this.message = message;
}

public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}
}

25 Modify Response Body GatewayFilter Factory

TIPS

该过滤器处于 BETA 状态,未来API可能会变化,生产环境请慎用。

可用于修改响应体内容。该过滤器只能通过代码配置,不支持在配置文件设置。示例:

1
2
3
4
5
6
7
8
9
@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
return builder.routes()
.route("rewrite_response_upper", r -> r.host("*.rewriteresponseupper.org")
.filters(f -> f.prefixPath("/httpbin")
.modifyResponseBody(String.class, String.class,
(exchange, s) -> Mono.just(s.toUpperCase()))).uri(uri)
.build();
}

26 Default Filters

1
2
3
4
5
6
spring:
cloud:
gateway:
default-filters:
- AddResponseHeader=X-Response-Default-Foo, Default-Bar
- PrefixPath=/httpbin

如果你想为所有路由添加过滤器,可使用该属性。

]]>
<blockquote> <p><strong>TIPS</strong></p> <p>本文基于 <code>Spring Cloud Greenwich SR2</code> ,理论支持 <code>Spring Cloud Greenwich SR1</code> ,其中的
Spring Cloud Gateway-路由谓词工厂详解(Route Predicate Factories) /spring-cloud-gateway/route-predicate-factory/ 2019-08-10T04:45:20.000Z 2019-08-10T09:13:15.072Z

TIPS

本文基于Spring Cloud Greenwich SR2编写,兼容Spring Cloud Finchley及更高版本。

这一节来详细探讨Spring Cloud Gateway的路由谓词工厂 (Route Predicate Factories),路由谓词工厂的作用是:符合Predicate的条件,就使用该路由的配置,否则就不管。 只要掌握这一句,掌握路由谓词工厂就比较轻松了。

TIPS

Predicate是Java 8提供的一个函数式编程接口。

本文探讨了Spring Cloud Gateway中内置的谓词工厂,包括:

谓词工厂
After
Before
Between
Cookie
Header
Host
Method
Path
Query
RemoteAddr

路由配置的两种形式

先来探讨Spring Cloud Gateway路由配置的两种姿势:

路由到指定URL

示例1:通配

1
2
3
4
5
6
spring:
cloud:
gateway:
routes:
- id: {唯一标识}
uri:

表示访问 GATEWAY_URL/** 会转发到 /**

TIPS

这段配置不能直接使用,需要和下面的Predicate配合使用才行。

示例2:精确匹配

1
2
3
4
5
6
spring:
cloud:
gateway:
routes:
- id: {唯一标识}
uri: /spring-cloud/spring-cloud-stream-pan-ta/

表示访问 GATEWAY_URL/spring-cloud/spring-cloud-stream-pan-ta/ 会转发到 /spring-cloud/spring-cloud-stream-pan-ta/

TIPS

这段配置不能直接使用,需要和下面的Predicate配合使用才行。

路由到服务发现组件上的微服务

示例1:通配

1
2
3
4
5
6
spring:
cloud:
gateway:
routes:
- id: {唯一标识}
uri: lb://user-center

表示访问 GATEWAY_URL/** 会转发到 user-center 微服务的 /**

TIPS

这段配置不能直接使用,需要和下面的Predicate配合使用才行。

示例2:精确匹配

1
2
3
4
5
6
spring:
cloud:
gateway:
routes:
- id: {唯一标识}
uri: lb://user-center/shares/1

表示访问 GATEWAY_URL/shares/1 会转发到 user-center 微服务的 /shares/1

TIPS

这段配置不能直接使用,需要和下面的Predicate配合使用才行。

谓词工厂详解

下面正式探讨路由谓词工厂。Spring Cloud Gateway提供了十来种路由谓词工厂。为网关实现灵活的转发提供了基石。

After

示例:

1
2
3
4
5
6
7
8
9
10
11
12
spring:
cloud:
gateway:
routes:
- id: after_route
uri: lb://user-center
predicates:
# 当且仅当请求时的时间After配置的时间时,才会转发到用户微服务
# 目前配置不会进该路由配置,所以返回404
# 将时间改成 < now的时间,则访问localhost:8040/** -> user-center/**
# eg. 访问http://localhost:8040/users/1 -> user-center/users/1
- After=2030-01-20T17:42:47.789-07:00[America/Denver]

TIPS

  • 技巧:时间可使用 System.out.println(ZonedDateTime.now()); 打印,然后即可看到时区。例如:2019-08-10T16:50:42.579+08:00[Asia/Shanghai]
  • 时间格式的相关逻辑:
    • 默认时间格式:org.springframework.format.support.DefaultFormattingConversionService#addDefaultFormatters
    • 时间格式注册:org.springframework.format.datetime.standard.DateTimeFormatterRegistrar#registerFormatters

Before

示例:

1
2
3
4
5
6
7
8
9
10
11
12
spring:
cloud:
gateway:
routes:
- id: before_route
uri: lb://user-center
predicates:
# 当且仅当请求时的时间Before配置的时间时,才会转发到用户微服务
# 目前配置不会进该路由配置,所以返回404
# 将时间改成 > now的时间,则访问localhost:8040/** -> user-center/**
# eg. 访问http://localhost:8040/users/1 -> user-center/users/1
- Before=2018-01-20T17:42:47.789-07:00[America/Denver]

Between

示例:

1
2
3
4
5
6
7
8
9
10
11
spring:
cloud:
gateway:
routes:
- id: between_route
uri: lb://user-center
predicates:
# 当且仅当请求时的时间Between配置的时间时,才会转发到用户微服务
# 因此,访问localhost:8040/** -> user-center/**
# eg. 访问http://localhost:8040/users/1 -> user-center/users/1
- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2027-01-21T17:42:47.789-07:00[America/Denver]

示例:

1
2
3
4
5
6
7
8
9
10
11
spring:
cloud:
gateway:
routes:
- id: cookie_route
uri: lb://user-center
predicates:
# 当且仅当带有名为somecookie,并且值符合正则ch.p的Cookie时,才会转发到用户微服务
# 如Cookie满足条件,则访问http://localhost:8040/** -> user-center/**
# eg. 访问http://localhost:8040/users/1 -> user-center/users/1
- Cookie=somecookie, ch.p
1
2
3
4
5
6
7
8
9
10
11
spring:
cloud:
gateway:
routes:
- id: header_route
uri: lb://user-center
predicates:
# 当且仅当带有名为X-Request-Id,并且值符合正则d+的Header时,才会转发到用户微服务
# 如Header满足条件,则访问http://localhost:8040/** -> user-center/**
# eg. 访问http://localhost:8040/users/1 -> user-center/users/1
- Header=X-Request-Id, d+

Host

1
2
3
4
5
6
7
8
9
10
11
spring:
cloud:
gateway:
routes:
- id: host_route
uri: lb://user-center
predicates:
# 当且仅当名为Host的Header符合**.somehost.org或**.anotherhost.org时,才会转发用户微服务
# 如Host满足条件,则访问http://localhost:8040/** -> user-center/**
# eg. 访问http://localhost:8040/users/1 -> user-center/users/1
- Host=**.somehost.org,**.anotherhost.org

Method

1
2
3
4
5
6
7
8
9
10
11
spring:
cloud:
gateway:
routes:
- id: method_route
uri: lb://user-center
predicates:
# 当且仅当HTTP请求方法是GET时,才会转发用户微服务
# 如请求方法满足条件,访问http://localhost:8040/** -> user-center/**
# eg. 访问http://localhost:8040/users/1 -> user-center/users/1
- Method=GET

Path

1
2
3
4
5
6
7
8
9
10
11
spring:
cloud:
gateway:
routes:
- id: path_route
uri: lb://user-center
predicates:
# 当且仅当访问路径是/users/*或者/some-path/**,才会转发用户微服务
# segment是一个特殊的占位符,单层路径匹配
# eg. 访问http://localhost:8040/users/1 -> user-center/users/1
- Path=/users/{segment},/some-path/**

TIPS

建议大家看下这一部分的官方文档,里面有个segment编程技巧。比较简单,留个印象。

https://cloud.spring.io/spring-cloud-static/Greenwich.SR2/single/spring-cloud.html#_path_route_predicate_factory

Query

示例1:

1
2
3
4
5
6
7
8
9
10
spring:
cloud:
gateway:
routes:
- id: query_route
uri: lb://user-center
predicates:
# 当且仅当请求带有baz的参数,才会转发到用户微服务
# eg. 访问http://localhost:8040/users/1?baz=xx -> user-center的/users/1
- Query=baz

示例2:

1
2
3
4
5
6
7
8
9
10
spring:
cloud:
gateway:
routes:
- id: query_route
uri: lb://user-center
predicates:
# 当且仅当请求带有名为foo的参数,且参数值符合正则ba.,才会转发到用户微服务
# eg. 访问http://localhost:8040/users/1?baz=baz -> user-center的/users/1?baz=baz
- Query=foo, ba.

RemoteAddr

示例:

1
2
3
4
5
6
7
8
9
10
spring:
cloud:
gateway:
routes:
- id: remoteaddr_route
uri: lb://user-center
predicates:
# 当且仅当请求IP是192.168.1.1/24网段,例如192.168.1.10,才会转发到用户微服务
# eg. 访问http://localhost:8040/users/1 -> user-center的/users/1
- RemoteAddr=192.168.1.1/24

TIPS

建议大家看下这一部分的官方文档,有个小编程技巧。比较简单,留个印象。

https://cloud.spring.io/spring-cloud-static/Greenwich.SR2/single/spring-cloud.html#_remoteaddr_route_predicate_factory

相关代码

Route Predicate Factory

]]>
<blockquote> <p><strong>TIPS</strong></p> <p>本文基于Spring Cloud Greenwich SR2编写,兼容Spring Cloud Finchley及更高版本。</p> </blockquote> <p>这一节来详细探讨Spr
Spring Cloud Stream知识点盘点 /spring-cloud/spring-cloud-stream-pan-ta/ 2019-08-04T16:54:49.000Z 2019-08-11T00:04:26.976Z 前面,已经探讨了:

本文来对Spring Cloud Stream,做一个知识点盘点和总结,包括:

  • 概念
  • Stream注解
  • Spring Cloud Integration(Spring Cloud Stream的底层)注解
  • Spring Messaging(Spring消息编程模型)注解
  • Spring Cloud Stream API

概念

group

组内只有1个实例消费。如果不设置group,则stream会自动为每个实例创建匿名且独立的group——于是每个实例都会消费。

组内单次只有1个实例消费,并且会轮询负载均衡。通常,在将应用程序绑定到给定目标时,最好始终指定consumer group。

destination binder

与外部消息系统通信的组件,为构造 Binding提供了 2 个方法,分别是 bindConsumerbindProducer ,它们分别用于构造生产者和消费者。Binder使Spring Cloud Stream应用程序可以灵活地连接到中间件,目前spring为kafka、rabbitmq提供binder。

destination binding

Binding 是连接应用程序跟消息中间件的桥梁,用于消息的消费和生产,由binder创建。

partition

TIPS

严格来说这个不是概念,而是一种Stream提高伸缩性、吞吐量的一种方式。不过不想另起标题了,写在这里吧。

一个或多个生产者将数据发送到多个消费者,并确保有共同特征标识的数据由同一个消费者处理。默认是对消息进行hashCode,然后根据分区个数取余,所以对于相同的消息,总会落到同一个消费者上。

注解

Input(Stream)

示例:

1
2
3
4
public interface Barista {
@Input("inboundOrders")
SubscribableChannel orders();
}

作用:

  • 用于接收消息
  • 为每个binding生成channel实例
  • 指定channel名称
  • 在spring容器中生成一个名为inboundOrders,类型为SubscribableChannel的bean
  • 在spring容器中生成一个类,实现Barista接口。

Output(Stream)

示例:

1
2
3
4
public interface Source {
@Output
MessageChannel output();
}

作用:

类似Input,只是用来生产消息。

StreamListener(Stream)

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@StreamListener(value = Sink.INPUT, condition = "headers['type']=='dog'")
public void handle(String body) {
System.out.println("Received: " + body);
}

@Bean
@InboundChannelAdapter(value = Source.OUTPUT,
poller = @Poller(fixedDelay = "1000", maxMessagesPerPoll = "2"))
public MessageSource<String> test() {
return () -> {
Map<String, Object> map = new HashMap<>(1);
map.put("type", "dog");
return new GenericMessage<>("abcdef", map);
};
}

作用:

用于消费消息

condition的作用:符合条件,才进入处理方法。

condition起作用的两个条件:

  • 注解的方法没有返回值
  • 方法是一个独立方法,不支持Reactive API

SendTo(messaging)

示例:

1
2
3
4
5
6
// 接收INPUT这个channel的消息,并将返回值发送到OUTPUT这个channel
@StreamListener(Sink.INPUT)
@SendTo(Source.OUTPUT)
public String receive(String receiveMsg) {
return "handle...";
}

作用:

用于发送消息

InboundChannelAdapter(Integration)

示例:

1
2
3
4
5
6
@Bean
@InboundChannelAdapter(value = Source.OUTPUT,
poller = @Poller(fixedDelay = "10", maxMessagesPerPoll = "1"))
public MessageSource<String> test() {
return () -> new GenericMessage<>("Hello Spring Cloud Stream");
}

作用:

表示让定义的方法生产消息。

注:用 InboundChannelAdapter 注解的方法上即使有参数也没用。即下面test方法不要有参数。

  • fixedDelay:多少毫秒发送1次
  • maxMessagesPerPoll:一次发送几条消息。

ServiceActivator(Integration)

示例:

1
2
3
4
@ServiceActivator(inputChannel = Sink.INPUT, outputChannel = Source.OUTPUT)
public String transform(String payload) {
return payload.toUpperCase();
}

作用:

表示方法能够处理消息或消息有效内容,监听input消息,用方法体的代码处理,然后输出到output中。

Transformer(Integration)

示例:

1
2
3
4
@Transformer(inputChannel = Processor.INPUT, outputChannel = Processor.OUTPUT)
public Object transform(String message) {
return message.toUpperCase();
}

作用:

ServiceActivator 类似,表示方法能够转换消息,消息头,或消息有效内容

PollableMessageSource(Stream)

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@SpringBootApplication
@EnableBinding({ConsumerApplication.PolledProcessor.class})
@EnableScheduling
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}

@Autowired
private PolledProcessor polledProcessor;

@Scheduled(fixedDelay = 5_000)
public void poll() {
polledProcessor.input().poll(message -> {
byte[] bytes = (byte[]) message.getPayload();
String payload = new String(bytes);
System.out.println(payload);
});
}

public interface PolledProcessor {
@Input
PollableMessageSource input();

@Output
MessageChannel output();
}

@Bean
@InboundChannelAdapter(value = "output",
poller = @Poller(fixedDelay = "1000", maxMessagesPerPoll = "1"))
public MessageSource<String> test() {
return () -> {
Map<String, Object> map = new HashMap<>(1);
map.put("type", "dog");
return new GenericMessage<>("adfdfdsafdsfa", map);
};
}
}

如果不想自己做byte数组转换,可以添加配置:

1
2
3
4
5
6
7
spring:
cloud:
stream:
bindings:
output:
# 指定content-type
content-type: text/plain

作用:

允许消费者控制消费速率。

相关文章:

https://spring.io/blog/2018/02/27/spring-cloud-stream-2-0-polled-consumers

]]>
<p>前面,已经探讨了:</p> <ul> <li><a href="/spring-cloud-alibaba/spring-cloud-stream-rocketmq-filter-consume/">Spring Cloud Str
Spring Cloud Stream错误处理详解 /spring-cloud/spring-cloud-stream-error-handling/ 2019-08-04T14:51:49.000Z 2019-08-04T13:02:43.410Z

TIPS

本文基于Spring Cloud Greenwich SR1,理论支持Finchley及更高版本。

本节详细探讨Spring Cloud Stream的错误处理。

应用处理

局部处理【通用】

配置:

1
2
3
4
5
6
7
8
9
spring:
cloud:
stream:
bindings:
input:
destination: my-destination
group: my-group
output:
destination: my-destination

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Slf4j
@SpringBootApplication
@EnableBinding({Processor.class})
@EnableScheduling
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}

@StreamListener(value = Processor.INPUT)
public void handle(String body) {
throw new RuntimeException("x");
}

@ServiceActivator(inputChannel = "my-destination.my-group.errors")
public void handleError(ErrorMessage message) {
Throwable throwable = message.getPayload();
log.error("截获异常", throwable);

Message<?> originalMessage = message.getOriginalMessage();
assert originalMessage != null;

log.info("原始消息体 = {}", new String((byte[]) originalMessage.getPayload()));
}

@Bean
@InboundChannelAdapter(value = Processor.OUTPUT,
poller = @Poller(fixedDelay = "1000", maxMessagesPerPoll = "1"))
public MessageSource<String> test() {
return () -> new GenericMessage<>("adfdfdsafdsfa");
}
}

全局处理【通用】

1
2
3
4
5
6
7
8
9
10
@StreamListener(value = Processor.INPUT)
public void handle(String body) {
throw new RuntimeException("x");
}

@StreamListener("errorChannel")
public void error(Message<?> message) {
ErrorMessage errorMessage = (ErrorMessage) message;
System.out.println("Handling ERROR: " + errorMessage);
}

系统处理

系统处理方式,因消息中间件不同而异。如果应用没有配置错误处理,那么error将会被传播给binder,binder将error回传给消息中间件。消息中间件可以丢弃消息、requeue(重新排队,从而重新处理)或将失败的消息发送给DLQ(死信队列)。

丢弃

默认情况下,错误消息将被丢弃。虽然在某些情况下可以接受,但这种方式一般不适用于生产。

DLQ【RabbitMQ】

TIPS

  • 虽然RocketMQ也支持DLQ,但目前RocketMQ控制台并不支持在界面上操作,将死信放回消息队列,让客户端重新处理。所以使用很不方便,而且用法也和本节有一些差异。
  • 如使用RocketMQ,建议参考上面【应用处理】一节的用法,也可额外订阅这个Topic %DLQ%+consumerGroup
  • 个人给RocketMQ控制台提的Issue:https://github.com/apache/rocketmq/issues/1334

配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring:
cloud:
stream:
bindings:
input:
destination: my-destination
group: my-group
output:
destination: my-destination
rabbit:
bindings:
input:
consumer:
auto-bind-dlq: true

代码:

1
2
3
4
5
6
7
8
9
10
11
@StreamListener(value = Processor.INPUT)
public void handle(String body) {
throw new RuntimeException("x");
}

@Bean
@InboundChannelAdapter(value = Processor.OUTPUT,
poller = @Poller(fixedDelay = "1000", maxMessagesPerPoll = "1"))
public MessageSource<String> test() {
return () -> new GenericMessage<>("adfdfdsafdsfa");
}

这样,消息消费失败后,就会放入死信队列。在控制台操作一下,即可将死信放回消息队列,这样,客户端就可以重新处理。

如果想获取原始错误的异常堆栈,可添加如下配置:

1
2
3
4
5
6
7
8
spring:
cloud:
stream:
rabbit:
bindings:
input:
consumer:
republish-to-dlq: true

requeue【RabbitMQ】

Rabbit/Kafka的binder依赖RetryTemplate实现重试,从而提升消息处理的成功率。然而,如果设置了spring.cloud.stream.bindings.input.consumer.max-attempts=1 ,那么RetryTemplate则不再重试。此时可通过requeue方式处理异常。

添加如下配置:

1
2
3
4
# 默认是3,设为1则禁用重试
spring.cloud.stream.bindings.<input channel名称>.consumer.max-attempts=1
# 表示是否要requeue被拒绝的消息(即:requeue处理失败的消息)
spring.cloud.stream.rabbit.bindings.input.consumer.requeue-rejected=true

这样,失败的消息将会被重新提交到同一个handler进行处理,直到handler抛出 AmqpRejectAndDontRequeueException 异常为止。

RetryTemplate【通用】

配置方式

RetryTemplate重试也是错误处理的一种手段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
spring:
cloud:
stream:
bindings:
<input channel名称>:
consumer:
# 最多尝试处理几次,默认3
maxAttempts: 3
# 重试时初始避退间隔,单位毫秒,默认1000
backOffInitialInterval: 1000
# 重试时最大避退间隔,单位毫秒,默认10000
backOffMaxInterval: 10000
# 避退乘数,默认2.0
backOffMultiplier: 2.0
# 当listen抛出retryableExceptions未列出的异常时,是否要重试
defaultRetryable: true
# 异常是否允许重试的map映射
retryableExceptions:
java.lang.RuntimeException: true
java.lang.IllegalStateException: false

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
@StreamListener(value = Processor.INPUT)
public void handle(String body) {
throw new RuntimeException(body);
}

private AtomicInteger count = new AtomicInteger(0);

@Bean
@InboundChannelAdapter(value = Processor.OUTPUT,
poller = @Poller(fixedDelay = "1000", maxMessagesPerPoll = "1"))
public MessageSource<String> test() {
return () -> new GenericMessage<>(count.getAndAdd(1) + "");
}

编码方式

多数场景下,使用配置方式定制重试行为都是可以满足需求的,但配置方式可能无法满足一些复杂需求。此时可使用编码方式配置RetryTemplate:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Configuration
class RetryConfiguration {
@StreamRetryTemplate
public RetryTemplate sinkConsumerRetryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setRetryPolicy(retryPolicy());
retryTemplate.setBackOffPolicy(backOffPolicy());

return retryTemplate;
}

private ExceptionClassifierRetryPolicy retryPolicy() {
BinaryExceptionClassifier keepRetryingClassifier = new BinaryExceptionClassifier(
Collections.singletonList(IllegalAccessException.class
));
keepRetryingClassifier.setTraverseCauses(true);

SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy(3);
AlwaysRetryPolicy alwaysRetryPolicy = new AlwaysRetryPolicy();

ExceptionClassifierRetryPolicy retryPolicy = new ExceptionClassifierRetryPolicy();
retryPolicy.setExceptionClassifier(
classifiable -> keepRetryingClassifier.classify(classifiable) ?
alwaysRetryPolicy : simpleRetryPolicy);

return retryPolicy;
}

private FixedBackOffPolicy backOffPolicy() {
final FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
backOffPolicy.setBackOffPeriod(2);

return backOffPolicy;
}
}

然后添加配置:

1
spring.cloud.stream.bindings.<input channel名称>.consumer.retry-template-name=myRetryTemplate

注意

Spring Cloud Stream 2.2才支持设置retry-template-name

]]>
<blockquote> <p><strong>TIPS</strong></p> <p>本文基于Spring Cloud Greenwich SR1,理论支持Finchley及更高版本。</p> </blockquote> <p>本节详细探讨Spring Cloud Strea
Spring Cloud Stream实现消息过滤消费 /spring-cloud-alibaba/spring-cloud-stream-rocketmq-filter-consume/ 2019-08-04T00:08:49.000Z 2019-08-04T12:06:31.823Z

TIPS

本文基于Spring Cloud Greenwich SR1 + spring-cloud-starter-stream-rocketmq 0.9.0

理论兼容:Spring Cloud Finchley+ + spring-cloud-starter-stream-rocketmq 0.2.2+

MQ使用的是RocketMQ,也可使用Kafka或者RabbitMQ。

本文探讨Spring Cloud Stream & RocketMQ过滤消息的各种姿势。

在实际项目中,我们可能需要实现消息消费的过滤。

举个例子:实现消息的分流处理:

生产者生产的消息,虽然消息体可能一样,但是header不一样。可编写两个或者更多的消费者,对不同header的消息做针对性的处理!

condition

生产者

生产者设置一下header,比如my-header,值根据你的需要填写:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Autowired
private Source source;

public String testStream() {
this.source.output()
.send(
MessageBuilder
.withPayload("消息体")
.setHeader("my-header","你的header")
.build()
);
return "success";
}

消费者

1
2
3
4
5
6
7
8
@Service
@Slf4j
public class TestStreamConsumer {
@StreamListener(value = Sink.INPUT,condition = "headers['my-header']=='你的header'")
public void receive(String messageBody) {
log.info("通过stream收到了消息:messageBody ={}", messageBody);
}
}

如代码所示,使用 StreamListener 注解的 condition 属性。当 headers['my-header']=='你的header' 条件满足,才会进入到方法体。

Tags

TIPS

该方式只支持RoketMQ,不支持Kafka/RabbitMQ

生产者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Autowired
private Source source;

public String testStream() {
this.source.output()
.send(
MessageBuilder
.withPayload("消息体")
// 注意:只能设置1个tag
.setHeader(RocketMQHeaders.TAGS, "tag1")
.build()
);
return "success";
}

消费者

  • 接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public interface MySink {
    String INPUT1 = "input1";
    String INPUT2 = "input2";

    @Input(INPUT1)
    SubscribableChannel input();

    @Input(INPUT2)
    SubscribableChannel input2();
    }
  • 注解

    1
    @EnableBinding({MySink.class})
  • 配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    spring:
    cloud:
    stream:
    rocketmq:
    binder:
    name-server: 127.0.0.1:9876
    bindings:
    input1:
    consumer:
    # 表示input2消费带有tag1的消息
    tags: tag1
    input2:
    consumer:
    # 表示input2消费带有tag2或者tag3的消息
    tags: tag2||tag3
    bindings:
    input1:
    destination: test-topic
    group: test-group1
    input2:
    destination: test-topic
    group: test-group2
  • 消费代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    @Service
    @Slf4j
    public class MyTestStreamConsumer {
    /**
    * 我消费带有tag1的消息
    *
    * @param messageBody 消息体
    */
    @StreamListener(MySink.INPUT1)
    public void receive1(String messageBody) {
    log.info("带有tag1的消息被消费了:messageBody ={}", messageBody);
    }

    /**
    * 我消费带有tag1或者tag2的消息
    *
    * @param messageBody 消息体
    */
    @StreamListener(MySink.INPUT2)
    public void receive2(String messageBody) {
    log.info("带有tag2/tag3的消息被消费了:messageBody ={}", messageBody);
    }
    }
  • 日志:

    1
    2019-08-04 19:10:03.799  INFO 53760 --- [MessageThread_1] c.i.u.rocketmq.MyTestStreamConsumer      : 带有tag1的消息被消费了:messageBody =消息体

Sql 92

TIPS

  • 该方式只支持RoketMQ,不支持Kafka/RabbitMQ
  • 用了sql,就不要用Tag

RocketMQ支持使用SQL语法过滤消息。官方文档:http://rocketmq.apache.org/rocketmq/filter-messages-by-sql92-in-rocketmq/

Spring Clous Stream RocketMQ也为此特性提供了支持。

开启SQL 92支持

默认情况下,RocketMQ的SQL过滤支持是关闭的,要想使用SQL 92过滤消息,需要:

  • conf/broker.conf 添加

    1
    enablePropertyFilter = true
  • 启动RocketMQ

    1
    nohup sh bin/mqbroker -n localhost:9876 -c ./conf/broker.conf &

生产者

1
2
3
4
5
6
7
8
9
10
11
12
13
@Autowired
private Source source;

public String testStream() {
this.source.output()
.send(
MessageBuilder
.withPayload("消息体")
.setHeader("index", 1000)
.build()
);
return "success";
}

消费者

  • 接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public interface MySink {
    String INPUT1 = "input1";
    String INPUT2 = "input2";

    @Input(INPUT1)
    SubscribableChannel input();

    @Input(INPUT2)
    SubscribableChannel input2();
    }
  • 注解

    1
    @EnableBinding({MySink.class})
  • 配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    spring:
    cloud:
    stream:
    rocketmq:
    binder:
    name-server: 127.0.0.1:9876
    bindings:
    input1:
    consumer:
    sql: 'index < 1000'
    input2:
    consumer:
    sql: 'index >= 1000'
    bindings:
    input1:
    destination: test-topic
    group: test-group1
    input2:
    destination: test-topic
    group: test-group2
  • 消费代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    @Service
    @Slf4j
    public class MyTestStreamConsumer {
    /**
    * 我消费带有tag1的消息
    *
    * @param messageBody 消息体
    */
    @StreamListener(MySink.INPUT1)
    public void receive1(String messageBody) {
    log.info("index > 1000的消息被消费了:messageBody ={}", messageBody);
    }

    /**
    * 我消费带有tag1或者tag2的消息
    *
    * @param messageBody 消息体
    */
    @StreamListener(MySink.INPUT2)
    public void receive2(String messageBody) {
    log.info("index <=1000 的消息被消费了:messageBody ={}", messageBody);
    }
    }
  • 日志

    1
    2019-08-04 19:58:59.787  INFO 56375 --- [MessageThread_1] c.i.u.rocketmq.MyTestStreamConsumer      : index <=1000 的消息被消费了:messageBody =消息体

相关代码

org.springframework.cloud.stream.binder.rocketmq.properties.RocketMQConsumerProperties

参考文档

]]>
<blockquote> <p><strong>TIPS</strong></p> <p>本文基于<code>Spring Cloud Greenwich SR1</code> + <code>spring-cloud-starter-stream-rocketmq 0.9.0<
分享:手把手教你如何免费且光荣地使用正版IntelliJ IDEA /other/how-to-use-idea-freely/ 2019-07-31T14:09:49.000Z 2019-08-01T16:39:23.002Z

TIPS

近日在个人技术讨论QQ群里,谈论到IDEA的那些事儿。有童鞋居然在某电商网站花钱买激活码。我觉得是助纣为虐(自己用盗版就算了,花钱养卖盗版感觉很无语),遂有此文。

其实IDEA是可以免费使用的。

IDEA是个人最喜欢的IDE,它非常智能,懂我的心,极大地提高了个人编程效率;让人爱不释手,欲罢不能。

然而,这是一款收费软件,价格不菲。IDEA价目详见:https://www.jetbrains.com/idea/buy/#commercial?billing=yearly

本文教大家如何 免费,并且 光荣地 使用 正版 IntelliJ IDEA。

IDEA免费开源协议

https://www.jetbrains.com/community/opensource/ ,IDEA有一个开源免费协议。简单翻译一下。

申请条款

  • 您必须是项目负责人或常规提交者。
  • 您的OS项目符合 开源定义
  • 您的操作系统项目可能不提供付费赞助,或从商业公司或组织(非政府组织,教育,研究或政府)获得资金。您不得为您的操作系统项目提供任何付费支持,咨询或培训服务,也不得分发您的操作系统软件的付费版本。获得该项目工作报酬的贡献者不符合资格。
  • 您的OS项目正在积极开发至少3个月。
  • 您的OS项目社区处于活动状态。
  • 您定期发布更新的版本。

许可条款

  • 许可证提供1年,并允许在1年内免费升级软件的所有新版本。
  • 如果您的项目仍满足要求,可根据要求提供许可证续订。
  • 一个许可证可以安装在任意数量的计算机上,但不能在两个或更多计算机上同时使用。
  • 许可证仅提供给核心团队开发人员。

许可限制

  • 许可证仅可用于非商业OS开发。请考虑购买单独的许可证以处理商业项目。
  • 该软件的使用仅限于许可用户,无权将软件转让给任何第三方。

有关完整的详细信息,请查看开源项目许可协议

申请免费使用

申请门槛

从协议不难看出,你只需在GitHub上准备一个维护超过3个月的项目开源项目,就可以免费使用IDEA 1年了,1年到期后,可以按照此步骤再申请一次。

这是一个良好的闭环:

  • 有开源项目,所以能申请免费使用IDEA;
  • 有了IDEA神器,又可以更好地维护开源项目……

申请

https://www.jetbrains.com/shop/eform/opensource?product=ALL 即可提交申请。

IDEA 1

点击 APPLY FOR FREE LICENSES 按钮,即可看到类似如下的界面:

IDEA 2

收取激活码

  • 等待1天左右,即可前往申请时填写的邮箱,即可收到激活码了。

    IDEA 3

  • 点击图中的链接,即可进入协议界面,点击 ACCEPT ,即可看到类似如下的界面:

    IDEA 4

  • 点击图中的链接,并按照提示操作,注册一个账号,或者如果你已经有Jetbrains账号,就直接登录。这一步主要是让你的Jetbrains账号和Liscence绑定。

  • 将激活码填入如下界面即可激活IDEA:

    IDEA 5

  • 激活后的效果:

    IDEA 6

    可以看到,已经成功激活了。未来过期后,依照本次操作再执行一次即可。当然我本机电脑还没有升级到2019.2,这个无妨。你可以先升级,再激活;也可以先激活再升级。

]]>
<blockquote> <p><strong>TIPS</strong></p> <p>近日在个人技术讨论QQ群里,谈论到IDEA的那些事儿。有童鞋居然在某电商网站花钱买激活码。我觉得是助纣为虐(自己用盗版就算了,花钱养卖盗版感觉很无语),遂有此文。</p> <p><stron
常用MQ产品的对比 /other/mq-pk/ 2019-07-27T17:09:49.000Z 2019-07-28T13:03:20.734Z 本文整理了常用MQ之间的对比,旨在帮助大家在实际项目中选择MQ产品。

消息队列对比参照表

对照表来自:消息队列对比参照表 ,对比维度比较全面,结果个人比较认同,强烈建议参考。

mq-pk

RocketMQ官方PK

注:

RocketMQ官方和其他MQ之间的PK,表格非常宽,如显示不全,请点击原文地址阅读

原文地址:RocketMQ vs. ActiveMQ vs. Kafka

Messaging Product Client SDK Protocol and Specification Ordered Message Scheduled Message Batched Message BroadCast Message Message Filter Server Triggered Redelivery Message Storage Message Retroactive Message Priority High Availability and Failover Message Track Configuration Management and Operation Tools
ActiveMQ Java, .NET, C++ etc. Push model, support OpenWire, STOMP, AMQP, MQTT, JMS Exclusive Consumer or Exclusive Queues can ensure ordering Supported Not Supported Supported Supported Not Supported Supports very fast persistence using JDBC along with a high performance journal,such as levelDB, kahaDB Supported Supported Supported, depending on storage,if using kahadb it requires a ZooKeeper server Not Supported The default configuration is low level, user need to optimize the configuration parameters Supported
Kafka Java, Scala etc. Pull model, support TCP Ensure ordering of messages within a partition Not Supported Supported, with async producer Not Supported Supported, you can use Kafka Streams to filter messages Not Supported High performance file storage Supported offset indicate Not Supported Supported, requires a ZooKeeper server Not Supported Kafka uses key-value pairs format for configuration. These values can be supplied either from a file or programmatically. Supported, use terminal command to expose core metrics
RocketMQ Java, C++, Go Pull model, support TCP, JMS, OpenMessaging Ensure strict ordering of messages,and can scale out gracefully Supported Supported, with sync mode to avoid message loss Supported Supported, property filter expressions based on SQL92 Supported High performance and low latency file storage Supported timestamp and offset two indicates Not Supported Supported, Master-Slave model, without another kit Supported Work out of box,user only need to pay attention to a few configurations Supported, rich web and terminal command to expose core metrics
]]>
<p>本文整理了常用MQ之间的对比,旨在帮助大家在实际项目中选择MQ产品。</p> <h2 id="消息队列对比参照表"><a href="#消息队列对比参照表" class="headerlink" title="消息队列对比参照表"></a>消息队列对比参照表</h2><bl
RocketMQ控制台安装教程 /rocketmq/rocketmq-console-install/ 2019-07-24T15:40:49.000Z 2019-10-02T06:29:55.505Z RocketMQ安装教程 一节中,详细探讨了如何搭建RocketMQ,这一节来搭建RocketMQ控制台,RocketMQ的可视化管理界面。

一、下载代码

1
2
3
4
5
# 方式一、git下载,执行如下命令
git clone https://github.com/apache/rocketmq-externals.git

# 方式二、直接下载,访问如下地址即可
https://github.com/apache/rocketmq-externals/archive/master.zip

二、修改控制台代码

2.1 修改配置

找到rocketmq-console/src/main/resources/application.properties 根据需求,修改配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 管理后台访问上下文路径,默认为空
# 如果填写,需写成/xxx的形式,例如/console
server.contextPath=

# 控制台的端口
server.port=8080

...

# if this value is empty,use env value rocketmq.config.namesrvAddr NAMESRV_ADDR | now, you can set it in ops page.default localhost:9876
# Name Server地址
rocketmq.config.namesrvAddr=

# if you use rocketmq version < 3.5.8, rocketmq.config.isVIPChannel should be false.default true
rocketmq.config.isVIPChannel=

#rocketmq-console's data path:dashboard/monitor
rocketmq.config.dataPath=/tmp/rocketmq-console/data

#set it false if you don't want use dashboard.default true
rocketmq.config.enableDashBoardCollect=true

#set the message track trace topic if you don't want use the default one
rocketmq.config.msgTrackTopicName=

rocketmq.config.ticketKey=ticket

#Must create userInfo file: ${rocketmq.config.dataPath}/users.properties if the login is required
rocketmq.config.loginRequired=false

笔者只修改了如下两项:

1
2
3
4
5
# console端口
server.port=17890
# name server地址
# 也可以不修改,在启动完console后,在控制台导航栏 - 运维 - NameSvrAddrList一栏设置
rocketmq.config.namesrvAddr=localhost:9876

2.2 修改依赖

修改 pom.xml ,修改RocketMQ相关依赖的版本

找到

1
<rocketmq.version>4.4.0</rocketmq.version>

修改为

1
<rocketmq.version>你的RocketMQ版本</rocketmq.version>

笔者使用的是RocketMQ 4.5.1,故而改为

1
<rocketmq.version>4.5.1</rocketmq.version>

2.3 修改代码

修改pom.xml后,org.apache.rocketmq.console.service.impl.MessageServiceImpl#queryMessageByTopic 编译会报错,所以需要解决一下。将

1
DefaultMQPullConsumer consumer = new DefaultMQPullConsumer(MixAll.TOOLS_CONSUMER_GROUP, null);

改为:

1
2
RPCHook rpcHook = null;
DefaultMQPullConsumer consumer = new DefaultMQPullConsumer(MixAll.TOOLS_CONSUMER_GROUP, rpcHook);

即可。

2.4 打包构建

1
2
3
4
5
6
7
8
# 切换到代码根目录
cd rocketmq-externals

# 切换到控制台目录
cd rocketmq-console

# 构建
mvn clean package -DskipTests

三、懒人包

笔者已经将修改好的RocketMQ控制台发布到GitHub了。

下载地址:https://github.com/eacdy/rocketmq-externals/releases

四、启动

1
java -jar rocketmq-console-ng-1.0.1.jar

五、访问

访问 http://localhost:17890 (端口用上面 application.properties 中的 server.port=17890 指定,默认是8080) ,即可看到类似如下的界面:

RocketMQ控制台首页

六、控制台使用说明

RocketMQ控制台使用文档

]]>
<p><a href="/rocketmq/rocketmq-install">RocketMQ安装教程</a> 一节中,详细探讨了如何搭建RocketMQ,这一节来搭建RocketMQ控制台,RocketMQ的可视化管理界面。</p> <h2 id="一、下载代码"><a hr
RocketMQ 4.5.1安装教程 /rocketmq/rocketmq-install/ 2019-07-24T15:00:49.000Z 2019-07-29T12:36:30.855Z

TIPS

本文基于RocketMQ 4.5.1,理论支持RocketMQ 4.0+

本文详细探讨如何搭建RocketMQ

一、下载

前往 http://rocketmq.apache.org/release_notes/release-notes-4.5.1/ ,下载 Binary 文件即可。

例如RocketMQ 4.5.1的下载地址:

二、系统要求

  • 64位操作系统,生产环境建议Linux/Unix/MacOS(Windows操作系统安装说明详见 Windows操作系统安装教程
  • 64位JDK 1.8+
  • 4G+的可用磁盘

三、Linux/Unix/MacOS安装教程

3.1 搭建

  • 解压压缩包

    1
    unzip rocketmq-all-4.5.1-bin-release.zip
  • 切换目录到RocketMQ根目录

    1
    cd rocketmq-all-4.5.1-bin-release
  • 启动Name Server

    1
    nohup sh bin/mqnamesrv &

    验证是否启动OK:

    1
    2
    3
    4
    tail -f ~/logs/rocketmqlogs/namesrv.log

    # 如果成功启动,能看到类似如下的日志:
    2019-07-18 17:03:56 INFO main - The Name Server boot success. ...
  • 启动 Broker

    1
    nohup sh bin/mqbroker -n localhost:9876 &

    验证是否启动OK:

    1
    2
    3
    4
    tail -f ~/logs/rocketmqlogs/broker.log

    # 如果启动成功,能看到类似如下的日志:
    2019-07-18 17:08:41 INFO main - The broker[itmuchcomdeMacBook-Pro.local, 192.168.43.197:10911] boot success. serializeType=JSON and name server is localhost:9876

3.2 验证RocketMQ功能正常(可选)

3.2.1 验证生产消息正常

执行如下命令:

1
2
export NAMESRV_ADDR=localhost:9876
sh bin/tools.sh org.apache.rocketmq.example.quickstart.Producer

能看到类似如下输出:

1
SendResult [sendStatus=SEND_OK, msgId=C0A82BC5F36C511D50C05B41...

3.2.2 验证消费消息正常

执行如下命令:

1
sh bin/tools.sh org.apache.rocketmq.example.quickstart.Consumer

能看到类似如下输出:

1
ConsumeMessageThread_4 Receive New Messages: [MessageExt [queueId=3, stor....

3.3 停止

依次执行以下两条命令即可

1
2
3
4
5
6
7
8
9
10
11
# 命令
sh bin/mqshutdown broker
# 输出如下信息说明停止成功
The mqbroker(36695) is running...
Send shutdown request to mqbroker(36695) OK

# 命令
sh bin/mqshutdown namesrv
# 输出如下信息说明停止成功
The mqnamesrv(36664) is running...
Send shutdown request to mqnamesrv(36664) OK

四、Windows操作系统安装教程

详见 Windows下RocketMQ安装部署

五、生产可用集群搭建教程

RocketMQ集群搭建说明 ,集群安装模式非常多,本文不展开了。请各位看客根据自己的需求,选择适合自己的模式自行搭建。

]]>
<blockquote> <p><strong>TIPS</strong></p> <p>本文基于RocketMQ 4.5.1,理论支持RocketMQ 4.0+</p> </blockquote> <p>本文详细探讨如何搭建RocketMQ</p> <h2 id="一、下载">
Alibaba Sentinel 配置项总结 /spring-cloud-alibaba/sentinel-config-properties/ 2019-07-17T00:08:49.000Z 2019-07-17T14:55:09.686Z 前面总结了:

这一节来总结Sentinel的所有配置。

TIPS

本文基于Sentinel 1.6.2编写,未来Sentinel发布新版本后,各位看官可按照本文中的“参考文档”,自行查阅新版本的配置项目。

Spring Cloud Alibaba Sentienl相关配置项

TIPS

参考文档:https://github.com/alibaba/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-docs/src/main/asciidoc-zh/sentinel.adoc

配置项 含义 默认值
spring.cloud.sentinel.enabled Sentinel自动化配置是否生效 true
spring.cloud.sentinel.eager 取消Sentinel控制台懒加载 false
spring.cloud.sentinel.transport.port 应用与Sentinel控制台交互的端口,应用本地会起一个该端口占用的HttpServer 8719
spring.cloud.sentinel.transport.dashboard Sentinel 控制台地址
spring.cloud.sentinel.transport.heartbeat-interval-ms 应用与Sentinel控制台的心跳间隔时间
spring.cloud.sentinel.transport.client-ip 客户端IP
spring.cloud.sentinel.filter.order Servlet Filter的加载顺序。Starter内部会构造这个filter Integer.MIN_VALUE
spring.cloud.sentinel.filter.url-patterns 数据类型是数组。表示Servlet Filter的url pattern集合 /*
spring.cloud.sentinel.filter.enabled Enable to instance CommonFilter true
spring.cloud.sentinel.metric.charset metric文件字符集 UTF-8
spring.cloud.sentinel.metric.file-single-size Sentinel metric 单个文件的大小
spring.cloud.sentinel.metric.file-total-count Sentinel metric 总文件数量
spring.cloud.sentinel.log.dir Sentinel 日志文件所在的目录
spring.cloud.sentinel.log.switch-pid Sentinel 日志文件名是否需要带上pid false
spring.cloud.sentinel.servlet.block-page 自定义的跳转 URL,当请求被限流时会自动跳转至设定好的 URL
spring.cloud.sentinel.flow.cold-factor 冷启动因子 3

Alibaba Sentinel启动配置项

TIPS

参考文档:https://github.com/alibaba/Sentinel/wiki/启动配置项

sentinel-core 配置项

名称 含义 类型 默认值 是否必需 备注
project.name 指定程序的名称 String null
csp.sentinel.app.type 指定程序的类型 int 0 (APP_TYPE_COMMON) 1.6.0 引入
csp.sentinel.metric.file.single.size 单个监控文件的大小 long 52428800
csp.sentinel.metric.file.total.count 监控文件的总数上限 int 6
csp.sentinel.log.dir Sentinel 日志文件目录 String ${user.home}/logs/csp/ 1.3.0 引入
csp.sentinel.log.use.pid 日志文件名中是否加入进程号,用于单机部署多个应用的情况 boolean false 1.3.0 引入
csp.sentinel.statistic.max.rt 最大的有效响应时长(ms),超出此值则按照此值记录 int 4900ms 1.4.1 引入

其中 project.name 项用于指定应用名(appName)。若未指定,则默认从 sun.java.command 中解析出对应的类名作为应用名。实际项目使用中建议指定应用名

注意:若需要在单台机器上运行相同服务的多个实例,则需要加入 -Dcsp.sentinel.log.use.pid=true 来保证不同实例日志的独立性。

sentinel-transport-common 配置项

名称 含义 类型 默认值 是否必需
csp.sentinel.dashboard.server 控制台的地址,指定控制台后客户端会自动向该地址发送心跳包。地址格式为:hostIp:port String null
csp.sentinel.heartbeat.interval.ms 心跳包发送周期,单位毫秒 long null 非必需,若不进行配置,则会从相应的 HeartbeatSender中提取默认值
csp.sentinel.api.port 本地启动 HTTP API Server 的端口号 int null 是,且不可冲突

使用说明

所有参数均可通过 JVM -D 参数指定。除 project.name 以及日志的配置项(如 csp.sentinel.log.dir)之外,其余参数还可通过 properties 文件指定,路径为 ${user_home}/logs/csp/${project.name}.properties

优先级顺序:JVM -D 参数的优先级最高,若 properties 和 JVM 参数中有相同项的配置,以 JVM -D 参数配置的为准。

]]>
<p>前面总结了:</p> <ul> <li><a href="/spring-cloud-alibaba/sentinel-configuration-rule/">Alibaba Sentinel 规则参数总结</a></li> <l