dottext阅读之系统调度分析

    记得很早以前下载过DUDU的dottext.但当时由于看说明配置过于繁杂没弄.由于最近有一个博客系统的项目.又打开dottext研究了下,发现它里面的搜索是基于Lucene.Net.并且搜索文件的更新是增量式.因为自己在以前公司做过关于Lucene的项目.当时每次更新索引都是编写的CS结构程序重新生成,然后更新到服务器,非常麻烦.于是自己对于dottext系统中如何直接在WEB中对该模块进行调度很感兴趣.在网上搜索了下有没有前人对这一块进行过分析,发现baidu,google均只能找到很有限的资料.只好这二天空余时间就自己研究了下,在这里就我的理解分析下dottext系统中的调度模块!

     好,入正题.拿到一个系统,进行分析.先大概看一下它解决方案下的项目.该解决方案下有六个项目,基本很好理解.然后打开WEB项目下的配置文件!从中发现

< Events >
            
< Event  type  = "Dottext.Search.SearchEngineSchedule, Dottext.Search"  minutes  = "1"  key  = "SearchEngine"   />
            
< Event  type  = "Dottext.Framework.Tracking.StatsQueueSchedule, Dottext.Framework"  minutes  = "5"  key  = "StatsQueue"   />
</ Events >

 有朋友可能会说,你为什么会先看到这个配置,因为很简单.我首先对他的事件调度很感兴趣,大致阅览配置文件,只有这一部分看上去非常像,另外再看到Event type就是在Dottext.Search项目下.可以确定搜索的索引文件生成.就是靠这个事件.

这里的Events并不是web.config本身的属性,往上看会发现这里的Events配置节点处于BlogConfigurationSettings配置节点下.这是一个自定义的配置节.到上面去找他的处理器

< configSections >
    
<!-- 声明了自定义配置节处理程序 -->
        
< section  name ="BlogConfigurationSettings"  type ="Dottext.Framework.Util.XmlSerializerSectionHandler, Dottext.Framework"   />
        
< section  name ="HandlerConfiguration"  type ="Dottext.Framework.Util.XmlSerializerSectionHandler, Dottext.Framework"   />
        
< section  name ="SearchConfiguration"  type ="Dottext.Framework.Util.XmlSerializerSectionHandler, Dottext.Framework"   />
        
< section  name ="microsoft.web.services"  type ="Microsoft.Web.Services.Configuration.WebServicesConfiguration, Microsoft.Web.Services, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"   />
        
< section  name ="codeHighlighter"  type ="ActiproSoftware.CodeHighlighter.CodeHighlighterConfigurationSectionHandler, ActiproSoftware.CodeHighlighter"   />
    
</ configSections >

 

发现BlogConfigurationSettings配置节是由自己写的Dottext.Framework.Util.XmlSerializerSectionHandler处理.这里有兴趣可以进去看看.

