定制设计Spring Boot Admin2 实例状态监控详解

定制设计其他相关文章:

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

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));		}	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

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

  1. 定义消息提醒实现
public class FeiShuNotifier extends AbstractStatusChangeNotifier implements AlarmMessage {	private static final String DEFAULT_MESSAGE = " 服务名称:#{instance.registration.name}  服务实例:#{instance.id}  服务URL:#{instance.registration.serviceUrl}  服务状态:【#{event.statusInfo.status}】  发送时间:#{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 + "\" + 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);	}}
  • 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
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132

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

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

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

状态监控源码分析

从《》这篇文章我们可以知道,在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);	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

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();	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

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()));	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在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));	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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();	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

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();	}
  • 1
  • 2
  • 3
  • 4

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);	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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

EventsourcingInstanceRepository.save

	public Mono<Instance> save(Instance instance) {		return this.eventStore.append(instance.getUnsavedEvents()).then(Mono.just(instance.clearUnsavedEvents()));	}
  • 1
  • 2
  • 3

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

InMemoryEventStore.append

	public Mono<Void> append(List<InstanceEvent> events) {		return super.append(events).then(Mono.fromRunnable(() -> this.publish(events)));	}
  • 1
  • 2
  • 3

该方法将在事件保存后,发送一个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);		}	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

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());	}
  • 1
  • 2
  • 3
  • 4

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)));	}
  • 1
  • 2
  • 3

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();	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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;	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

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

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

最后这么弯弯圈圈下来,实例的状态变更事件就到了FeiShuNotifier.doNotify中,到此我们对SBA2的实例状态监控的分析就结束了。

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发