Windows上的时钟漂移
我已经开发了一种Windows服务,该服务可以跟踪业务事件。它使用Windows时钟对事件进行时间戳记。但是,底层时钟可能会发生相当大的漂移(例如,每分钟损失几秒钟),尤其是在CPU努力工作时。我们的服务器使用Windows时间服务与域控制器保持同步,该域控制器在后台使用NTP,但是同步频率由域策略控制,并且在任何情况下,即使每分钟同步也仍然会产生很大的漂移。除了使用硬件时钟以外,是否有其他技术可以使时钟更稳定?
解决方案
除了更频繁地重新同步时钟外,我不认为除了获得新的主板外,我们还不能做很多事情,因为时钟信号似乎频率不正确。
增加重新同步的频率。
如果同步是与我们自己的网络上的主服务器同步的,则没有理由不每分钟同步一次。
同步更多。查看W32Time服务的注册表项,尤其是" Period"。 " SpecialSkew"听起来可能会对我们有所帮助。
时钟漂移可能是温度的结果;也许我们可以尝试使用更好的冷却方法使温度更恒定?但是,我们永远都不会完全摆脱漂移。
我们使用外部时钟(GPS接收器等)以及一种将CPU时间与绝对时间相关联的统计方法来同步分布式系统中的事件。
我们正在运行哪些服务器?在台式机中,我遇到的情况是启用了扩频FSB,这会导致中断时序出现一些问题,这正是时钟计时的原因。可能希望查看其中一台服务器上BIOS中的选项,如果启用,则将其关闭。
我们还有另一个选择是编辑时间轮询间隔,并使用以下注册表项将其缩短,很可能必须添加它(请注意,这是一个DWORD值,该值以秒为单位,例如10分钟为600) :
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpClient\SpecialPollInterval
这是一个完整的检查结果:KB816042
我们可以在计划的任务.bat文件中运行" w32tm / resync"。这适用于Windows Server 2003.
我曾经写过一个Delphi类来处理时间重新同步。它粘贴在下面。现在,我看到了拉里·西尔弗曼(Larry Silverman)提到的" w32tm"命令,我怀疑自己在浪费时间。
unit TimeHandler; interface type TTimeHandler = class private FServerName : widestring; public constructor Create(servername : widestring); function RemoteSystemTime : TDateTime; procedure SetLocalSystemTime(settotime : TDateTime); end; implementation uses Windows, SysUtils, Messages; function NetRemoteTOD(ServerName :PWideChar; var buffer :pointer) : integer; stdcall; external 'netapi32.dll'; function NetApiBufferFree(buffer : Pointer) : integer; stdcall; external 'netapi32.dll'; type //See MSDN documentation on the TIME_OF_DAY_INFO structure. PTime_Of_Day_Info = ^TTime_Of_Day_Info; TTime_Of_Day_Info = record ElapsedDate : integer; Milliseconds : integer; Hours : integer; Minutes : integer; Seconds : integer; HundredthsOfSeconds : integer; TimeZone : LongInt; TimeInterval : integer; Day : integer; Month : integer; Year : integer; DayOfWeek : integer; end; constructor TTimeHandler.Create(servername: widestring); begin inherited Create; FServerName := servername; end; function TTimeHandler.RemoteSystemTime: TDateTime; var Buffer : pointer; Rek : PTime_Of_Day_Info; DateOnly, TimeOnly : TDateTime; timezone : integer; begin //if the call is successful... if 0 = NetRemoteTOD(PWideChar(FServerName),Buffer) then begin //store the time of day info in our special buffer structure Rek := PTime_Of_Day_Info(Buffer); //windows time is in GMT, so we adjust for our current time zone if Rek.TimeZone <> -1 then timezone := Rek.TimeZone div 60 else timezone := 0; //decode the date from integers into TDateTimes //assume zero milliseconds try DateOnly := EncodeDate(Rek.Year,Rek.Month,Rek.Day); TimeOnly := EncodeTime(Rek.Hours,Rek.Minutes,Rek.Seconds,0); except on e : exception do raise Exception.Create( 'Date retrieved from server, but it was invalid!' + #13#10 + e.Message ); end; //translate the time into a TDateTime //apply any time zone adjustment and return the result Result := DateOnly + TimeOnly - (timezone / 24); end //if call was successful else begin raise Exception.Create('Time retrieval failed from "'+FServerName+'"'); end; //free the data structure we created NetApiBufferFree(Buffer); end; procedure TTimeHandler.SetLocalSystemTime(settotime: TDateTime); var SystemTime : TSystemTime; begin DateTimeToSystemTime(settotime,SystemTime); SetLocalTime(SystemTime); //tell windows that the time changed PostMessage(HWND_BROADCAST,WM_TIMECHANGE,0,0); end; end.
我相信Windows时间服务仅实现SNTP,这是NTP的简化版本。完整的NTP实现在决定同步频率时会考虑时钟的稳定性。
我们可以在此处获取适用于Windows的完整NTP服务器。
时钟滴答应该是可预测的,但是在大多数PC硬件上,因为它们不是为实时系统设计的,所以其他I / O设备中断的优先级高于时钟滴答中断,并且某些驱动程序在中断服务例程中进行了大量处理,而不是延迟将其发送给延迟过程调用(DPC),这意味着系统可能直到发出信号后(有时)很长时间才能处理时钟滴答中断。
其他因素还包括总线主控I / O控制器,这些控制器从CPU窃取了许多内存总线周期,从而导致其在相当长的一段时间内都缺乏内存总线带宽。
正如其他人所说,随着组件值随温度变化,时钟生成硬件也可能会改变其频率。
Windows确实允许调整每个中断上添加到实时时钟的滴答声的数量:请参阅SetSystemTimeAdjustment。但是,这仅在我们具有可预测的时钟偏斜的情况下才有效。如果时钟仅略微关闭,则SNTP客户端(" Windows时间"服务)将调整此偏斜以使时钟略微变快或者变慢,以趋向正确的时间。
我不知道这是否适用,但是...
Windows存在一个问题,如果我们使用timeBeginPeriod()大量更改计时器分辨率,则时钟会漂移。
实际上,Java的Threadwait()
(和os :: sleep()
)函数的Windows实现中存在一个导致此行为的错误。为了准确(无论睡眠时间长短),它总是将计时器分辨率设置为等待前1 ms(无论睡眠时间长短),并在完成后立即将其恢复,除非任何其他线程仍在睡眠。然后,此设置/重置将使Windows时钟混乱,后者期望Windows时间范围相当恒定。
实际上,Sun自2006年以来就已经知道这一点,并且尚未解决,AFAICT!
正因为如此,我们实际上使时钟运行速度提高了一倍!一个简单的Java程序在一个循环中睡眠1毫秒。
解决方案是自己将时间分辨率设置为较低的值,并使其保持尽可能长的时间。使用timeBeginPeriod()来控制它。 (我们将其设置为1毫秒,而没有任何不利影响。)
对于那些使用Java进行编码的人来说,解决此问题的更简单方法是创建一个线程,该线程在应用程序运行时一直处于休眠状态。
请注意,这将是在全局计算机上解决此问题的方法,而不管哪个应用程序是真正的罪魁祸首。
由于听起来生意很大:
拿一台旧笔记本电脑或者不太好用的东西,但似乎时钟或者多或者少可靠,将其称为Timekeeper。计时器的唯一工作是每2分钟一次(例如2分钟)向服务器发送一条消息,告知时间。服务器将不使用Windows时钟作为时间戳,而是从Timekeeper的最后一个信号开始减去时间,再加上自该信号以来经过的时间。每周一次或者两次通过手表查看计时表的时钟。这样就足够了。