// 自定义的配置节处理器必须实现IConfigurationSectionHandler接口
     public   class  XmlSerializerSectionHandler : IConfigurationSectionHandler 
    {
        
public   object  Create( object  parent,  object  configContext, System.Xml.XmlNode section) 
        {
            XPathNavigator nav 
=  section.CreateNavigator();
            
string  typename  =  ( string ) nav.Evaluate( " string(@type) " ); // 是从当前XML(配置文件是一个符合xml要求的文档)节点处,获取”type”属性
            Type t  =  Type.GetType(typename); // 然后按照属性描述,获得一个.net的类型
             /* 这种生成类的方法是区别于new 方法生成具体类的另外途径,好处就是灵活根据具
             * 体环境内容(甚至是用户交互输入的类型描述字符串)就可以生成获得托管类型。(此处反射细节请参考MSDN)。
             * 坏处就是可能隐藏着类型错误,运行时出错,导致不可预料(好像这个词在windows编程时代相当的常见)例外甚至系统崩溃。
             
*/  

            
// 当然,上面仅仅创建了类型是不够用的,还需要通过一定途径来确定生成类的具体状态,
            
// 这有用到OOP语言的重要特性—序列化,将一个对象存储器来,以及从存储中还原具体对象的机制。

            
// 这里使用的是System提供的 XmlSerializer 类的反序列化方法Deserialize。反序列化后面还有很多
            
// 代码涉及到,我认为现在就大致理解为“通过这个反序列化,我们刚刚得到的类实例中的属性、成员变
            
// 量获得了赋值,进入到某个状态,就好像我们此处运行了new 语法和进行了对象的构造以及赋值”即可。更进一步的可以从权威资料MSDN获取。

            XmlSerializer ser 
=   new  XmlSerializer(t);
            
return  ser.Deserialize( new  XmlNodeReader(section));
        }

上面为方便一些朋友理解,我加上了注释!它的作用是将类的实例属性持久化到web.config.当程序运行时再从web.config文件取出属性还原类实例!当然,也方便修改!而外部调用是直接用系统提供的方法获取.如下

// .NET1.1方式
BlogConfigurationSettings bs  =  (BlogConfigurationSettings)System.Configuration.ConfigurationSettings.GetConfig( " BlogConfigurationSettings " );
// .NET2.0方式
BlogConfigurationSettings bs  =  (BlogConfigurationSettings)System.Configuration.ConfigurationManager.GetSection( " BlogConfigurationSettings " );

这样就可以从配置文件中获取到一个BlogConfigurationSettings实例!他的属性值均来自配置文件!这就是通常所说的序列化.给开发可以带来很大便利!但使用者也要清楚.序列化和反序列化是比较耗性能的!所以在使用时应该注意代码书写方式.否则用的不当将带来性能问题!
     好了,我们现在去看Dottext.Search.SearchEngineSchedule.cs这个感兴趣的类文件

public   class  SearchEngineSchedule : Dottext.Framework.ScheduledEvents.IEvent
    {
        
public  SearchEngineSchedule()
        {
            
//
            
//  TODO: Add constructor logic here
            
//
        }
        
#region  IEvent Members

        
public   void  Execute( object  state)
        {
            Log log 
=   new  Log();
            log.Title 
=   " Search Index " ;
            log.Message 
=   string .Format( " Daily ({0}) Build " ,log.StartDate.ToShortDateString());

            IndexManager.RebuildSafeIndex(
30 );

            log.EndDate 
=  DateTime.Now;
            
            LogManager.Create(log);
            
        }

        
#endregion
    }
我们发现这个类实现了Dottext.Framework.ScheduledEvents.IEvent这样一个接口.而其中只有一个Execute方法.我们初步猜这个方法就是具体调度要执行的任务代码.它里面有一些日志的记录以及具体的更新索引文件!我们先不关心该方法的体.现在想知道他如何实现调度的!去看看IEvent接口!
namespace  Dottext.Framework.ScheduledEvents
{
    
///   <summary>
    
///  Interface for defining an event.
    
///   </summary>
     public   interface  IEvent
    {
        
void  Execute( object  state);
    }
}

这个接口很简单,就定义了一个Execute方法!再看看他同命名空间下有哪些其他文件!
1.Event.cs 事件实体 它里面主要定义事件的一些属性,如是否应该执行,最后执行时间,对应的执行方法的类等等.

ContractedBlock.gif ExpandedBlockStart.gif Event.cs
using System;
using System.Xml.Serialization;
using Dottext.Framework.Providers;

namespace Dottext.Framework.ScheduledEvents
ExpandedBlockStart.gifContractedBlock.gif
{
ExpandedSubBlockStart.gifContractedSubBlock.gif    
/**//// <summary>
    
/// Event is the configuration of an IEvent. 
    
/// </summary>

    [Serializable]
    
public class Event
ExpandedSubBlockStart.gifContractedSubBlock.gif    
{
        
public Event()
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{

        }


        
private IEvent _ievent =null;

ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// The current implementation of IEvent
        
/// </summary>

        [XmlIgnoreAttribute]
        
public IEvent IEventInstance
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            
get
ExpandedSubBlockStart.gifContractedSubBlock.gif            
{
                LoadIEvent();
                
return _ievent;
            }

        }


ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// Private method for loading an instance of IEvent
        
/// </summary>

        private void LoadIEvent()
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            
if(_ievent == null)
ExpandedSubBlockStart.gifContractedSubBlock.gif            
{
                
if(this.ScheduleType == null)
ExpandedSubBlockStart.gifContractedSubBlock.gif                
{
                    
throw new BlogProviderException("A Scheduled Event (IEvent) does not have its type property defined");
                }

                _ievent 
= (IEvent)Activator.CreateInstance(Type.GetType(this.ScheduleType));
                
if(_ievent == null)
ExpandedSubBlockStart.gifContractedSubBlock.gif                
{
                    
throw new BlogProviderException(string.Format("The Scheduled Event {0} could not be loaded",this.ScheduleType));
                }

            }

        }


        
private string _key;
        
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// A unique key used to query the database. The name of the Server will also be used to ensure the "Key" is 
        
/// unique in a cluster
        
/// </summary>

        [XmlAttribute("key")]
        
public string Key
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
ExpandedSubBlockStart.gifContractedSubBlock.gif            
get {return this._key;}
ExpandedSubBlockStart.gifContractedSubBlock.gif            
set {this._key = value;}
        }


        
