最近在搞admin監控時,遇到一個坑。背景是這樣得,使用得事nacos做註冊中心,開發了admin server服務以後,服務offline,down狀態可以正常推送到釘釘,但是up狀態一直監控不到,網上查了一些資料,相關說明較少,有一篇文章是說要複製NacosWatch.java,本地實現一下,因爲nacos沒有監聽上線通知。
按說明操作了,但是還是不好使。相關代碼如下:
- NacosWatch.java
/**
* @ClassName: NacosWatch
* @Author: shuyu.wang
* @Description:
* @Date: 2020/6/1 14:02
* @Version: 1.0
*/
@Slf4j
public class NacosWatch implements ApplicationEventPublisherAware, SmartLifecycle {
/**
* watch delay,duration to pull new service from nacos server.
*/
private long watchDelay = 30000;
private final NacosDiscoveryProperties properties;
private final TaskScheduler taskScheduler;
private final AtomicLong nacosWatchIndex;
private final AtomicBoolean running;
private ApplicationEventPublisher publisher;
private ScheduledFuture<?> watchFuture;
private Set<String> cacheServices;
private HashMap<String, EventListener> subscribeListeners;
public NacosWatch(NacosDiscoveryProperties properties) {
this(properties, getTaskScheduler());
}
public NacosWatch(NacosDiscoveryProperties properties, TaskScheduler taskScheduler) {
this.nacosWatchIndex = new AtomicLong(0L);
this.running = new AtomicBoolean(false);
this.cacheServices = new HashSet();
this.subscribeListeners = new HashMap();
this.properties = properties;
this.taskScheduler = taskScheduler;
}
private static ThreadPoolTaskScheduler getTaskScheduler() {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.initialize();
return taskScheduler;
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
@Override
public boolean isAutoStartup() {
return true;
}
@Override
public void stop(Runnable callback) {
this.stop();
callback.run();
}
@Override
public void start() {
if (this.running.compareAndSet(false, true)) {
this.watchFuture =
this.taskScheduler.scheduleWithFixedDelay(this::nacosServicesWatch, watchDelay);
}
}
@Override
public void stop() {
if (this.running.compareAndSet(true, false) && this.watchFuture != null) {
this.watchFuture.cancel(true);
}
}
@Override
public boolean isRunning() {
return false;
}
@Override
public int getPhase() {
return 0;
}
public void nacosServicesWatch() {
try {
boolean changed = false;
NamingService namingService = this.properties.namingServiceInstance();
ListView<String>
listView = this.properties.namingServiceInstance().getServicesOfServer(1, 2147483647);
List<String> serviceList = listView.getData();
Set<String> currentServices = new HashSet(serviceList);
currentServices.removeAll(this.cacheServices);
if (currentServices.size() > 0) {
changed = true;
}
Iterator var6;
String serviceName;
if (this.cacheServices.removeAll(new HashSet(serviceList)) && this.cacheServices.size() > 0) {
changed = true;
var6 = this.cacheServices.iterator();
while (var6.hasNext()) {
serviceName = (String) var6.next();
namingService
.unsubscribe(serviceName, (EventListener) this.subscribeListeners.get(serviceName));
this.subscribeListeners.remove(serviceName);
}
}
this.cacheServices = new HashSet(serviceList);
var6 = this.cacheServices.iterator();
while (var6.hasNext()) {
serviceName = (String) var6.next();
if (!this.subscribeListeners.containsKey(serviceName)) {
EventListener eventListener = (event) -> {
this.publisher
.publishEvent(new HeartbeatEvent(this, this.nacosWatchIndex.getAndIncrement()));
};
this.subscribeListeners.put(serviceName, eventListener);
namingService.subscribe(serviceName, eventListener);
}
}
if (changed) {
this.publisher
.publishEvent(new HeartbeatEvent(this, this.nacosWatchIndex.getAndIncrement()));
}
} catch (Exception var9) {
log.error("Error watching Nacos Service change", var9);
}
}
}
- NacosWatchAutoConfiguration.java
@Configuration
public class NacosWatchAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(value = "spring.cloud.nacos.discovery.watch.enabled", matchIfMissing = true)
public NacosWatch nacosWatchDeepBlue(NacosDiscoveryProperties nacosDiscoveryProperties) {
return new NacosWatch(nacosDiscoveryProperties);
}
}
添加上這兩個類以後,一頓debug,但是還是一直沒有上線狀態得提醒,最後發現自定義通知類繼承得AbstractStatusChangeNotifier.java需要重寫兩個方法。
正常情況下我們一般只會重寫 doNotify(InstanceEvent event, Instance instance)這個方法,但實際上還要重寫shouldNotify(InstanceEvent event, Instance instance)方法父類中只對UNKNOWN:UP得狀態變化做了推送。
最終自定義通知類如下:
DingTalkNotifier.java
@Slf4j
public class DingTalkNotifier extends AbstractStatusChangeNotifier {
@Autowired
private AlarmDingTalkRobotClient alarmDingTalkRobotClient;
@Autowired
private NacosConfigService nacosConfigService;
/**
* 消息模板
*/
private static final String template = "<<<%s>>> \n 【服務名】: %s(%s) \n 【狀態】: %s(%s) \n 【服務ip】: %s \n 【詳情】: %s";
private String titleAlarm = "系統告警";
private String titleNotice = "系統通知";
private String[] ignoreChanges = new String[]{"UNKNOWN:UP","DOWN:UP","OFFLINE:UP"};
public DingTalkNotifier(InstanceRepository repository) {
super(repository);
}
@Override
protected boolean shouldNotify(InstanceEvent event, Instance instance) {
if (!(event instanceof InstanceStatusChangedEvent)) {
return false;
} else {
InstanceStatusChangedEvent statusChange = (InstanceStatusChangedEvent)event;
String from = this.getLastStatus(event.getInstance());
String to = statusChange.getStatusInfo().getStatus();
return Arrays.binarySearch(this.ignoreChanges, from + ":" + to) < 0 && Arrays.binarySearch(this.ignoreChanges, "*:" + to) < 0 && Arrays.binarySearch(this.ignoreChanges, from + ":*") < 0;
}
}
@Override
protected Mono<Void> doNotify(InstanceEvent event, Instance instance) {
return Mono.fromRunnable(() -> {
if (!nacosConfigService.getIsopen()){
return;
}
String watchapplications = nacosConfigService.getWatchapplications();
Boolean flag=watchapplications.contains(instance.getRegistration().getName());
if (!flag){
return;
}
if (event instanceof InstanceStatusChangedEvent) {
log.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":
log.info("發送 健康檢查沒通過 的通知!");
messageText = String
.format(template,titleAlarm, instance.getRegistration().getName(), event.getInstance(),
((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(), "健康檢查沒通過",
instance.getRegistration().getServiceUrl(), JSONObject.toJSONString(instance.getStatusInfo().getDetails()));
alarmDingTalkRobotClient.sendMarkdownMessage(titleAlarm, messageText, true);
break;
// 服務離線
case "OFFLINE":
log.info("發送 服務離線 的通知!");
messageText = String
.format(template,titleAlarm, instance.getRegistration().getName(), event.getInstance(),
((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(), "服務離線",
instance.getRegistration().getServiceUrl(), JSONObject.toJSONString(instance.getStatusInfo().getDetails()));
alarmDingTalkRobotClient.sendMarkdownMessage(titleAlarm, messageText, true);
break;
//服務上線
case "UP":
log.info("發送 服務上線 的通知!");
messageText = String
.format(template,titleNotice, instance.getRegistration().getName(), event.getInstance(),
((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(), "服務上線",
instance.getRegistration().getServiceUrl(), JSONObject.toJSONString(instance.getStatusInfo().getDetails()));
alarmDingTalkRobotClient.sendMarkdownMessage(titleNotice, messageText, true);
break;
// 服務未知異常
case "UNKNOWN":
log.info("發送 服務未知異常 的通知!");
messageText = String
.format(template,titleAlarm, instance.getRegistration().getName(), event.getInstance(),
((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(), "服務未知異常",
instance.getRegistration().getServiceUrl(), JSONObject.toJSONString(instance.getStatusInfo().getDetails()));
alarmDingTalkRobotClient.sendMarkdownMessage(titleAlarm, messageText, true);
break;
default:
break;
}
} else {
log.info("Instance {} ({}) {}", instance.getRegistration().getName(), event.getInstance(),
event.getType());
}
});
}
}
其中AlarmDingTalkRobotClient類是釘釘報警自己封裝得類,這裏不做詳細描述,大家可以根據自己業務需求,自行封裝。