在 Spring Boot 项目中,我们使用的信息采集器主要就是 Spring Boot Actuator,这个模块由 Spring Boot 官方提供。

它包含了许多生产级别的功能,例如健康检查、审计、指标收集、HTTP 请求追踪等,Spring Boot Actuator 将这些信息收集起来后,通过 HTTP 和 JMX 两种方式暴露给外部模块。

例如 Spring Boot Actuator 通过 /health 端点(endpoints)提供了应用的健康信息,开发者只需要访问该端点就可以看到应用的健康信息,但是这些端点返回的数据是 JSON 格式的,不方便查看,也不方便分析,所以一般情况下,Spring Boot Actuator 都是和一些外部模块一起使用。


Actuator 端点

  • 首先创建一个 Spring Boot 项目,添加上 Actuator 依赖。
  • 项目创建成功后,直接运行项目,可以在 Actuator 的 Mappings 看见端点信息。
  • 可以通过配置文件来暴露可使用的端点。
  • 也可以通过配置文件来打开远程暂停 Spring Boot 项目端点。发起 Post 请求: http://localhost:8080/actuator/shutdown

Spring Boot Security 保护 Actuator 端点信息

我们可以使用 Security 来保护 Actuator,需要登陆才可以访问我们打开的端点。

  • 首先添加 Security 依赖。
  • 创建 config/SecurityConfig 文件来配置 Security,重写 Security 方法。
  • 在配置文件中,配置用户基本登陆信息。
  • 重启项目,Postman 中需要选择 Authorization,登陆类型选择 Basic Auth,输入用户名密码,重新请求远程暂停端点查看效果。

修改 Actuator 路径映射

只需要在配置文件中修改即可。

1
2
3
4
5
management:
endpoints:
web:
## 默认为 /actuator,修改为 /
base-path: /

也可以针对某一个端点进行修改路径映射

1
2
3
4
5
6
management:
endpoints:
web:
## 针对某一个端点修改路径映射
path-mapping:
beans: bs

Actuator 跨域支持

在配置文件中配置跨域支持

1
2
3
4
5
6
7
8
management:
endpoints:
web:
cors:
## 允许所有的域访问
allowed-origins: *
## 允许的请求方法
allowed-methods: GET,POST

Actuator 健康指示器

健康指示器显示信息,默认为 never,它有三个参数:

  • always: 总是显示
  • never:从来不限时
  • when_authorized:显示给认证用户
    1
    2
    3
    4
    management:
    endpoint:
    health:
    show-details: when_authorized

访问健康指示器,指定所需角色:

1
2
3
4
management:
endpoint:
health:
roles: ADMIN

重启项目后,访问 GET 请求: http://localhost:8080/actuator/health

Actuator 健康指示器也支持生成很多技术详细,比如 redis:


Actuator 自定义健康指示器

新建一个配置类 /HealthConfig。
配置配置文件,自定义健康指示器和响应码。

1
2
3
4
5
6
7
8
9
management:
endpoint:
health:
## 自定义健康指示器
status:
order: FATAL,DOWN,OUT_OF_SERVER,UP,UNKNOWN
## 自定义响应码
http-mapping:
FATAL: 503

Actuator 自定义应用信息

通过配置文件定义

1
2
3
4
5
6
7
8
info:
app:
encoding: @project.build.sourceEncoding@
java:
source: @java.version@
target: @java.version@
author:
name: sihai

访问:http://localhost:8080/actuator/info

通过类定义

创建一个类 AppInfoConfig,继承自 InfoContributor。

1
2
3
4
5
6
7
8
9
10
@Component
public class AppInfoConfig implements InfoContributor {
@Override
public void contribute(Info.Builder builder) {
Map<String, String> link = new HashMap<>();
link.put("site", "https://tsihai.github.io/");
link.put("site-2", "https://github.com/Tsihai");
builder.withDetail("link", link);
}
}

