在微服务场景中多个服务状态的监听变得复杂,如果直接使用Spring Boot Actuator 进行服务的监控也不是很方便,因为集群中的服务肯定不止一个,将这些数据汇总展示就很不方便,本文利用Spring Boot Admin简洁快速的实现集群服务健康监控,通过简单的配置实现钉钉机器人通知(你也可以改成其他的类型)
一、环境配置
Springboot 2.1.1
springCloud 2.1.1
Spring Boot Admin 2.1.6
dingtalk 1.0.1
二、服务端搭建
因为此项目是微服务版所以我们此次先从服务端开始搭建
1.导入依赖
<!--SpringBoot web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--SpringCloud Eureka客户端依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/de.codecentric/spring-boot-admin-starter-server -->
<!--这个是 spring-boot-admin 服务端的依赖-->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
<version>2.1.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.aliyun/alibaba-dingtalk-service-sdk -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>alibaba-dingtalk-service-sdk</artifactId>
<version>1.0.1</version>
</dependency>
2.编写配置
server:
port: 8788
spring:
application:
name: springboot-admin-server
admin:
environment: test(环境名称)
ding-talk-token: 钉钉机器人的token
check: false #是否开启发送
# eureka相关配置
eureka:
client:
service-url:
defaultZone: http://localhost:5550/eureka
#客户端每隔30秒从Eureka服务上更新一次服务信息
registry-fetch-interval-seconds: 30
#需要将我的服务注册到eureka上
register-with-eureka: true
#需要检索服务
fetch-registry: true
#心跳检测检测与续约时间
instance:
#告诉服务端,如果我10s之内没有给你发心跳,就代表我故障了,将我剔除掉,默认90s
#Eureka服务端在收到最后一次心跳之后等待的时间上限,单位为秒,超过则剔除(客户端告诉服务端按照此规则等待自己)
lease-expiration-duration-in-seconds: 10
#每隔2s向服务端发送一次心跳,证明自已依然活着,默认30s
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(客户端告诉服务端自己会按照该规则)
lease-renewal-interval-in-seconds: 2
# 这就是我们的监控检查地址
health-check-url-path: /actuator/health
#使用注册中心后,admin服务端也可以监控自身服务状况
management:
endpoints:
web:
exposure:
#开放所有页面节点 默认只开启了health、info两个节点
include: '*'
endpoint:
health:
#显示健康具体信息 默认不会显示详细信息
show-details: always
3.编写消息通知类
@Component
@Slf4j
public class CustomNotifier extends AbstractEventNotifier {
@Value("${spring.admin.ding-talk-token}")
private String dingTalkToken;
@Value("${spring.admin.environment}")
private String environment;
@Value("${spring.admin.check}")
private boolean check;
/**
* 消息模板
*/
private static final String template = "业务报警!\n各位同事大家好,现有 %s 环境 以下服务发生变动:\n服务名:%s(%s) \n状态:%s(%s) \n服务ip:%s \n预警时间:%s";
protected CustomNotifier(InstanceRepository repository) {
super(repository);
}
@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());
Date date = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
String format = simpleDateFormat.format(date);
String status = ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus();
String messageText = null;
switch (status) {
// // 服务离线
case "OFFLINE":
log.info("发送 服务离线 的通知!");
messageText = String.format(template, environment, instance.getRegistration().getName(),event.getInstance(), ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(),"服务离线",instance.getRegistration().getServiceUrl(),format);
this.sendMessage(messageText);
break;
//服务上线
case "UP":
log.info("发送 服务上线 的通知!");
messageText = String.format(template,environment, instance.getRegistration().getName(),event.getInstance(), ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(),"服务上线",instance.getRegistration().getServiceUrl(),format);
this.sendMessage(messageText);
break;
// 服务未知异常
case "UNKNOWN":
log.info("发送 服务未知异常 的通知!");
messageText = String.format(template,environment, instance.getRegistration().getName(),event.getInstance(), ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(),"服务未知异常",instance.getRegistration().getServiceUrl(),format);
this.sendMessage(messageText);
break;
default:
break;
}
} else {
log.info("Instance {} ({}) {}", instance.getRegistration().getName(), event.getInstance(),
event.getType());
}
});
}
/**
* 发送消息
* @param messageText
*/
private void sendMessage(String messageText) {
Long timestamp = System.currentTimeMillis();
String secret = "钉钉机器人密钥";
String stringToSign = timestamp + "\n" + secret;
Mac mac = null;
String sign = null;
try {
mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256"));
byte[] signData = mac.doFinal(stringToSign.getBytes("UTF-8"));
//签名数据
sign = URLEncoder.encode(new String(Base64.encodeBase64(signData)),"UTF-8");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
System.out.println(messageText);
DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/robot/send?access_token="+dingTalkToken+"×tamp="+timestamp+"&sign="+sign);
OapiRobotSendRequest request = new OapiRobotSendRequest();
request.setMsgtype("text");
OapiRobotSendRequest.Text text = new OapiRobotSendRequest.Text();
text.setContent(messageText);
request.setText(text);
try {
if (check) {
client.execute(request);
}
} catch (ApiException e) {
log.info("[ERROR] sendMessage", e);
}
}
}
三、客户端搭建
在微服务场景下我们只需要引入依赖,配置一下就可以了
1.导入依赖
<!--SpringCloud Eureka客户端依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>2.1.6</version>
</dependency>
2.编写配置
eureka:
client:
serviceUrl:
defaultZone: http://localhost:5550/eureka/
registry-fetch-interval-seconds: 60 # 每隔60秒就去注册中心拉取注册信息
instance:
lease-renewal-interval-in-seconds: 2 # 每隔2秒,向服务端发送一次心跳
lease-expiration-duration-in-seconds: 6 # 如果6秒之内没给我发送心跳,则表示服务挂了,剔除该服务
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: ALWAYS
最终效果
界面
通知效果