private int _timeOfDay =-1;
        
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// Absolute time in mintues from midnight. Can be used to assure event is only 
        
/// executed once per-day and as close to the specified
        
/// time as possible. Example times: 0 = midnight, 27 = 12:27 am, 720 = Noon
        
/// </summary>

        [XmlAttribute("timeOfDay")]
        
public int TimeOfDay
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
ExpandedSubBlockStart.gifContractedSubBlock.gif            
get {return this._timeOfDay;}
ExpandedSubBlockStart.gifContractedSubBlock.gif            
set {this._timeOfDay = value;}
        }


        
private int _minutes = 60;
        
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// The scheduled event interval time in minutes. If TimeOfDay has a value >= 0, Minutes will be ignored. 
        
/// This values should not be less than the Timer interval.
        
/// </summary>

        [XmlAttribute("minutes")]
        
public int Minutes
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            
get 
ExpandedSubBlockStart.gifContractedSubBlock.gif            
{
                
if(this._minutes < EventManager.TimerMinutesInterval)
ExpandedSubBlockStart.gifContractedSubBlock.gif                
{
                    
return EventManager.TimerMinutesInterval;
                }

                
return this._minutes;
            }

ExpandedSubBlockStart.gifContractedSubBlock.gif            
set {this._minutes = value;    }
        }


        
private string _scheduleType;
        
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// The Type of class which implements IEvent
        
/// </summary>

        [XmlAttribute("type")]
        
public string ScheduleType
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
ExpandedSubBlockStart.gifContractedSubBlock.gif            
get {return this._scheduleType;}
ExpandedSubBlockStart.gifContractedSubBlock.gif            
set {this._scheduleType = value;}
        }


        
private DateTime _lastCompleted;
        
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// Last Date and Time this event was processed/completed.
        
/// </summary>

        [XmlIgnoreAttribute]
        
public DateTime LastCompleted
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
ExpandedSubBlockStart.gifContractedSubBlock.gif            
get {return this._lastCompleted;}
            
set 
ExpandedSubBlockStart.gifContractedSubBlock.gif            
{
                dateWasSet 
= true;
                
this._lastCompleted = value;
            }

        }


        
//internal testing variable
        bool dateWasSet = false;

        [XmlIgnoreAttribute]
        
public bool ShouldExecute
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            
get
ExpandedSubBlockStart.gifContractedSubBlock.gif            
{
                
if(!dateWasSet) //if the date was not set (and it can not be configured), check the data store
ExpandedSubBlockStart.gifContractedSubBlock.gif
                {
                    LastCompleted 
= DTOProvider.Instance().GetLastExecuteScheduledEventDateTime(this.Key,Environment.MachineName);
                }


                
//If we have a TimeOfDay value, use it and ignore the Minutes interval
                if(this.TimeOfDay > -1)
ExpandedSubBlockStart.gifContractedSubBlock.gif                
{
                    
//Now
                    DateTime dtNow = DateTime.Now;  //now
                    
//We are looking for the current day @ 12:00 am
                    DateTime dtCompare = new DateTime(dtNow.Year,dtNow.Month,dtNow.Day);
                    
//Check to see if the LastCompleted date is less than the 12:00 am + TimeOfDay minutes
                    return LastCompleted < dtCompare.AddMinutes(this.TimeOfDay);
                    
                }

                
else
ExpandedSubBlockStart.gifContractedSubBlock.gif                
{
                    
//Is the LastCompleted date + the Minutes interval less than now?
                    return LastCompleted.AddMinutes(this.Minutes) < DateTime.Now;
                }

            }

        }


ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// Call this method BEFORE processing the ScheduledEvent. This will help protect against long running events 
        
/// being fired multiple times. Note, it is not absolute protection. App restarts will cause events to look like
        
/// they were completed, even if they were not. Again, ScheduledEvents are helpfulbut not 100% reliable
        
