公司业务发展壮大,集群监控也逐渐走向自动化:上报集群重要指标,实时监控集群状态,异常时进行自动告警
老大说:你去写一个告警程序,集群状态异常时,以短信和电话的形式通知运维人员
新来的可能会这样写(程序简化了,能表述出编程思路就行):
public class AlertApplication {
private final MessageAlarm messageAlarm;
private final TelephoneAlarm telephoneAlarm;
public AlertApplication(MessageAlarm messageAlarm, TelephoneAlarm telephoneAlarm) {
this.messageAlarm = messageAlarm;
this.telephoneAlarm = telephoneAlarm;
}
// 收到来自实时监控的指标数据,根据阈值确定是否需要进行告警
public void metricData(double memory, double cpu) {
// 打印日志
System.out.printf("集群内存使用: %.2fGB, cpu使用率: %.2f%%\n", memory, cpu * 100);
if (memory >= Threshold.MAX_MEMORY.getThreshold()) {
String msg = String.format("集群内存使用量: %.2fGB, 超过阈值: %.2fGB", memory, Threshold.MAX_MEMORY.getThreshold());
messageAlarm.sendMessage(msg);
telephoneAlarm.ringUp(msg);
}
if (cpu >= Threshold.MAX_CPU.getThreshold()) {
String msg = String.format("集群cpu使用率: %.2f%%, 超过阈值: %.2f%%", cpu * 100, Threshold.MAX_CPU.getThreshold() * 100);
messageAlarm.alert(msg);
telephoneAlarm.alert(msg);
}
}
}
enum Threshold {
MAX_MEMORY(100),
MAX_CPU(0.8);
private double threshold;
Threshold(double threshold) {
this.threshold = threshold;
}
public double getThreshold() {
return this.threshold;
}
}
class MessageAlarm {
public void alert(String msg) {
System.out.println("短信告警: " + msg);
}
}
class TelephoneAlarm {
public void alert(String msg) {
System.out.println("电话告警: " + msg);
}
}
编写主程序,启动AlertApplication
public class Main {
public static void main(String[] args) {
AlertApplication application = new AlertApplication(new MessageAlarm(), new TelephoneAlarm());
application.metricData(64.8, 0.45);
application.metricData(120.26, 0.97);
}
}
执行结果如下:
有经验的同事对这段代码做了如下评价(实际来自博客:Observer Pattern | Set 1 (Introduction)):
alert()
方法。(违反了迪米特原则?菜鸟不是很懂)alert(String msg)
方法,是在使用具体对象共享数据,而非使用接口共享数据。这违背了一个重要的设计原则:
Program to interfaces, not implementations
针对以上问题,自己体会最深的就是违反了开闭原则,代码不易维护
observer模式定义了对象之间的一对多依赖,当一个对象的状态发生变化,会自动通知并更新其他依赖对象
button
(Subject)和 ActionListener
(observer) 是用观察者模式构建的。observer模式的UML图如下:
Subject(抽象主题):Subject一般为接口或抽象类,提供添加、删除、通知observer对象的三个抽象方法
ConcreteSubject(具体主题):内部使用集合存储注册的observer,实现Subject中的抽象方法,以便在内部状态发生变化时,通知所有注册过的observer对象。
Observer(抽象观察者):Observer一般为借口或抽象类,为Subject提供通知自己的notify()
方法
update()
方法,二者都是subject向observer传递信息的接口)ConcreteObserver(具体观察者): 实现notify()方法,在Subject状态边变化时,做出相应的反应
定义Subject接口:
public interface Subject {
void addObserver(Observer observer);
void deleteObserver(Observer observer);
void notifyObservers(double cpu, double memory);
}
定义Observer接口:
public interface Observer {
void notify(double cpu, double memory);
}
实现AlertApplication对应的Subject:
public class AlertApplicationSubject implements Subject {
private final List<Observer> observers;
public AlertApplicationSubject() {
this.observers = new ArrayList<>();
}
@Override
public void addObserver(Observer observer) {
if (observer == null && observers.contains(observer)) {
return;
}
observers.add(observer);
}
@Override
public void deleteObserver(Observer observer) {
if (observer == null) {
return;
}
observers.remove(observer);
}
@Override
public void notifyObservers(double cpu, double memory) {
System.out.printf("集群当前cpu使用率: %.2f%%, 内存使用量: %.2fGB\n", cpu * 100, memory);
// 当cpu或memory超过阈值,通知observer
// observer接收到信息后,自动告警
if (cpu > 0.8 || memory > 100) {
for (Observer observer : observers) {
observer.notify(cpu, memory);
}
}
}
}
实现短信告警、电话告警两种observer:
public class MessageAlarmObserver implements Observer{
@Override
public void notify(double cpu, double memory) {
if (cpu > 0.8 ) {
System.out.printf("短信告警: 集群cpu使用率%.2f%%, 超过阈值80%%\n", cpu*100);
}
if (memory > 100) {
System.out.printf("短信告警: 集群内存使用量%.2fGB, 超过阈值100GB\n", memory);
}
}
}
public class PhoneAlarmObserver implements Observer{
@Override
public void notify(double cpu, double memory) {
if (cpu > 0.8 ) {
System.out.printf("电话告警: 集群cpu使用率%.2f%%, 超过阈值80%%\n", cpu * 100);
}
if (memory > 100) {
System.out.printf("电话告警: 集群内存使用量%.2fGB, 超过阈值100GB\n", memory);
}
}
}
使用observer模式实现的集群监控告警程序
public class Main {
public static void main(String[] args) {
Subject subject = new AlertApplicationSubject();
Observer messageAlarm = new MessageAlarmObserver();
Observer phoneAlarm = new PhoneAlarmObserver();
subject.addObserver(messageAlarm);
subject.addObserver(phoneAlarm);
// 采集集群监控数据
subject.notifyObservers(0.6, 45);
subject.notifyObservers(0.5, 127);
}
}
执行结果如下:
Lapsed listener problem
将导致内存泄漏问题一:内存泄漏
问题二:性能下降
不感兴趣
的observer没有从subject中注册自己,将增加subject发送消息的工作量,导致性能下降解决办法:
推模式
notify()
方法主动向observer传递数据,属于push模式(推模式)notify()
方法需要根据实际需求定义参数,很可能无法兼顾其他需求场景拉模式
obs
,一个标识状态是否变化的布尔值changed
notifyObservers()
却只是局部同步,并非整体同步 —— 这样的设计存在问题?欢迎讨论public void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
// 如注释说的一样,没有对这部分代码进行同步,容易出现:
// 新添加的observer无法收到正在进行的通知,最近移除的observer会错误地收到通知
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
update(Observable o, Object arg)
方法public interface Observer {
void update(Observable o, Object arg);
}
Object arg
使用推模式,主动向observer传递数据使用Java自带的observer模式,实现看病叫号的需求
继承Observable类实现叫号器
public class Caller extends Observable {
private final int room; // 诊室
private int number; // 记录当前的就诊序号
public Caller(int room) {
super(); // 初始化存储observer的Vector
this.room = room;
}
public void call(int number) {
// 就诊序号发生变化,开始叫号
if (number != this.number) {
this.number = number; // 记录最新的就诊序号
setChanged(); // 将状态更新为true,表示状态发生变化,以触发notifyObservers()方法
notifyObservers(); // 调用notifyObservers()通知就诊的病人
}
}
public int getNumber() {
return number;
}
public int getRoom() {
return room;
}
}
实现Observer接口,创建Patient类
public class Patient implements Observer {
private final int number;
private final String name;
public Patient(int number, String name) {
this.number = number;
this.name = name;
}
@Override
public void update(Observable o, Object arg) {
// 获取诊室号和就诊序号,如果是自己则做出回应
int room = ((Caller) o).getRoom();
int number = ((Caller) o).getNumber();
if (number == this.number) {
System.out.printf("我是%d号病人: %s,轮到我去%d诊室就诊\n", number, name, room);
}
}
}
测试程序
public class Main {
public static void main(String[] args) {
Caller caller = new Caller(7);
// 添加已经到场的病人
Patient patient1 = new Patient(3, "张三");
caller.addObserver(patient1);
Patient patient2 = new Patient(1, "王二");
caller.addObserver(patient2);
Patient patient3 = new Patient(4, "李四");
caller.addObserver(patient3);
// 开始叫号
caller.call(1);
caller.call(2); // 没有对应的病人,无任何响应
caller.call(3);
}
}
执行结果如下:
通过这个示例程序的顿悟: Subject和observer之间的一对多关系,并非是对应多个不同类型的observer,同一类型的多个observer也行
ProperyChangeListener
接口实现ProperyChangeListener
接口