目录

Spring Boot Admin 监控告警服务

邮件警报

钉钉警报


Spring Boot Admin 监控告警服务

在微服务架构下,服务的数量少则几十,多则上百,所以对服务的监控必不可少。如果是以前的单体项目,启动了多少个项目是固定的,可以通过第三方监控工具对其进行监控,然后实时告警。

在微服务下,由于服务数量太多,并且可以随时扩展,这个时候第三方的监控功能就不适用了,不过我们可以通过 Spring Boot Admin 连接注册中心来查看服务状态,这个只能在页面查看。

很多时候我们更希望能够自动监控,通过邮件告警,比如发出“某某服务下线了”这样的功能。在 Spring Boot Admin 中其实已经有这样的功能了,我们只需要配置一些邮件的信息就可以使用。

 

邮件警报

引入邮件所需要的依赖,代码如下所示:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

然后在配置文件中增加邮件服务器的信息:

spring:
  mail:
    host: smtp.qq.com
    username: xxx.qq.com
    password: qq 邮箱的授权码
    properties:
      smtp:
        auth: true
        starttls:
          enable: true
          required: true
  boot:
    admin:
      notify:
        mail:
          # 发送给谁
          to: xxx@126.com
          # 是谁发送出去的
          from: xxx.qq.com

配置好之后就可以收到监控邮件了。

 

钉钉警报

目前很多公司都是用钉钉来办公,通过钉钉可以发送监控消息,非常方便。Spring Boot Admin 中默认是没有钉钉警报这个功能的,我们可以自己去扩展使用钉钉来发送监控信息。

创建钉钉机器人,拿到 token

钉钉官方提供了统一的 SDK ,使用 SDK 可以便捷的调用服务端 API,但没有放到公共 maven 仓库中,需要自行下载后导入到项目,或者上传到自己的搭建的nexus私服中:

<dependency>
    <groupId>com.dingtalk</groupId>
    <artifactId>taobao-sdk-java-auto</artifactId>
    <version>20210219</version>
</dependency>

通过扩展 AbstractEventNotifierAbstractStatusChangeNotifier 。在 blog-admin-server 工程中编写一个自定义的通知器:

@Component
public class DingTalkNotifier extends AbstractEventNotifier {

    private static final Logger logger = LoggerFactory.getLogger(DingTalkNotifier.class);
 
    /**
     * 消息模板
     */
    private static final String template = "服务名:%s(%s) \n状态:%s(%s) \n服务ip:%s";
 
    @Value("${spring.admin.ding-talk-token}")
    private String dingTalkToken;
 
 
    public DingTalkNotifier(InstanceRepository repository) {
        super(repository);
    }
 
    @Override
    protected Mono<Void> doNotify(InstanceEvent event, Instance instance) {
        return Mono.fromRunnable(() -> {
            if (event instanceof InstanceStatusChangedEvent) {
                logger.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":
                        logger.info("发送 健康检查没通过 的通知!");
                        messageText = String.format(template, instance.getRegistration().getName(),event.getInstance(), ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(),"健康检查没通过",instance.getRegistration().getServiceUrl());
                        this.sendMessage(messageText);
                        break;
                    // 服务离线
                    case "OFFLINE":
                        logger.info("发送 服务离线 的通知!");
                        messageText = String.format(template, instance.getRegistration().getName(),event.getInstance(), ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(),"服务离线",instance.getRegistration().getServiceUrl());
                        this.sendMessage(messageText);
                        break;
                    //服务上线
                    case "UP":
                        logger.info("发送 服务上线 的通知!");
                        messageText = String.format(template, instance.getRegistration().getName(),event.getInstance(), ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(),"服务上线",instance.getRegistration().getServiceUrl());
                        this.sendMessage(messageText);
                        break;
                    // 服务未知异常
                    case "UNKNOWN":
                        logger.info("发送 服务未知异常 的通知!");
                        messageText = String.format(template, instance.getRegistration().getName(),event.getInstance(), ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(),"服务未知异常",instance.getRegistration().getServiceUrl());
                        this.sendMessage(messageText);
                        break;
                    default:
                        break;
                }
            } else {
                logger.info("Instance {} ({}) {}", instance.getRegistration().getName(), event.getInstance(),
                    event.getType());
            }
        });
    }
 
    /**
     * 发送消息
     * @param messageText
     */
    private void sendMessage(String messageText){
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/robot/send?access_token=" + dingTalkToken);
        OapiRobotSendRequest request = new OapiRobotSendRequest();
        request.setMsgtype("text");
        OapiRobotSendRequest.Text text = new OapiRobotSendRequest.Text();
        text.setContent(messageText);
        request.setText(text);
        try {
            client.execute(request);
        } catch (ApiException e) {
            logger.info("[ERROR] sendMessage", e);
        }
    }
}

目前我们已经配置好了警报功能,当服务上下线的时候就会发送警报,当网络发生波动的时候也有可能会触发警报。当前的警报只会发送一次,也就是说你的服务挂掉之后你会收到一条警报。如果是网络引起的警报也会收到一条警报,这个时候你就无法判断服务是不是真正出问题了。

我们的需求也很简单,当服务真正挂掉的时候,警报可以发送多条,比如每 10 秒发送一条,这样连续性的警报就很容易让维护人员关注和辨别。

可以通过配置 RemindingNotifier 来实现上面的需求:

@Autowired
private DingTalkNotifier dingTalkNotifier;
@Primary
@Bean(initMethod = "start", destroyMethod = "stop")
public RemindingNotifier remindingNotifier(InstanceRepository repository) {
    RemindingNotifier notifier = new RemindingNotifier(dingTalkNotifier, repository);
    notifier.setReminderPeriod(Duration.ofSeconds(20));
    notifier.setCheckReminderInverval(Duration.ofSeconds(20));
    return notifier;
}

这里设置的时间间隔是 20 秒一次,当服务出问题的时候就会每隔 20 秒发送一次警报,直到服务正常才会停止。