定制设计其他相关文章:
在中集成Spring Boot Admin 定制设计的主要作用之一就是用定制设计来监控服务的实例状态,定制设计并且最好是当服务DOWN或者OFFLINE定制设计的时候发消息提醒,SBA2 定制设计提供了很多提醒方式,并且SBA2 定制设计已经集成了钉钉,定制设计只要进行少量配置即可定制设计将状态变更发送到钉钉,定制设计详见我的另外一篇文章《》。
SBA2 接入飞书
定制设计这里我要说明如何进行定制设计自定义提醒,定制设计将飞书提醒集成到SBA2中,顺便看看SBA2定制设计的状态监控具体是如何实现的。
- 定制设计定义配置类
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代理配置
- 定义消息提醒实现
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启动的时候,会加载StatusUpdater
和StatusUpdateTrigger
,前者用于更新实例状态,后者用来触发状态更新,这两个类我将从头至下分部说明。
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.notify
,AbstractStatusChangeNotifier.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的实例状态监控的分析就结束了。