Actuator Info 端点查看 Git 提交信息

  • 首先添加插件 git commit id。
  • 然后在 maven 中 git-commit-id 插件 revision Git 的信息,生成完成之后。
  • 重启项目后访问 info 端点。 http://localhost:8080/actuator/info
  • 即可看见 git 的简略 json 信息。
  • 可以在配置文件中配置显示 git 的详细信息。

Actuator Info 端点查看项目构建信息

首先需要配置一下插件

1
2
3
4
5
6
7
8
9
10
11
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>
  • 在 maven 中的 spring-boot 插件 build-info 信息。
  • 生成数据后,重启项目后访问 info 端点。

Actuator 单体应用监控信息可视化

Actuator Server

  • 首先另外创建一个 Spring Boot 项目 /actuator-server,添加 web、Admin (Server) 依赖。
  • 在启动类上添加上 @EnableAdminServer。
  • 在配置类上配置端口号为 8081。
  • 由于我们在 client 上配置了 security 进行了端点保护,所以需要在配置类上配置 security 的账号密码。
  • 启动项目后在浏览器打开:http://localhost:8081

Actuator Client

  • 在 actuator 项目中添加 Admin (Client) 依赖
  • 在配置文件中配置连接的 server 地址。
    1
    2
    3
    4
    5
    spring:
    boot:
    admin:
    client:
    url: http://localhost:8081
  • 将 HealthConfig 类设置成正常返回。
    1
    2
    3
    4
    5
    6
    7
    @Component
    public class HealthConfig implements HealthIndicator {
    @Override
    public Health health() {
    return Health.up().withDetail("msg", "正常").build();
    }
    }
  • 重新访问 http://localhost:8081
  • 就可以看见 actuator-client 配置的详细信息 /info。

Actuator 邮件报警

如果服务掉线后,可以及时发送邮件报警

  • 在 actuator-server 中,添加上 mail,fastjson 依赖。
  • 在配置文件中配置信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
