在系統運維中經常須要按期去跑一些計劃任務,好比掃描服務器監控其性能、檢查SQL Server做業是否正常、監控MQ隊列是否存在堵塞現象等。若是使用Windows計劃任務調度,一來管理起來就比較鬆散,二來如需更改計劃任務的配置就必須登陸到服務器上進行修改,形成很大的不便。所以筆者在實際工做中自行開發計劃任務調度服務來處理這些任務,將調度週期、任務配置等常常須要修改的配置信息保存到數據庫中,並開發一個前臺界面進行維護和管理。sql
1、基本結構數據庫
計劃任務調度服務使用插件的方式處理各種不一樣的計劃任務,插件必須繼承自服務框架提供的MonitorTask抽象類,並在數據庫中註冊任務名、調度週期等信息,這樣就能夠由該服務調度執行。爲方便起見,要求任務名、插件的dll文件名、插件對象的類型名必須一致,各插件都放在同一個命名空間MonitorTask下。緩存
在工做中,各種監控任務主要有3種配置:服務器
1.調度週期,以xml形式保存在數據庫中。框架
2.監控項,以鍵值對的方式保存在數據庫中。運維
3.基本不會修改的一些個性化配置,以xml文件方式保存在插件同級目錄下,名稱與插件名一致。性能
2、插件的父類spa
全部插件都必須繼承自MonitorTask抽象類,這裏之因此使用抽象類而不使用接口是考慮到能夠在這個抽象類中實現一些共性的東西,如參數配置的加載、錯誤日誌的記錄等。插件
1 public abstract class MonitorTask 2 { 3 protected XElement _configElement; 4 public List<Schedule> Schedules; 5 protected List<MonitorSetting> MonitorSettings; 6 private bool _isRunning = false; 7 8 protected MonitorTask() 9 { 10 LoadParameters(); 11 } 12 13 public void Execute() 14 { 15 try 16 { 17 if (CouldExecute()) 18 { 19 _isRunning = true; 20 ExecuteTask(); 21 ExecuteLog(); 22 } 23 } 24 catch (Exception e) 25 { 26 new LogText(AppDomain.CurrentDomain.BaseDirectory + @"log.log").Write(GetType().Name + "插件執行異常", e.ToString()); 27 } 28 finally 29 { 30 _isRunning = false; 31 } 32 } 33 34 private bool CouldExecute() 35 { 36 if (_isRunning || Schedules == null) 37 { 38 return false; 39 } 40 foreach (var schedule in Schedules) 41 { 42 if (schedule.TimeUp()) 43 { 44 return true; 45 } 46 } 47 return false; 48 } 49 50 protected abstract void ExecuteTask(); 51 52 private void ExecuteLog() 53 { 54 if (ConfigManagement.GetConfig().GetAppSetting("ExecuteLog") == "1") 55 { 56 new LogText(AppDomain.CurrentDomain.BaseDirectory + @"executelog.log").Write(GetType().Name + "插件執行完畢"); 57 } 58 } 59 60 protected void LoadParameters() 61 { 62 if (File.Exists(GetConfigFile())) 63 { 64 _configElement = XElement.Load(GetConfigFile()); 65 LoadXMLParameters(); 66 } 67 LoadMonitorSettings(); 68 } 69 70 private string GetConfigFile() 71 { 72 return AppDomain.CurrentDomain.BaseDirectory + GetType().Name + ".xml"; 73 } 74 75 protected virtual void LoadXMLParameters() 76 { 77 } 78 79 protected void LoadMonitorSettings() 80 { 81 MonitorSettings = new List<MonitorSetting>(); 82 string sql = "SELECT Name, Value FROM MonitorTaskSetting WHERE MonitorTaskID IN (SELECT ID FROM MonitorTask WHERE Name = '" + GetType().Name + "')"; 83 using (SqlDataReader reader = SqlAssist.Select("SOMP", sql)) 84 { 85 while (reader.Read()) 86 { 87 MonitorSettings.Add(new MonitorSetting(reader.GetString(0), reader.GetString(1))); 88 } 89 } 90 } 91 }
在開發計劃任務插件時只須要繼承該類,並實現ExecuteTask方法就好了。在這裏ConfigManagement.GetConfig().GetAppSetting(string name)、new LogText(string filename).Write(string value)以及SqlAssist.Select(string dbname, string sql)這三個方法都是筆者在開發過程當中自行封裝的,爲了不重複勞動,一看名字就該知道這些方法的做用吧。線程
2、任務調度週期
在MonitorTask抽象類中還有一個List<Schedule>,用於保存任務調度的週期,Schedule也是一個抽象類,該抽象類主要由3個方法:
public static List<Schedule> CreateSchedule(XElement element)
該方法根據保存在數據庫中的xml數據構建出Schedule。
public abstract bool TimeUp()
由子類實現該方法,判斷是否已到達須要調度任務的時間。
public abstract XElement ToXML()
由子類實現該方法,用於將任務調度週期序列化成xml,便於保存到數據庫中。
3、管理類
該類主要根據數據庫中的配置利用反射構建出相應的計劃任務對象,並將其緩存在Dictionary對象裏避免屢次構建,同時建立線程執行這些插件。
1 public static class MonitorTaskManagement 2 { 3 private static Dictionary<string, MonitorTask> _tasks = new Dictionary<string, MonitorTask>(); 4 5 private static MonitorTask GetMonitorTask(string fileName, string taskType, XElement element) 6 { 7 Assembly assembly = Assembly.LoadFrom(AppDomain.CurrentDomain.BaseDirectory + fileName); 8 Type type = assembly.GetType(taskType); 9 if (type.IsSubclassOf(typeof(MonitorTask))) 10 { 11 MonitorTask task = Activator.CreateInstance(type) as MonitorTask; 12 task.Schedules = Schedule.CreateSchedule(element); 13 return task; 14 } 15 else 16 { 17 throw new Exception("程序集" + taskType + "有誤。"); 18 } 19 } 20 21 private static MonitorTask GetMonitorTask(string taskName, XElement element) 22 { 23 MonitorTask task; 24 if (_tasks.ContainsKey(taskName)) 25 { 26 task = _tasks[taskName]; 27 } 28 else 29 { 30 task = GetMonitorTask(taskName + ".dll", "MonitorTask." + taskName, element); 31 _tasks.Add(taskName, task); 32 } 33 return task; 34 } 35 36 public static void ExecuteTask() 37 { 38 string sql = "SELECT Name, Schedule FROM MonitorTask WHERE Enable = 1"; 39 using (SqlDataReader reader = SqlAssist.Select("SOMP", sql)) 40 { 41 while (reader.Read()) 42 { 43 try 44 { 45 MonitorTask task = GetMonitorTask(reader.GetString(0), XElement.Parse(reader.GetString(1))); 46 Thread childThread = new Thread(task.Execute); 47 childThread.IsBackground = true; 48 childThread.Start(); 49 } 50 catch (Exception e) 51 { 52 new LogText(AppDomain.CurrentDomain.BaseDirectory + @"log.log").Write(reader.GetString(0) + "插件異常", e.ToString()); 53 } 54 } 55 } 56 } 57 }