springbootadmin2实例状态监控详解(代码片段)

阿提说说 阿提说说     2023-04-04     727

关键词:

其他相关文章:

  1. Spring Boot Admin 参考指南
  2. SpringBoot Admin服务离线、不显示健康信息的问题
  3. Spring Boot Admin2 @EnableAdminServer的加载
  4. Spring Boot Admin2 AdminServerAutoConfiguration详解

在微服务中集成Spring Boot Admin 的主要作用之一就是用来监控服务的实例状态,并且最好是当服务DOWN或者OFFLINE的时候发消息提醒,SBA2 提供了很多提醒方式,并且SBA2 已经集成了钉钉,只要进行少量配置即可将状态变更发送到钉钉,详见我的另外一篇文章《Spring Boot Admin 参考指南》。

SBA2 接入飞书

这里我要说明如何进行自定义提醒,将飞书提醒集成到SBA2中,顺便看看SBA2的状态监控具体是如何实现的。

  1. 定义配置类

FeiShuNotifierConfiguration

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnProperty(prefix = "spring.boot.admin.notify.feishu",  name = "enabled", havingValue = "true")
	@AutoConfigureBefore( AdminServerNotifierAutoConfiguration.NotifierTriggerConfiguration.class, AdminServerNotifierAutoConfiguration.CompositeNotifierConfiguration.class )
	@Lazy(false)
	public static class FeiShuNotifierConfiguration 

		@Bean
		@ConditionalOnMissingBean
		@ConfigurationProperties("spring.boot.admin.notify.feishu")
		public FeiShuNotifier feiShuNotifier(InstanceRepository repository,
											 NotifierProxyProperties proxyProperties) 
			return new FeiShuNotifier(repository, createNotifierRestTemplate(proxyProperties));
		

	

这里是模仿SBA2 定义其他通知的方式,InstanceRepository 用于实例持久化操作,NotifierProxyProperties 是通知的HTTP代理配置

  1. 定义消息提醒实现