spring:
boot:
admin:
instance-auth:
default-user-name: sihai
default-password: root
notify:
mail:
from: 1075991706@qq.com
to: 13169186610@163.com
ignore-changes:
## 邮件发送信息
mail:
host: smtp.qq.com
port: 465
username: 1075991706@qq.com
password:
default-encoding: utf-8
properties:
mail:
smtp:
socketFactory:
class: javax.net.ssl.SSLSocketFactory
  • 添加一个后端控制台的提示信息
    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
    package com.sihai.actuatorserver.config;

    import com.alibaba.fastjson.JSONObject;
    import de.codecentric.boot.admin.server.domain.entities.Instance;
    import de.codecentric.boot.admin.server.domain.entities.InstanceRepository;
    import de.codecentric.boot.admin.server.domain.events.InstanceEvent;
    import de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;
    import de.codecentric.boot.admin.server.notify.AbstractStatusChangeNotifier;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    import reactor.core.publisher.Mono;

    import java.util.Arrays;


    @Component
    public class AdminNotifier extends AbstractStatusChangeNotifier {

    private static final Logger log = LoggerFactory.getLogger(AdminNotifier.class);

    /**
    * 消息模板
    */
    private static final String template = "<<<%s>>> \n 【服务名】: %s(%s) \n 【状态】: %s(%s) \n 【服务ip】: %s \n 【详情】: %s";

    private String titleAlarm = "系统告警";

    private String titleNotice = "系统通知";

    private String[] ignoreChanges = new String[]{"UNKNOWN:UP","DOWN:UP","OFFLINE:UP"};

    public AdminNotifier(InstanceRepository repository) {
    super(repository);
    }

    @Override
    protected boolean shouldNotify(InstanceEvent event, Instance instance) {
    if (!(event instanceof InstanceStatusChangedEvent)) {
    return false;
    } else {
    InstanceStatusChangedEvent statusChange = (InstanceStatusChangedEvent)event;
    String from = this.getLastStatus(event.getInstance());
    String to = statusChange.getStatusInfo().getStatus();
    return Arrays.binarySearch(this.ignoreChanges, from + ":" + to) < 0 && Arrays.binarySearch(this.ignoreChanges, "*:" + to) < 0 && Arrays.binarySearch(this.ignoreChanges, from + ":*") < 0;
    }
    }


    @Override
    protected Mono<Void> doNotify(InstanceEvent event, Instance instance) {

    return Mono.fromRunnable(() -> {

    if (event instanceof InstanceStatusChangedEvent) {
    log.info("Instance {} ({}) is {}", instance.getRegistration().getName(),
    event.getInstance(),
    ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus());

    String status = ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus();
    String messageText = null;
    switch (status) {
    // 健康检查没通过
    case "DOWN":
    log.info("发送 健康检查没通过 的通知!");
    messageText = String
    .format(template,titleAlarm, instance.getRegistration().getName(), event.getInstance(),
    ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(), "健康检查没通过通知",
    instance.getRegistration().getServiceUrl(), JSONObject.toJSONString(instance.getStatusInfo().getDetails()));
    log.info(messageText);
    break;
    // 服务离线
    case "OFFLINE":
    log.info("发送 服务离线 的通知!");
    messageText = String
    .format(template,titleAlarm, instance.getRegistration().getName(), event.getInstance(),
    ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(), "服务离线通知",
    instance.getRegistration().getServiceUrl(), JSONObject.toJSONString(instance.getStatusInfo().getDetails()));
    log.info(messageText);
    break;
    //服务上线
    case "UP":
    log.info("发送 服务上线 的通知!");
    messageText = String
    .format(template,titleNotice, instance.getRegistration().getName(), event.getInstance(),
    ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(), "服务上线通知",
    instance.getRegistration().getServiceUrl(), JSONObject.toJSONString(instance.getStatusInfo().getDetails()));
    log.info(messageText);
    break;
    // 服务未知异常
    case "UNKNOWN":
    log.info("发送 服务未知异常 的通知!");
    messageText = String
    .format(template,titleAlarm, instance.getRegistration().getName(), event.getInstance(),
    ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(), "服务未知异常通知",
    instance.getRegistration().getServiceUrl(), JSONObject.toJSONString(instance.getStatusInfo().getDetails()));
    log.info(messageText);
    break;
    default:
    break;
    }
    } else {
    log.info("Instance {} ({}) {}", instance.getRegistration().getName(), event.getInstance(),
    event.getType());
    }
    });
    }
    }

Actuator + Admin + nacos + Sentinel

Sentinel 安装

  • 使用 Sentinel 把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

首先下载控制台 jar,这是一个 Spring boot 工程,下载好了之后,直接使用 Spring boot 命令启动即可。

官方下载地址:https://github.com/alibaba/Sentinel/releases

执行启动命令:nohup java -Dserver.port=9999 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-2.0.0-alpha-preview.jar >/dev/null &

访问浏览器:http://localhost:9999/#/dashboard/home ,默认登陆用户名密码 sentinel/sentinel。

Admin Client

  • 创建一个 Spring Boot 模块。

依赖项

1
2
3
4
5
6
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
1
2
3
4
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR1</spring-cloud.version>
</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
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
<dependencies>
<!-- spring boot web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- nacos discovery -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.4.RELEASE</version>
</dependency>
<!-- spring boot admin client -->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>2.2.4</version>
</dependency>
<!-- spring boot security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- spring boot actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- junit5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<!-- sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<version>1.7.1</version>
</dependency>
<!-- alibaba nacos config -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2.2.4.RELEASE</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
</plugin>
</plugins>
</build>

