Spring Boot Admin2 实例状态监控详解

这篇具有很好参考价值的文章主要介绍了Spring Boot Admin2 实例状态监控详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

其他相关文章:

  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 实例状态监控详解

状态监控源码分析

从《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());
	}

this.notifier.notify(event) 表示会调用对应通知类的notify方法,这里已飞书为例,由于飞书继承了AbstractStatusChangeNotifier类,该处会调用AbstractStatusChangeNotifier.notifyAbstractStatusChangeNotifier.notify又会调用其父类AbstractEventNotifier的notify方法。

AbstractStatusChangeNotifier.notify

	public Mono<Void> notify(InstanceEvent event) {
		return super.notify(event).then(Mono.fromRunnable(() -> updateLastStatus(event)));
	}

AbstractEventNotifier.notify

	public Mono<Void> notify(InstanceEvent event) {
		if (!enabled) {
			return Mono.empty();
		}

		return repository.find(event.getInstance()).filter((instance) -> shouldNotify(event, instance))
				.flatMap((instance) -> doNotify(event, instance))
				.doOnError((ex) -> getLogger().error("Couldn't notify for event {} ", event, ex)).then();
	}

AbstractEventNotifier.notify中会通过shouldNotify判断该事件是否应该通知,该方法由子类实现,因此这里父类又调用了子类AbstractStatusChangeNotifier的实现,如果需要通知,则执行具体的doNotify方法。

AbstractStatusChangeNotifier.shouldNotify

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

AbstractStatusChangeNotifier.shouldNotify 采用了二分查找法来判断当前变更状态是否在忽略状态类,<0表示不在忽略状态内,需要通知。

使用二分查找,必须先对元素进行排序

最后这么弯弯圈圈下来,实例的状态变更事件就到了FeiShuNotifier.doNotify中,到此我们对SBA2的实例状态监控的分析就结束了。文章来源地址https://www.toymoban.com/news/detail-402171.html

到了这里,关于Spring Boot Admin2 实例状态监控详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • 【Spring实战】25 Spring Boot Admin 应用

    【Spring实战】25 Spring Boot Admin 应用

    Spring Boot Admin 是一个功能强大的工具,用于监控和管理多个 Spring Boot 应用程序。通过上一篇文章 【Spring实战】24 使用 Spring Boot Admin 管理和监控应用 我们知道了如何去使用 Spring Boot Admin。本文我们将继续介绍 Spring Boot Admin 的各种功能,并提供简单的样例,包括查看健康信息

    2024年01月24日
    浏览(12)
  • Spring Boot+Atomikos进行多数据源的分布式事务管理详解和实例

    Spring Boot+Atomikos进行多数据源的分布式事务管理详解和实例

    背景: 一直零散的使用着Spring Boot 的各种组件和特性,从未系统性的学习和总结,本次借着这个机会搞一波。共同学习,一起进步。哈哈 Atomikos是一个易用、可靠、开放源码的事务管理器,它可以用于管理分布式事务,尤其在微服务架构中非常实用。它支持JTA(Java Transacti

    2024年02月11日
    浏览(10)
  • 【Spring实战】26 使用Spring Security 保护 Spring Boot Admin

    【Spring实战】26 使用Spring Security 保护 Spring Boot Admin

    Spring Boot Admin 是一个用于监控和管理 Spring Boot 应用程序的工具,而 Spring Security 是一个用于提供身份验证和授权的强大框架。本文们将探讨如何将 Spring Boot Admin 与 Spring Security 集成,以确保管理端的安全性。 Spring Boot Admin: Spring Boot Admin 是一个基于Web的用户界面,用于集中监

    2024年01月25日
    浏览(18)
  • 【Spring Boot Admin】使用(整合Spring Security服务,添加鉴权)

    【Spring Boot Admin】使用(整合Spring Security服务,添加鉴权)

    Spring Boot Admin 监控平台 背景:Spring Boot Admin 监控平台不添加鉴权就直接访问的话,是非常不安全的。所以在生产环境中使用时,需要添加鉴权,只有通过鉴权后才能监控客户端服务。本文整合Spring Security进行实现。 pom依赖 yml配置 启动类@EnableAdminServer 安全配置类:SecuritySe

    2024年02月16日
    浏览(28)
  • RabbitMQ和spring boot整合及其他内容

    在现代分布式应用程序的设计中,消息队列系统是不可或缺的一部分,它为我们提供了解耦组件、实现异步通信和确保高性能的手段。RabbitMQ,作为一款强大的消息代理,能够协助我们实现这些目标。在本篇CSDN博客中,我们将探讨一些高级主题,包括RabbitMQ与Spring Boot的整合、

    2024年02月07日
    浏览(11)
  • ELADMIN - 免费开源 admin 后台管理系统,基于 Spring Boot 和 Vue ,包含前端和后端源码

    ELADMIN - 免费开源 admin 后台管理系统,基于 Spring Boot 和 Vue ,包含前端和后端源码

    一款简单好用、功能强大的 admin 管理系统,包含前端和后端源码,分享给大家。 ELADMIN 是一款基于 Spring Boot、Jpa 或 Mybatis-Plus、 Spring Security、Redis、Vue 的前后端分离的后台管理系统。 ELADMIN 的作者在 Github 和 Gitee 上看了很多的项目,发现大多数都是基于 Mybatis , 而基于 Sp

    2024年02月04日
    浏览(12)
  • Spring Boot 如何让你的 bean 在其他 bean 之前完成加载 ?

    Spring Boot 如何让你的 bean 在其他 bean 之前完成加载 ?

    今天有个小伙伴给我出了一个难题:在 SpringBoot 中如何让自己的某个指定的 Bean 在其他 Bean 前完成被 Spring 加载?我听到这个问题的第一反应是,为什么会有这样奇怪的需求? Talk is cheap,show me the code,这里列出了那个想做最先加载的“天选 Bean” 的代码,我们来分析一下:

    2024年02月03日
    浏览(10)
  • Spring Boot |如何让你的 bean 在其他 bean 之前完成加载

    Spring Boot |如何让你的 bean 在其他 bean 之前完成加载

    问题 今天有个小伙伴给我出了一个难题:在 SpringBoot 中如何让自己的某个指定的 Bean 在其他 Bean 前完成被 Spring 加载?我听到这个问题的第一反应是,为什么会有这样奇怪的需求? Talk is cheap,show me the code,这里列出了那个想做最先加载的“天选 Bean” 的代码,我们来分析一

    2024年02月05日
    浏览(11)
  • Spring Boot 的版本与 MyBatis 或其他依赖库的版本不兼容

    报错:java.lang.IllegalArgumentException: Unable to instantiate org.mybatis.spring.boot.autoconfigure.MybatisDependsOnDatabaseInitializationDetector [org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitializationDetector] 检查pom文件之后发现 我 在依赖中使用了 MyBatis Spring Boot Starter 的版本是 3.0.2 ,但 Spring Boot 的

    2024年02月07日
    浏览(43)
  • Spring Boot指标监控及日志管理

    Spring Boot指标监控及日志管理

    目录 一、添加Actuator功能 二、SpringBoot指标监控 Spring Boot Admin 1. 创建Spring Boot Admin服务端项目 2. 连接Spring Boot Admin项目 三、SpringBoot日志管理 Spring Boot Actuator可以帮助程序员监控和管理SpringBoot应用,比如健康检查、内存使用情况统计、线程使用情况统计等。我们在SpringBoot项目

    2024年02月06日
    浏览(7)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包