說實在的,在閱讀Hadoop YARN的源碼以前,我對於Java枚舉的使用相形見絀。YARN中實現的事件在可讀性、可維護性、可擴展性方面的工做都值得借鑑。java
在具體分析源碼以前,咱們先看看YARN是如何定義一個事件的。好比做業啓動的事件,不少人可能會用常量將它定義到一個class文件中,就像下面這樣:api
[java] view plain copyide
class Constants { public static final String JOB_START_EVENT = "jobStart"; }
或者簡單的使用枚舉,就像下面這樣;oop
[java] view plain copythis
enum Enums { JOB_START_EVENT("jobStart"); private String name; private Enums(String name) { this.name = name; } }
以後,當增長了做業中止的事件,代碼會變爲:spa
[java] view plain copy.net
class Constants { public static final String JOB_START_EVENT = "jobStart"; public static final String JOB_END_EVENT = "jobEnd"; }
或者:code
[java] view plain copyblog
enum Enums { JOB_START_EVENT("jobStart"), JOB_END_EVENT("jobEnd"); private String name; private Enums(String name) { this.name = name; } }
咱們的系統每每很複雜,這時候引入了任務的概念,包括任務啓動、任務中止的事件。隨着業務發展,有更多的概念被加進來,就像下面這樣;接口
[java] view plain copy
class Constants { public static final String JOB_START_EVENT = "jobStart"; public static final String JOB_END_EVENT = "jobEnd"; public static final String TASK_START_EVENT = "taskStart"; public static final String TASK_END_EVENT = "taskEnd"; // 其它各類概念的常量 }
或者:
[java] view plain copy
enum Enums { JOB_START_EVENT("jobStart"), JOB_END_EVENT("jobEnd"), // 其它各類概念的常量枚舉 TASK_START_EVENT("taskStart"), TASK_END_EVENT("taskEnd"); private String name; private Enums(String name) { this.name = name; } }
當加入的常量值愈來愈多時,你會發現以上使用方式愈來愈不可維護。各類概念混雜在一塊兒,顯得雜亂無章。你可能會說,我不會這麼傻,我會將做業與任務以及其它概念的常量值分而治之,每一個業務概念相關的放入一個文件,就像下面這樣:
[java] view plain copy
class JobConstants { public static final String JOB_START_EVENT = "jobStart"; public static final String JOB_END_EVENT = "jobEnd"; }
[java] view plain copy
class TaskConstants { public static final String TASK_START_EVENT = "taskStart"; public static final String TASK_END_EVENT = "taskEnd"; }
或者:
[java] view plain copy
enum JobEnums { JOB_START_EVENT("jobStart"), JOB_END_EVENT("jobEnd"); private String name; private JobEnums (String name) { this.name = name; } }
[java] view plain copy
enum TaskEnums { TASK_START_EVENT("taskStart"), TASK_END_EVENT("taskEnd"); private String name; private TaskEnums (String name) { this.name = name; } }
如今業務出現了新的變化,每種枚舉值除了name屬性以外,還增長了code屬性。假如你以前選擇了常量值來實現,此時不可避免的須要重構。若是你選擇了枚舉,說明你初步的選擇是明智的,你能夠這樣來擴展:
[java] view plain copy
enum JobEnums { JOB_START_EVENT(10, "jobStart"), JOB_END_EVENT(20, "jobEnd"); private int code; private String name; private JobEnums (int code, String name) { this.code = code; this.name = name; } }
[java] view plain copy
enum TaskEnums { TASK_START_EVENT(110, "taskStart"), TASK_END_EVENT(120, "taskEnd"); private int code; private String name; private TaskEnums (int code, String name) { this.code = code; this.name = name; } }
可悲的是,你不得不在每個枚舉中都重複加入相似的代碼。也許你認爲這只不過是增長些許的工做量,你操做鍵盤的手法熟練而迷人,幾回快速的複製操做就能夠完成。噩夢遠沒有結束,新的需求給兩個枚舉類型融入了新的不一樣——JobEnums增長了description屬性,而TaskEnums則增長了timestamp字段。此外,二者還必須都增長hashCode方法以用於散列。增長這些功能後,代碼將變爲:
[java] view plain copy
enum JobEnums { JOB_START_EVENT(10, "jobStart", "job start description"), JOB_END_EVENT(20, "jobEnd", "job end description"); private int code; private String name; private String description; private JobEnums (int code, String name, String description) { this.code = code; this.name = name; this.description = description; } public int hashCode() { return this.name.hashCode() + this.description.hashCode(); } }
[java] view plain copy
enum TaskEnums { TASK_START_EVENT(110, "taskStart", 1460977775087), TASK_END_EVENT(120, "taskEnd", 1460977775088); private int code; private String name; private long timestamp; private TaskEnums (int code, String name, long timestamp) { this.code = code; this.name = name; this.timestamp = timestamp; } public int hashCode() { return this.name.hashCode(); } }
隨着業務的發展,你會發現你須要維護的枚舉類型差別愈來愈多。即使它們之間有所不一樣,但是卻有不少內容是重複的。爲了解決枚舉與常量在可讀性、可維護性、可複用性、可擴展性等方面的問題,Hadoop將事件進行了如下定義:
事件 = 事件名稱 + 事件類型
好比做業啓動事件 = 做業事件 + 做業事件類型
Hadoop2.6.0中的事件多種多樣,最爲常見的包括:ContainerEvent、ApplicationEvent、JobEvent、RMAppEvent、RMAppAttemptEvent、TaskEvent、TaskAttemptEvent等。爲了解決枚舉與常量在可讀性、可維護性、可複用性、可擴展性等方面的問題,Hadoop對事件進行了如下抽象:
[java] view plain copy
/** * Interface defining events api. * */ @Public @Evolving public interface Event<TYPE extends Enum<TYPE>> { TYPE getType(); long getTimestamp(); String toString(); }
以上接口說明了任何一個具體事件都是一個枚舉類型,並且有一個事件類型屬性(用泛型標記TYPE表示),一個時間戳及toString()方法。
全部事件都有一個基本實現AbstractEvent,其實現以下:
[java] view plain copy
/** * Parent class of all the events. All events extend this class. */ @Public @Evolving public abstract class AbstractEvent<TYPE extends Enum<TYPE>> implements Event<TYPE> { private final TYPE type; private final long timestamp; // use this if you DON'T care about the timestamp public AbstractEvent(TYPE type) { this.type = type; // We're not generating a real timestamp here. It's too expensive. timestamp = -1L; } // use this if you care about the timestamp public AbstractEvent(TYPE type, long timestamp) { this.type = type; this.timestamp = timestamp; } @Override public long getTimestamp() { return timestamp; } @Override public TYPE getType() { return type; } @Override public String toString() { return "EventType: " + getType(); } }
以JobEvent表示做業事件,其實現以下:
[java] view plain copy
/** * This class encapsulates job related events. * */ public class JobEvent extends AbstractEvent<JobEventType> { private JobId jobID; public JobEvent(JobId jobID, JobEventType type) { super(type); this.jobID = jobID; } public JobId getJobId() { return jobID; } }
TaskEvent表示任務事件,其實現以下:
[java] view plain copy
/** * this class encapsulates task related events. * */ public class TaskEvent extends AbstractEvent<TaskEventType> { private TaskId taskID; public TaskEvent(TaskId taskID, TaskEventType type) { super(type); this.taskID = taskID; } public TaskId getTaskID() { return taskID; } }
事件類型屬性(用泛型標記TYPE表示)在任務事件中對應的是TaskEventType,其實現以下:
[java] view plain copy
/** * Event types handled by Task. */ public enum TaskEventType { //Producer:Client, Job T_KILL, //Producer:Job T_SCHEDULE, T_RECOVER, //Producer:Speculator T_ADD_SPEC_ATTEMPT, //Producer:TaskAttempt T_ATTEMPT_LAUNCHED, T_ATTEMPT_COMMIT_PENDING, T_ATTEMPT_FAILED, T_ATTEMPT_SUCCEEDED, T_ATTEMPT_KILLED }
JobEventType相似,再也不贅述。
這種實現將枚舉與各類事件之間的差別(表如今屬性和方法的不一樣)解耦,極大地擴展了可讀性、可維護性,而且保留了相同邏輯的代碼複用。