配置文件

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
## 端口号
server:
port: 8093
spring:
## 应用程序名称
application:
name: nacos-consumer
security:
## 自定义用户
user:
name: sihai
password: root
roles: ADMIN
cloud:
nacos:
## nacos 访问地址
server-addr: localhost:8848
## 开启注册
discovery:
register-enabled: true
sentinel:
transport:
## sentinel 控制台地址
dashboard: localhost:9999
## sentinel 端口
port: 8819
log:
#日志输出地址
dir: logs/sentinel
datasource:
ds:
nacos:
data-id: sentinel-rule
group-id: DEFAULT_GROUP
rule-type: flow
redis:
password:
boot:
admin:
client:
## admin 服务端地址
url: localhost:8091
management:
info:
git:
## 默认 simple 简略信息
mode: full
endpoints:
web:
## 跨域请求
cors:
allowed-origins: '*'
exposure:
## 暴露 Actuator 全部可使用端点
include: '*'
## 修改请求前缀
# base-path: /
endpoint:
health:
## 显示信息
show-details: when_authorized
## 所需角色
roles: ADMIN
## 自定义健康指示器
status:
order: FATAL,DOWN,OUT_OF_SERVER,UP,UNKNOWN
## 自定义响应码
http-mapping:
FATAL: 503
shutdown:
## 打开远程暂停服务端点
enabled: true
info:
app:
encoding: @project.build.sourceEncoding@
java:
source: @java.version@
target: @java.version@
author:
name: sihai

配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 应用信息配置
*/
@Component
public class AppInfoConfig implements InfoContributor {
@Override
public void contribute(Info.Builder builder) {
Map<String, String> link = new HashMap<>();
link.put("site", "https://tsihai.github.io/");
link.put("site-2", "https://github.com/Tsihai");
builder.withDetail("link", link);
}
}
1
2
3
4
5
6
7
8
9
10
/**
* 健康状态配置
*/
@Component
public class HealthConfig implements HealthIndicator {
@Override
public Health health() {
return Health.up().withDetail("msg", "正常").build();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 安全配置
*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// 配置拦截请求,拦截所有端点
http.requestMatcher(EndpointRequest.toAnyEndpoint())
// 授权请求
.authorizeRequests()
// 设置权限
.anyRequest().hasRole("ADMIN")
.and()
// 登陆方式
.httpBasic()
.and()
// 禁用 csrf
.csrf().disable();
}
}

启动类

1
2
3
4
5
6
7
8
9
10
11
12
@SpringBootApplication
public class NacosConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(NacosConsumerApplication.class, args);
}

@Bean
@LoadBalanced // 开启负载均衡
RestTemplate restTemplate() {
return new RestTemplate();
}
}

接口

1
2
3
4
5
6
7
8
9
10
11
@RestController
public class HelloController {

@Autowired
RestTemplate restTemplate;

@GetMapping("/hello")
public String hello() {
return restTemplate.getForObject("http://nacos-server/hello", String.class);
}
}

Admin Server

  • 创建一个 Spring Boot 模块。

依赖项

1
2
3
4
5
6
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
1
2
3
4
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR1</spring-cloud.version>
</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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>

<!-- nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- spring boot admin -->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
<version>2.3.1</version>
</dependency>
<!--监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 邮箱监控 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
<version>2.7.10</version>
</dependency>
<!-- json -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.28</version>
</dependency>
</dependencies>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependencyManagement>
<dependencies>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-dependencies</artifactId>
<version>2.3.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.4.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
1
2
3
4
5
6
7
8
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

配置文件

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
server:
port: 8091
spring:
application:
name: nacos-server
boot:
admin:
instance-auth:
default-user-name: sihai
default-password: root
notify:
mail:
from: 1075991706@qq.com
to: 13169186610@163.com
ignore-changes:
## 邮箱监控
mail:
host: smtp.qq.com
port: 465
username: 1075991706@qq.com
password: iukjuhuybkrpfjcd
default-encoding: utf-8
properties:
mail:
smtp:
socketFactory:
class: javax.net.ssl.SSLSocketFactory
cloud:
nacos:
server-addr: localhost:8848
management:
endpoint:
health:
show-details: always
endpoints:
web:
exposure:
include: '*'
cors:
allowed-origins: '*'
logging:
file:
name: /home/java/admin.log