public class FeiShuNotifier extends AbstractStatusChangeNotifier implements AlarmMessage 

	private static final String DEFAULT_MESSAGE = " 服务名称:#instance.registration.name \\n 服务实例:#instance.id \\n 服务URL:#instance.registration.serviceUrl \\n 服务状态:【#event.statusInfo.status】 \\n 发送时间:#time";

	private final SpelExpressionParser parser = new SpelExpressionParser();

	private RestTemplate restTemplate;

	private String webhookUrl;

	private String secret;

	private Expression message;


	public FeiShuNotifier(InstanceRepository repository, RestTemplate restTemplate) 
		super(repository);
		this.restTemplate = restTemplate;
		this.message = parser.parseExpression(DEFAULT_MESSAGE, ParserContext.TEMPLATE_EXPRESSION);
	

	@Override
	protected Mono<Void> doNotify(InstanceEvent event, Instance instance) 
		return Mono.fromRunnable(() -> sendNotify(event, instance));
	

	@Override
	protected void updateLastStatus(InstanceEvent event) 
		//原有的更新最后实例状态,在重启后会删除最后状态,导致实例重启后会过滤掉UNKNOWN:UP通知,这里在重启注册后,将最后的状态重新更新会实例中
		//如此实例的变化状态为OFFLINE:UP
		//还有一种办法是:重写shouldNotify(),去掉UNKNOWN:UP,不过滤该通知,也能够收到UP通知,但如此会在Admin重启的时候,所有服务的通知都会发一遍
		if (event instanceof InstanceDeregisteredEvent) 
			String lastStatus = getLastStatus(event.getInstance());
			StatusInfo statusInfo = StatusInfo.valueOf(lastStatus);
			InstanceStatusChangedEvent instanceStatusChangedEvent = new InstanceStatusChangedEvent(event.getInstance(), event.getVersion(), statusInfo);
			super.updateLastStatus(instanceStatusChangedEvent);
		
		if (event instanceof InstanceStatusChangedEvent) 
			super.updateLastStatus(event);
		
	

	private void sendNotify(InstanceEvent event, Instance instance) 
		sendData(getText(event, instance));
	

	@Override
	public void sendData(String content) 
		if (!isEnabled()) 
			return;
		
		Map<String, Object> message = createMessage(content);
		doSendData(JSONObject.toJSONString(message));
	

	private void doSendData(String message) 
		sendWebData(message);
	

	private void sendWebData(String message) 
		HttpHeaders headers = new HttpHeaders();
		headers.setContentType(MediaType.APPLICATION_JSON);
		restTemplate.postForEntity(webhookUrl, new HttpEntity<>(message, headers), Void.class);
	

	protected Map<String, Object> createMessage(String content) 
		Map<String, Object> messageJson = new HashMap<>();
		messageJson.put("msg_type", "text");

		Map<String, Object> text = new HashMap<>();
		text.put("text", content);
		messageJson.put("content", text);
		Long timestamp = System.currentTimeMillis() / 1000;
		messageJson.put("timestamp", timestamp);
		messageJson.put("sign", getSign(timestamp));

		return messageJson;
	

	private String getText(InstanceEvent event, Instance instance) 
		Map<String, Object> root = new HashMap<>();
		root.put("event", event);
		root.put("instance", instance);
		root.put("lastStatus", getLastStatus(event.getInstance()));
		root.put("time", DateUtil.now());
		StandardEvaluationContext context = new StandardEvaluationContext(root);
		context.addPropertyAccessor(new MapAccessor());
		return message.getValue(context, String.class);
	

	private String getSign(Long timestamp) 
		try 
			String stringToSign = timestamp + "\\n" + secret;
			Mac mac = Mac.getInstance("HmacSHA256");
			mac.init(new SecretKeySpec(stringToSign.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
			byte[] signData = mac.doFinal(new byte[]);
			return new String(Base64.encodeBase64(signData));
		
		catch (Exception ex) 
			ex.printStackTrace();
		
		return "";
	

	public void setRestTemplate(RestTemplate restTemplate) 
		this.restTemplate = restTemplate;
	

	public String getWebhookUrl() 
		return webhookUrl;
	

	public void setWebhookUrl(String webhookUrl) 
		this.webhookUrl = webhookUrl;
	

	public String getSecret() 
		return secret;
	

	public void setSecret(String secret) 
		this.secret = secret;
	

	public String getMessage() 
		return message.getExpressionString();
	

	public void setMessage(String message) 
		this.message = parser.parseExpression(message, ParserContext.TEMPLATE_EXPRESSION);
	

这里继承了AbstractStatusChangeNotifier 用来处理实例状态变更通知,AlarmMessage 是我自己将消息发送给抽象了出来,以便其他告警也能调用。其他都比较简单,飞书的群提醒请参考飞书文档

另外,这里重写了updateLastStatus方法,在取消注册的时候将实例的最后一次状态重新更新到实例中,因为在测试中,实例如果重启,实例状态变为OFFLINE,但重启完成后,却没有收到UP的消息,查看源码后,SBA2在实例取消注册的时候,删除实例的最后一次状态,导致实例的状态变成UNKNOWN,而SBA2里面shouldNotify方法又会过滤UNKNOWN:UP的状态变更。后面会详细看下这部分的源码。

通过如上两步即可接入飞书,看效果图:

状态监控源码分析

从《Spring Boot Admin2 AdminServerAutoConfiguration详解》这篇文章我们可以知道,在SBA2启动的时候,会加载StatusUpdaterStatusUpdateTrigger,前者用于更新实例状态,后者用来触发状态更新,这两个类我将从头至下分部说明。

StatusUpdateTrigger

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

	private final StatusUpdater statusUpdater;

	private final IntervalCheck intervalCheck;

	public StatusUpdateTrigger(StatusUpdater statusUpdater, Publisher<InstanceEvent> publisher) 
		super(publisher, InstanceEvent.class);
		this.statusUpdater = statusUpdater;
		this.intervalCheck = new IntervalCheck("status", this::updateStatus);
	

StatusUpdateTrigger 继承自AbstractEventHandler类,通过构造函数,传入StatusUpdater 更新状态实例,Publisher 接收一个InstanceEvent事件。
super(publisher, InstanceEvent.class) 调用父类构造方法,将Publisher和要关注的事件InstanceEvent传入。
this.intervalCheck = new IntervalCheck(“status”, this::updateStatus) 创建了一个定时任务用来检查实例状态

接下来看下StatusUpdateTrigger 的父类AbstractEventHandler

AbstractEventHandler.start

	public void start() 
		this.scheduler = this.createScheduler();
		this.subscription = Flux.from(this.publisher).subscribeOn(this.scheduler).log(this.log.getName(), Level.FINEST)
				.doOnSubscribe((s) -> this.log.debug("Subscribed to  events", this.eventType)).ofType(this.eventType)
				.cast(this.eventType).transform(this::handle)
				.retryWhen(Retry.indefinitely().doBeforeRetry((s) -> this.log.warn("Unexpected error", s.failure())))
				.subscribe();
	

AbstractEventHandler 的start 方法会在StatusUpdateTrigger 初始化@Bean(initMethod = "start", destroyMethod = "stop") 中被调用,这里其创建了一个定时任务,并订阅了指定的事件类型eventType,如果监听到了感兴趣的事件,会调用handle方法,该方法由子类实现

StatusUpdateTrigger.handle

	@Override
	protected Publisher<Void> handle(Flux<InstanceEvent> publisher) 
		return publisher
				.filter((event) -> event instanceof InstanceRegisteredEvent
						|| event instanceof InstanceRegistrationUpdatedEvent)
				.flatMap((event) -> updateStatus(event.getInstance()));
	

在StatusUpdateTrigger 中 如果事件类型是InstanceRegisteredEvent(实例注册事件)或者InstanceRegistrationUpdatedEvent(实例更新事件),会调用更新实例状态方法
updateStatus

StatusUpdateTrigger.updateStatus

	protected Mono<Void> updateStatus(InstanceId instanceId) 
		return this.statusUpdater.updateStatus(instanceId).onErrorResume((e) -> 
			log.warn("Unexpected error while updating status for ", instanceId, e);
			return Mono.empty();
		).doFinally((s) -> this.intervalCheck.markAsChecked(instanceId));
	

StatusUpdateTrigger.updateStatus 调用了构造函数传入的StatusUpdater Bean 的updateStatus,执行具体的查询实例状态、更新实例状态操作,最后更新该实例的最后检查时间。

StatusUpdateTrigger.start/stop

	@Override
	public void start() 
		super.start();
		this.intervalCheck.start();
	

	@Override
	public void stop() 
		super.stop();
		this.intervalCheck.stop();
	

StatusUpdateTrigger 最后是Bean初始化调用方法start和销毁时调用的stop方法,分别用于启动其父类AbstractEventHandler的事件监听,和 IntervalCheck 的定时状态检查任务

StatusUpdater

StatusUpdater 是真正去查询实例状态,并更新实例的类,我们在StatusUpdateTrigger.updateStatus中已经看到其会请求StatusUpdater.updateStatus

	public Mono<Void> updateStatus(InstanceId id) 
		return this.repository.computeIfPresent(id, (key, instance) -> this.doUpdateStatus(instance)).then();

	

repository.computeIfPresent 会调用EventsourcingInstanceRepository.computeIfPresent,表示实例id存在的话,执行doUpdateStatus并更新状态,doUpdateStatus 会查询实例最新状态,并通过Instance.withStatusInfo包装成一个新的Instance 对象。

EventsourcingInstanceRepository.computeIfPresent

	@Override
	public Mono<Instance> computeIfPresent(InstanceId id,
			BiFunction<InstanceId, Instance, Mono<Instance>> remappingFunction) 
		return this.find(id).flatMap((application) -> remappingFunction.apply(id, application)).flatMap(this::save)
				.retryWhen(this.retryOptimisticLockException);
	

其中this::save 用来保存实例事件,此处为状态变更事件

EventsourcingInstanceRepository.save

	public Mono<Instance> save(Instance instance) 
		return this.eventStore.append(instance.getUnsavedEvents()).then(Mono.just(instance.clearUnsavedEvents()));
	

eventStore 实际调用的是在AdminServerAutoConfiguration中加载的InMemoryEventStore

InMemoryEventStore.append

	public Mono<Void> append(List<InstanceEvent> events) 
		return super.append(events).then(Mono.fromRunnable(() -> this.publish(events)));
	

该方法将在事件保存后,发送一个Publish,这样实现了AbstractEventHandler<InstanceEvent>的类就能监听到该变更事件。

AdminServerNotifierAutoConfiguration

当SBA2中存在通知相关的Notifier Bean时,会开启NotificationTrigger,用来发送变更事件通知

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnBean(Notifier.class)
	@Lazy(false)
	public static class NotifierTriggerConfiguration 

		@Bean(initMethod = "start", destroyMethod = "stop")
		@ConditionalOnMissingBean(NotificationTrigger.class)
		public NotificationTrigger notificationTrigger(Notifier notifier, Publisher<InstanceEvent> events) 
			return new NotificationTrigger(notifier, events);
		

	

NotificationTrigger 道理同 StatusUpdateTrigger 。

NotificationTrigger.sendNotifications

	protected Mono<Void> sendNotifications(InstanceEvent event) 
		return this.notifier.notify(event).doOnError((e) -> log.warn("Couldn't notify for event  ", event, e))
				.onErrorResume((e) -> Mono.empty()查看详情  

zabbix通过进程名监控进程状态配置详解

Zabbix通过进程名监控进程状态配置详解有时候我们只能通过进程名监控一个进程是否停掉了,因为有的进程并没有对外提供端口号,以下记录了下详细步骤,通过这个示例会学到很多zabbix核心配置相关的东西。 总的来说,配... 查看详情

详解supervisor进程守护监控(代码片段)

...,能将一个普通的命令行进程变为后台daemon,并监控进程状态,异常退出时能自动重启。v介绍Supervisor-supervisord运行Sup 查看详情

nginx总结启用nginxstatus及状态参数详解(代码片段)

...hangweizhong/category/1529997.html今天简单介绍下如何监控Nginx的状态。其实要监控Nginx的状态非常简单,它内建了一个状态页,只需修改Nginx配置启用Status即可,对于想了解nginx的状态以及监控nginx非常有帮助。 1.启用nginxstatus配置大... 查看详情

springboot2.x搭建springbootadmin2.x

1说明全部配置基于1.8.0_111当前SpringBoot使用2.0.5SpringBootAdmin基于Eureka进行Client发现,Eureka搭建参见SpringBoot2.x搭建EurekaSpringBootAdmin项目文档参见SpringBootAdmin参考文档2创建项目在SpringBoot项目生成器中,输入Group和Artifact,如下配置:3... 查看详情

jvm进程状态监控

...的键值不能重复,也就导致了一台主机上只能监控一个jvm实例  以上两点原因导致zabbix通过jmx监控jv 查看详情

es集群关键状态指标(代码片段)

...状态等。2:节点级别:节点级别的监控主要是针对每个ES实例的监控,其中包括每个实例的查询索引指标和物理资源使用指标。3:索引级别:索引级别的监控主要是针对每个索引来说,主要包括每个索引的性能指标。1集群级别... 查看详情

详解java类对象实例化

...Java支持以下基本概念:多态性继承封装抽象化类对象实例方法消息解析在本章中,我们将探讨类和对象这些概念。对象-对象具有状态和行为。例如:狗有状态-颜色,名称,繁殖以及行为,摇头晃脑,... 查看详情

mongodb多纬度监控方法详解

一、mongostat工具方法mongostat是mongdb自带的状态检测工具,在命令行下使用。它会间隔固定时间获取mongodb的当前运行状态,并输出。如果你发现数据库突然变慢或者有其他问题的话,你第一手的操作就考虑采用mongostat来查看mongo的... 查看详情

40张图详解docker容器监控(代码片段)

...业务正常运行,就必须利用工具时刻监控业务的运行状态,容器中的业务也不例外。除了容器自身的监控命令外,还有一些针对容器的动态特征而开发的第三方监控工具。本章将对容器监控及其相关内容进行详解。Doc... 查看详情

aws监控与报警awscloudwatch自动恢复硬件故障实例autorecover

AWS监控与报警awsCloudWatch自动恢复硬件故障实例AutoRecover20180702Chenxin常用项目创建EC2后:需要添加的报警主机状态检查(主机存活)CPU利用率内存使用率(含buffer,cache)磁盘使用率并修改"正常,警报,不足(缺失)"时的邮件告知.cloudwat... 查看详情

jprofiler性能分析工具详解

...据事件和命令的类型返回相对应的数据(线程状态、对象实例、CPU负荷、GC状态信息等)5.JProfilerAgent从JVMTI中得到相应数据后将对其进行计算,最终通过Socket传输给JProfilerGUI中进行展示.https://www.ej-technologies.com/download/jprofiler/files激活... 查看详情

linuxnetstat详解

 Linuxnetstat命令用于显示网络状态。利用netstat指令可让你得知整个Linux系统的网络情况。在InternetRFC标准中,Netstat的定义是:Netstat是在内核中访问网络连接状态及其相关信息的程序,它能提供TCP连接,TCP和UDP监听,进程内存... 查看详情

pythonsubprocess库六个实例详解

这次来说Python的第三方库subprocess库,在python2.4以上的版本commands模块被subprocess取代了。一般当我们在用Python写运维脚本时,需要履行一些Linux ​​shell的​​命令​,Python中subprocess模块就是专门用于调用Linuxshell​​命令,... 查看详情

yarn生产详解

...与调度器协商资源来启动ApplicationMaster(AM)和监控(AM)运行状态,并且失败的时候能够重新启动它,更新分配给一个新的Container容器的进度或者状态,除了资源它不管,它就负责job           &#... 查看详情

netstat命令详解

...显示路由表、实际的网络连接以及每一个网络接口设备的状态信息。语法选项netstat[选项]-a或--all:显示所有连线中的Socket;-A<网络类型>或--<网络类型>:列出该网络类型连线中的相关地址;-c或--continuous:持续列出网络... 查看详情

原生js模拟promise实现详解

...包含resolve和reject两个参数,然后,_Promise构造函数生成的实例有三种状态,分别是pending(初始值)、fullfilled(成功)和rejected(失败),且由pending变为成功或者失败后状态不可逆。resolve和reject函数执行的时候会先判断状态,如... 查看详情

linux监控详解(代码片段)

什么是监控?监控===监测+控制生活中的监控:事故追责运维中的监控:事后追责,事前预警,性能分析,实时报警为什么要使用监控监控是整个产品周期中最重要的一环,及时预警减少故... 查看详情