/// </summary>

        public void UpdateTime()
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            
this.LastCompleted = DateTime.Now;
            DTOProvider.Instance().SetLastExecuteScheduledEventDateTime(
this.Key,Environment.MachineName,this.LastCompleted);
        }

    }

}


2.EventHttpModule.cs

public   class  EventHttpModule : System.Web.IHttpModule
    {
        
static  Timer eventTimer; // 这里很重要

        
public  EventHttpModule()
        {
            
//
            
//  TODO: Add constructor logic here
            
//
        }
        
#region  IHttpModule Members

        
// Init方法是实现了IHttpModule中的方法,它在应用程序接受到请求时就会执行
         public   void  Init(System.Web.HttpApplication application)
        {
            
if  (eventTimer  ==   null )
            {
                
/*  这里是关键 用了一个Timer来实现定时执行任务 初始访问 1分钟后 会调用ScheduledEventWorkCallback
                 * 方法.之后 每EventManager.TimerMinutesInterval分钟执行一次.具体看配置
                 * 这里有几点需要注意 eventTimer 变量不能定义在本方法内部,一定要是一个类中的静态变量
                 * 否则,当线程执行完本方法后,.NET GC垃圾回收机制,会随时将它回收,达不到应用效果.
                 * 将eventTimer定义为静态的并且在这里有一个引用.这样可以保证它一直是活动的
                 * 当然有情况会断掉,一是IIS服务器关闭了,二是IIS进程回收也会停止
                 
*/                 
                eventTimer 
=   new  Timer( new  TimerCallback(ScheduledEventWorkCallback), application.Context,  60000 , EventManager.TimerMinutesInterval  *   60000 );
            }
        }

        
private   void  ScheduledEventWorkCallback( object  sender)
        {
            
try
            {                
                EventManager.Execute();
            }
            
catch (Exception ex)
            {
                LogManager.CreateExceptionLog(ex,
" Failed ScheduledEventCallBack " );
            }

        }

        
public   void  Dispose()
        {
            eventTimer 
=   null ;
        }

        
#endregion
    }
上面我注释的比较详细.ScheduledEventWorkCallback方法主要调用了EventManager.Execute()方法来执行任务.具体的什么任务在EventHttpModule不关心.这也不属于它的职责!另外需要本类正常运行是需要在web.config中配置一下httpModules
< httpModules >
            
< add  name ="UrlReWriteModule"  type ="Dottext.Common.UrlManager.UrlReWriteModule, Dottext.Common"   />
            
< add  name ="EventHttpModule"  type ="Dottext.Framework.ScheduledEvents.EventHttpModule, Dottext.Framework"   />
            
<!-- <add name="MsftBlogsHttpModule" type= "AspNetWeb.MsftBlogsHttpModule, MsftBlogsHttpModule" /> -->
        
</ httpModules >
上面的EventHttpModule是配置本类的!这样在系统接受到请求时会进过EventHttpModule.在里面触发timer.以后就会定期的执行!
3.EventManager.cs
///   <summary>
    
///  EventManager is called from the EventHttpModule (or another means of scheduling a Timer). Its sole purpose
    
///  is to iterate over an array of Events and deterimine of the Event's IEvent should be processed. All events are
    
///  added to the managed threadpool.
    
///  它是任务的管理器,负责取出并调用线程池中线程来执行这些任务 
    
///   </summary>
     public   class  EventManager
    {
        
private  EventManager()
        {
        }

        
public   static   readonly   int  TimerMinutesInterval  =   5 ;


        
public   static   void  Execute()
        {
            
// 第一步 取出所有需要执行的任务
            Event[] items  =  Config.Settings.ScheduledItems; // 这里系统是从配置文件中读取到所有的Events子节点Event
            Event item  =   null ;
            
            
// 第二步 将这些任务一个个放入线程池队列中等待执行
             if (items  !=   null )
            {                
                
for ( int  i  =   0 ; i < items.Length; i ++ )
                {
                    item 
=  items[i];
                    
if (item.ShouldExecute)
                    {
                        item.UpdateTime();
                        IEvent e 
=  item.IEventInstance; // 接口编程,将实例转成IEvent接口
                        
// 将任务放入线程池队列中等待执行 接口IEvent的Execute方法
                        ManagedThreadPool.QueueUserWorkItem( new  WaitCallback(e.Execute));
                    }
                }
            }
        }
    }
EventManager的现职很明确并单一.就是负责调出任务并放入线程池等待执行!
关于ManagedThreadPool.cs 线程池的管理器!下一篇文章再写
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值