配置类

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
package com.sihai.nacosserver.config;

import com.alibaba.fastjson.JSONObject;
import de.codecentric.boot.admin.server.domain.entities.Instance;
import de.codecentric.boot.admin.server.domain.entities.InstanceRepository;
import de.codecentric.boot.admin.server.domain.events.InstanceEvent;
import de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;
import de.codecentric.boot.admin.server.notify.AbstractStatusChangeNotifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import java.util.Arrays;

@Component
public class AdminNotifier extends AbstractStatusChangeNotifier {

private static final Logger log = LoggerFactory.getLogger(AdminNotifier.class);

/**
* 消息模板
*/
private static final String template = "<<<%s>>> \n 【服务名】: %s(%s) \n 【状态】: %s(%s) \n 【服务ip】: %s \n 【详情】: %s";

private String titleAlarm = "系统告警";

private String titleNotice = "系统通知";

private String[] ignoreChanges = new String[]{"UNKNOWN:UP","DOWN:UP","OFFLINE:UP"};

public AdminNotifier(InstanceRepository repository) {
super(repository);
}

@Override
protected boolean shouldNotify(InstanceEvent event, Instance instance) {
if (!(event instanceof InstanceStatusChangedEvent)) {
return false;
} else {
InstanceStatusChangedEvent statusChange = (InstanceStatusChangedEvent)event;
String from = this.getLastStatus(event.getInstance());
String to = statusChange.getStatusInfo().getStatus();
return Arrays.binarySearch(this.ignoreChanges, from + ":" + to) < 0 && Arrays.binarySearch(this.ignoreChanges, "*:" + to) < 0 && Arrays.binarySearch(this.ignoreChanges, from + ":*") < 0;
}
}


@Override
protected Mono<Void> doNotify(InstanceEvent event, Instance instance) {

return Mono.fromRunnable(() -> {

if (event instanceof InstanceStatusChangedEvent) {
log.info("Instance {} ({}) is {}", instance.getRegistration().getName(),
event.getInstance(),
((InstanceStatusChangedEvent) event).getStatusInfo().getStatus());

String status = ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus();
String messageText = null;
switch (status) {
// 健康检查没通过
case "DOWN":
log.info("发送 健康检查没通过 的通知!");
messageText = String
.format(template,titleAlarm, instance.getRegistration().getName(), event.getInstance(),
((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(), "健康检查没通过通知",
instance.getRegistration().getServiceUrl(), JSONObject.toJSONString(instance.getStatusInfo().getDetails()));
log.info(messageText);
break;
// 服务离线
case "OFFLINE":
log.info("发送 服务离线 的通知!");
messageText = String
.format(template,titleAlarm, instance.getRegistration().getName(), event.getInstance(),
((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(), "服务离线通知",
instance.getRegistration().getServiceUrl(), JSONObject.toJSONString(instance.getStatusInfo().getDetails()));
log.info(messageText);
break;
//服务上线
case "UP":
log.info("发送 服务上线 的通知!");
messageText = String
.format(template,titleNotice, instance.getRegistration().getName(), event.getInstance(),
((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(), "服务上线通知",
instance.getRegistration().getServiceUrl(), JSONObject.toJSONString(instance.getStatusInfo().getDetails()));
log.info(messageText);
break;
// 服务未知异常
case "UNKNOWN":
log.info("发送 服务未知异常 的通知!");
messageText = String
.format(template,titleAlarm, instance.getRegistration().getName(), event.getInstance(),
((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(), "服务未知异常通知",
instance.getRegistration().getServiceUrl(), JSONObject.toJSONString(instance.getStatusInfo().getDetails()));
log.info(messageText);
break;
default:
break;
}
} else {
log.info("Instance {} ({}) {}", instance.getRegistration().getName(), event.getInstance(),
event.getType());
}
});
}
}

使用