11 调整系统时钟导致ACE定时器丢失
由于我们采用的服务器一般都是靠纽扣电池作为能源驱动和记录时钟,一般在运行一段时间后都会出现时间误差。所以很多大规模的分布系统都有校时操作,特别是一些对时钟要求精确的分布式系统(比如计费等),往往都会有一个主机提供精确时钟服务(其可能采用GPS校时),其他服务器通过这台服务器校时,校时操作一般都是直接改变系统时钟。
ACE的定时器都是采用Event_Handler进行处理,而Event_Handler一般而言都是采用绝对时间作为记录超时的时间戳,但是绝对时间的方式在系统时钟被调整的时候,会导致“丢失”部分定时器的处理,导致一些问题。
在设置定时器时,schedule_timer函数通过gettimeofday得到定时器时间点的时间。
template <class ACE_SELECT_REACTOR_TOKEN> long
ACE_Select_Reactor_T<ACE_SELECT_REACTOR_TOKEN>::schedule_timer
(ACE_Event_Handler *handler,
const void *arg,
const ACE_Time_Value &delay_time,
const ACE_Time_Value &interval)
{
// schedule_timer记录的是系统时间,
if (0 != this->timer_queue_)
return this->timer_queue_->schedule
(handler,
arg,
timer_queue_->gettimeofday () + delay_time,
interval);
}
在派发定时器的过程中也是调用gettimeofday函数。
template <class TYPE, class FUNCTOR, class ACE_LOCK> ACE_INLINE int
ACE_Timer_Queue_T<TYPE, FUNCTOR, ACE_LOCK>::expire (void)
{
if (!this->is_empty ())
return this->expire (this->gettimeofday () + timer_skew_);
else
return 0;
}
可以看出,如果在schedule_timer后,将系统时钟向前调节(调慢)以后,原有的定时器将要经过更多的时间才能触发。从而导致这段时间内定时器无法触发。从而造成定时器丢失。
这个问题的解决方法有2个,简单方法是将系统时钟校准的频度提高,保证每次校准的时候,系统的时钟出现的偏差都不会影响时钟的定时器触发。
另外一种是ACE的Timer_Queue自己提供的方法,通过上面的代码我们可以发现,其实ACE_Timer_Queue_T::gettimeofday是一个调用的是一个函数指针。默认使用ACE_OS:: gettimeofday函数,这个函数可以替换的。
void gettimeofday (ACE_Time_Value (*gettimeofday)(void));
ACE提供一个依赖于操作系统的高解析定时器,ACE_High_Res_Timer,这个类是通过OS的TICK数量来得到更加精确的时钟的【注】。
【注】OS在启动后,都会有一个TICK在不断的计数,这个TICK就像一个打点计数器,每次增加1.一般计数周期就是一个CPU周期。
由于CPU的TICK不会随着你调整系统时钟而调整。所以可以看做是一个相对值。ACE_High_Res_Timer可以根据相对值计算得到非常精确的程序运行时钟,。直接使用ACE_High_Res_Timer:: gettimeofday_hr函数作为ACE_Timer_Queue_T::gettimeofday函数指针。并且在程序的开始部分使用函数,ACE_High_Res_Timer::global_scale_factor (),用于激活高精度定时器。【注】
【注】这个方法得益于原来公司的两位同事zhangtianhu和liaobincai的一个终结。在此怀念一下和他们共事的日子。另外,我没有仔细研究过这个方法,由于获取CPU的TICK的获取很有可能是一个内核操作,效率可能不高。
采用上述的两个方法基本可以避免这个问题。