找回密码
 注册
关于网站域名变更的通知
查看: 428|回复: 1
打印 上一主题 下一主题

这是一个诡异的时间问题追查

[复制链接]
  • TA的每日心情

    2019-11-20 15:22
  • 签到天数: 2 天

    [LV.1]初来乍到

    跳转到指定楼层
    1#
    发表于 2020-3-9 10:17 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

    EDA365欢迎您登录!

    您需要 登录 才可以下载或查看,没有帐号?注册

    x

    8 s' D) ~0 `4 `3 U7 m1 V问题引入:
    8 L1 I7 [; Z. a- r% v问题是我们重装了一台电脑之后,发现apache
      y& y  `; ~9 B0 o2 U的日志里突然增加了很多服务时间超长的请求,如下面两条:
    + h7 u+ C. t! @* q** - - 4913229 11227 [29/Mar/2012:09:59:58 +0800] "GET*****
    , S0 B( E- ~+ y$ o# y** - - 4793564 9846 [29/Mar/2012:09:59:58 +0800] "GET *****
    3 a4 S5 s3 \- l( X, e( N; q2 _请求服务时间都在4s以上!这绝对无法忍受啊!
    " p( @" B9 _0 X/ A: {' U
    0 }6 g8 G' W# O5 ~% M分析过程:
    0 L, B% J! _/ w* U/ R
    ( _. E/ q) n5 B# |) b但是,当我们:% h+ O9 I  U1 G. A$ A
    * 手动访问这台服务器之后,发现基本上请求都是正常响应的;
    + U! @. Q2 N8 A! S0 q  h, H7 B" h * 从前端反向代理的日志来看,服务时间也是正常的;2 x' p3 z. q3 V9 B( Q9 R4 |
    那这样推断,真相只有一个,那就是apache的日志时间记录有问题!
      ?0 J! {, ^) o3 z( y7 ?
    8 g5 W% y5 w- s# \& f什么问题呢?不清楚,但是apache0 P/ {# R& I) F9 X* `
    的时间记录不外乎是在收到请求时记个时间,请求处理完成之后再记一个时间,既然是时?
    - \8 @/ i/ y# [涞奈侍猓敲淳涂纯聪低呈奔浒桑幼罴虻サ?date 开始,多执行几次:
    / R1 Y1 J* l5 V7 ?+ O7 } apache2 # date
    5 q- q2 j$ I$ E* l' |Wed Mar 28 17:25:08 CST 2012
    - z8 @1 K$ E& o; Z' Rapache2 # date. B4 L2 ~% D# y5 `9 F1 Z
    Wed Mar 28 17:25:04 CST 2012  时间减少了?1 _: s6 w9 h* ^
    apache2 # date4 L. }' H, _( N. n- R( x3 E
    Wed Mar 28 17:25:06 CST 2012+ h+ P. s# K. `- n9 i- ^, W
    apache2 # date+ O* x2 z, K7 l
    Wed Mar 28 17:25:12 CST 2012: N, P! |& G$ m
    apache2 # date- J& H8 G3 K9 `! p3 d
    Wed Mar 28 17:25:13 CST 2012) B7 C9 P* H# ~8 |+ u
    apache2 # date
    4 G/ F" J2 \: [# LWed Mar 28 17:25:09 CST 2012 时间减少了?. |0 l$ J. _" r! Y
    apache2 # date
    # y9 L7 ^# u1 B; P5 `0 G' @- aWed Mar 28 17:25:09 CST 20124 M+ x- Y8 O; F+ J* s, v# w
    apache2 # date
    7 [3 F( U  E3 A; iWed Mar 28 17:25:15 CST 2012
    ' s! ~! }' {. I6 E0 I那么第一个怀疑就是系统里有进程在自己不断的调时间,比如ntpdate, ntpd3 p( i  {* H# i" ~  ]& k8 J
    ,那么我们首先把 ntpd给停掉;然后系统也没有任何地方会调用ntpdate;
    0 i( E( }" e( c继续观察,发现停掉ntpd后,现象依旧!: e: O4 o( i* n
    还有什么可能呢? date
    / q, t* i- l" ~  @程序是从内核里面读取时间还是说需要读取硬件时间?到底是怎么计算的呢?6 u9 W- \2 T- R- Y+ T1 Z
    请教下同事们,据说 sys_time 是读的kernel里的时间,而 sys_gettimeofday6 k, y6 S4 @3 w  c2 i4 x$ o. N* N
    是需要读硬件时钟的。+ t& V& j" V) M. X% ]% s5 V! V& S
    那么就测试一把吧:
    5 H; {/ ~3 e+ y9 a7 z先写个程序调用 time(), 然后打印结果,结果是这样的:
    / a& D2 r3 b3 h+ }) M1332986690
    / P2 K- u# U- X! V8 e5 L1332986692" ^: k5 H/ E$ y7 w/ c( i
    1332986693
    4 W% }' Z6 Z. T2 \13329866938 q7 ~$ g# N6 M; H$ V' R
    1332986694# w. Y/ A6 S1 B
    1332986694
    ( T' Q! t! s& d13329866955 g  Q) _8 I7 _7 {  }. h3 y
    1332986695
    : B* ]. |7 u6 U9 ]1 u1332986696, d% c$ U3 G3 X* B9 G0 ]" y
    1332986696
    - r# ]& _3 V$ x1332986697# d8 J( U& J6 X8 N
    1332986697
    - Y. O- S" x5 J( S. ~! j3 ?  c* g1332986698# D: w! d/ e: e" k4 j
    1332986698
    : ?* `! q9 K' D+ W- Y2 a# U1332986699# m- }$ F! y& e# s
    确实没有问题了!看来有可能是硬件问题了,再看看clock_gettime 的调用:4 X) n# k7 p6 N, x, h6 k
    {
    1 n" q# x- j9 B, s+ }2 C        struct timespec ts;                              
    " J9 T# i9 z% @% ~# m* ]! k1 l( ]        int ret = 0;                                      : A+ |0 b( U, ^4 E7 G
            ret = clock_gettime(CLOCK_REALTIME, ts);          5 t- w$ ~* G" K+ f
            printf("ret: %d, time: %d %d", ts.tv_sec, ts.tv_ns
    ; ^; I, b$ l/ ~' M}                                                          , B7 I+ o2 L9 S$ m% A; A2 \5 y
    编译:        gcc -g -o clock_gettime getdate.c -lrt            * [$ E5 i4 `3 S% A. ]' s
                                                              2 {5 Y/ Q" w$ f/ E0 N, H" y# K
    ~ # ./clock_gettime                                    2 [2 f0 w3 x4 w5 e  _$ _. C" K% ^
    ret: 1332991087, time: 594076000 -1
    + ?. H3 o) w: k) `ret: 1332991088, time: 379009000 -1
    - s1 q0 N0 s, H9 k: G  h% s6 n( Cret: 1332991089, time: 68561000 -1, c6 u% e& R: S4 s: \* R+ ]
    ret: 1332991089, time: 714013000 -1
    + j* z: T3 l5 ~7 q7 q9 X( A) Z0 I" Tret: 1332991086, time: 511250000 -1 时间在跳/ s& N0 V' e# y9 d' p( C4 C# [
    ret: 1332991091, time: 945626000 -1
    7 {+ n- k3 |$ T/ e0 ^" B" O. lret: 1332991092, time: 650479000 -1* `0 A% H3 y" n. v* M8 D* U- f. S% n/ w
    ret: 1332991088, time: 734780000 -1  R& |  Q/ [+ i5 u
    ret: 1332991094, time: 506114000 -1时间在跳                    $ c1 P2 ~- q7 i- e3 q2 J! G4 ~  p
    看来果然有可能是硬件问题!
    $ b+ L0 S0 M  v那么,检查一下,直接调用一下 hwclock --show ,看看是不是可以验证这个问题:/ O- J9 \8 s& c1 U
    ~ # hwclock --show                              
    3 z# k# j# z( n* X8 J' h. IThu 29 Mar 2012 10:22:21 AM CST  -0.948923 seconds  ) n3 [* ?! ^3 {2 p; D
    ~ # hwclock --show                               1 a! K" h, Y: P$ W; a3 p
    Thu 29 Mar 2012 10:22:22 AM CST  -0.188950 seconds  
    # u% z  n6 c) t! \. m% S7 s0 z ~ # hwclock --show                                 F4 [0 ?% m6 [
    Thu 29 Mar 2012 10:22:23 AM CST  -0.244766 seconds  ; w: b. h0 ?3 v1 m' E" U0 s- O
    ~ # hwclock --show                              
    : C! O* b: l- T* e- j" JThu 29 Mar 2012 10:22:24 AM CST  -0.336868 seconds  
    3 D7 R  U) f) s: X# O4 a ~ # hwclock --show                              
    # ^" {1 I, G2 DThu 29 Mar 2012 10:22:25 AM CST  4.237159 seconds   
    ( ?  B0 P/ P8 ^; q. x ~ # hwclock --show                               4 r' |( O  ]! T1 v; l, u9 r
    Thu 29 Mar 2012 10:22:26 AM CST  4.238672 seconds   
    " l9 q6 d& m8 N ~ # hwclock --show                               ! @% f3 u9 c3 O
    Thu 29 Mar 2012 10:22:27 AM CST  -0.379418 seconds  " O  V( H2 c1 {# F9 {
    时间居然都是正确的!显然问题没有那么简单。。。那到底是怎么回事??还是从date$ z3 u! v- f5 d4 @8 ]5 e1 q
    开始查起吧:
    * \, B: U. m# @6 ^1 g% g& j$ Pdate是一个glibc里面的程序,那它的时间源是从哪里来的?查看一下glibc
    " p/ I5 Z# W6 j的源码,它的时间从这里来:
    : P( e, |3 @+ j3 W/ ]7 r6 n. Z! C$ v' b// glibc coreutils gettime.c
    & X: {% u4 t2 U& g7 ?% f3 F5 V/* Get the system time into *TS.  */3 T4 }! U' K9 x  T6 G4 |
    void
    - p2 k# ^$ _7 i( |, ggettime (struct timespec *ts): p: ^3 |9 |6 l6 h* a7 s
    {$ [# u1 ^$ V/ `& }  m
    #if HAVE_NANOTIME7 \; @/ `1 @; T/ [: w0 k
      nanotime (ts);
    % h, S' S% D2 w6 t/ S( o, Q#else
    : a/ U+ ]/ B5 f5 r . n4 g. P$ Z  w, q! ~
    # if defined CLOCK_REALTIME && HAVE_CLOCK_GETTIME
    , ^& S! j& B- E* d2 h3 t  if (clock_gettime (CLOCK_REALTIME, ts) == 0)
    4 A& K& u8 E+ J  l, V' H" C. _    return;! d& T8 R: r; p3 `
    # endif  k5 V4 q; y, M3 b& w

    6 T* U$ U  {/ _! |# if HAVE_GETTIMEOFDAY: Z2 w8 r5 e, s1 m
      {
    + g. }' i! n$ a7 o2 @    struct timeval tv;
    " \7 \" H7 n  Y" X0 W    gettimeofday (&tv, NULL);
    + H( a' K; ^% v, k( |    ts->tv_sec = tv.tv_sec;! m5 ~. c; ^8 L, H2 s
        ts->tv_nsec = tv.tv_usec * 1000;* m1 S" t$ Y. a1 }2 ?5 ^
      }
    $ ~+ N: @2 F  c4 z( Z( T# else3 G, V+ s% |+ i' o" r; \9 E
      ts->tv_sec = time (NULL);1 v0 o1 x; L. ^" C# {5 I
      ts->tv_nsec = 0;
    0 c: Y; R$ o( h4 d4 F) a# endif
    / `, o) v  y4 n4 } ' N/ ]% P: @' |8 O* w/ L0 k, b
    #endif
    , z: j9 D% g- d' P8 ^}: t/ `% ]  y5 O6 ]; a- l3 n
    按照 nanotime, clock_gettime, gettimeofday的顺序去读取,我们这个系统中就是
    5 x0 B3 d/ z. `; Tclock_gettime,从上面的小程序中也可以看到clock_gettime的时间是有问题的,那么
    6 s  V0 Y% K: Kclock_gettime的时间源是怎么来的呢?7 q3 X. g+ }$ o+ r
    // linux kernel 2.6.18 arch/x86_64/kernel/time.c
    ! `6 M9 s0 M! B+ p8 {7 ^( Dvoid do_gettimeofday(struct timeval *tv)3 m0 h3 P6 g: ]! [
    {
    5 u1 r7 X* ^( O8 o% t9 _; Iunsigned long seq, t;
    , ~5 V8 j1 \4 T, H9 ^ unsigned int sec, usec;! s$ c) X6 U: s5 o- h4 Y
    3 \9 a5 n4 q( r. [. ~$ g
    do {% @- y8 k; R) F2 Q! z4 f& F
    seq = read_seqbegin(&xtime_lock);
    6 A* B& h" b! ^2 t . T% ?& n5 U( g
    sec = xtime.tv_sec;
    / C" k, C( |3 b9 l$ r" M) ~usec = xtime.tv_nsec / NSEC_PER_USEC;6 K0 h  S% ?4 w' E0 e
    t = (jiffies - wall_jiffies) * USEC_PER_TICK +
    + \% I- X7 E' t" e% p9 ~do_gettimeoffset();
    . R: }4 G6 l0 Eusec += t;
    , {# }4 I  ~$ ]' n 0 l6 ^; L5 \7 R' L- c  ]' D9 B  F
    } while (read_seqretry(&xtime_lock, seq));
    9 c# t( b/ G; r; `  y
    + }6 O& n! I* y. d" ?! q* vtv->tv_sec = sec + usec / USEC_PER_SEC;
    6 _0 i- j3 x( B& o7 D% [tv->tv_usec = usec % USEC_PER_SEC;
    ( y1 r/ y' O; k4 C* ]}
    5 [6 {+ G, c1 r$ m( p反正就是一通计算,其中比较重要的是 do_gettimeoffset2 Z% I4 Q* l5 L7 T& o
    ,这个函数的目的是计算上一次更新 xtime
    # X9 n# H1 S$ i; c2 x  v' H到当前执行这一段时间的偏移量,然后用这个偏移量去加到 xtime1 h5 C0 J) a+ I0 g
    上去,就是当前的时间(因为xtime的精度不够,clock_gettime# X$ L  w" }0 g( o7 m9 A, A- Y' F
    的精度要求更高,所以必须计算一个偏移量)。
    9 |0 [$ ], n( w' B7 u那么,这个 do_gettimeoffset又是从哪来的?同样的文件里面有:
    : H# v0 @2 A6 h: `$ Jstatic inline unsigned int do_gettimeoffset_tsc(void). e7 y! y- [# k5 Q2 q8 b5 n
    {
    * p, T& P) o5 ounsigned long t;
    4 r9 n' [! a: X" j6 T8 [& Xunsigned long x;2 d1 N( s* H8 e6 _
    t = get_cycles_sync();
    & E$ [7 s$ R3 Q% l# f) Kif (t < vxtime.last_tsc)
    ; V7 B; l/ V8 Kt = vxtime.last_tsc; /* hack */1 E. ~7 q% N# J  I
    x = ((t - vxtime.last_tsc) * vxtime.tsc_quot) >> US_SCALE;  b: K0 _# e2 _
    return x;
    & i' q8 E% ]& N# e/ ]; k}
    " a" Z7 G* u& Y. s
    ) z, P! G. G2 L" ^& n" n5 sstatic inline unsigned int do_gettimeoffset_hpet(void)
    * f& V% ^4 ]2 ]% i4 z( w{
    - A+ v8 e$ m  |# K/* cap counter read to one tick to avoid inconsistencies */
    0 T. H# ?8 v5 l0 ?! A. v/ u* n. Sunsigned long counter = hpet_readl(HPET_COUNTER) - vxtime.last;
    $ u1 g1 c# c2 a( q  w# Jreturn (min(counter,hpet_tick) * vxtime.quot) >> US_SCALE;3 i8 T3 r) a1 w" {- {
    }9 x9 B( {( }9 G5 V/ H
    , C& C7 q& P& K3 P+ Z4 ^
    unsigned int (*do_gettimeoffset)(void) = do_gettimeoffset_tsc;- w- O! c4 r1 E* G
    也就是说,go_gettimeoffset 是从 tsc寄存器从拿到的,这又是神马玩意呢? 2 z0 P5 d0 Y: F
    参考一下下面的几篇文章【1】【2】【3】,基本就明白了:# Y" ?7 H+ w9 n
    tsc是一个cpu内的寄存器,用来累加从系统启动到目前,所有的时钟周期数,全称是
    1 z* x3 y' G8 xTimestamp Counter;, I# ~% }: H! V. Q: L3 ?+ q/ v0 R
    而且,最重要的是我们发现了一个重要的线索,那就是:& y2 t9 ]9 N; ?5 {2 \/ M: k: y
    在多核cpu的架构中,tsc寄存器是很有可能不一致的,特别是在intel的比较老的多核CPU
    ; n1 f: s) F/ P中!坑爹....8 L$ \3 @# d9 i- X( y* `
    很多文章提到过这个问题,比如【1】【2】【4】,显然如果不同cpu4 l* {, e. ]7 D3 F; V9 U
    读到的值不同的话,多次执行,显然时间是不一致的。( a+ Y+ A6 x5 E3 Z( |0 ?
    看起来就是这个问题了,那么就来验证一把:
    & x6 T( d7 R( y8 {+ z2 ^#include<stdlib.h>
    $ a* ?: |2 p" F( ?#include<stdio.h>8 ~# Y4 o6 X  x3 u& c0 j$ i" Q$ [0 `
    #include<sys/types.h>* ?$ ]9 x9 ?) `, I. K0 Q7 G: F) @( U
    #include<sys/sysinfo.h>
    6 P* b8 d1 B; F/ M% O$ R#include<unistd.h>
    " X* `9 Z7 J2 w8 ~- M% `! K#include<time.h>
    , n' v9 p0 ~2 ]7 U4 v' Q
    ! a/ l8 [+ A. p6 [0 U: x- w#define __USE_GNU
    ) P9 _9 S& q8 X  ?9 d% C3 }: D#include<sched.h>3 H) E0 }; z3 u1 V1 S2 C
    #include<ctype.h>, x# v) _2 `5 h: f8 ~
    #include<string.h>
    & `$ |2 W4 F, v0 [; N0 j' r 9 A' C; M% N/ {

    % J% N) j- g4 K' c( [
    6 b- m8 x5 `3 Y7 {4 qint main(int argc, char **argv){! R* n/ g- l: \) `

    ' n$ k7 p; ^& ]5 X2 O! I1 T, }, V& I7 r        cpu_set_t cs;
    5 p! C/ y) p* l5 W& n8 `. R$ l        CPU_ZERO(&cs);9 }2 t) \0 F2 [. E
            CPU_SET(0, &cs);% o! X2 q& }) |9 _' b

    / Z) o4 o; L+ S2 I6 I% O8 l        sched_setaffinity(0, sizeof(cpu_set_t), &cs);
    ) f( m: I& K9 j: o- J- P 9 u# s$ u- L9 m+ h
            struct timespec ts;
    6 a" h) J' E9 g2 @/ v7 K# w        int ret = 0;
    7 o  ]# x4 a/ p! v" ^% a. a& o        ret = clock_gettime(CLOCK_REALTIME, &ts);
    3 G( e* x4 d2 ^1 Q$ @* z* t        printf("ret: %d, time: %lld %lld", ret,  ts.tv_sec, ts.tv_nsec);
    ) A0 }1 L" U- o: A. C 7 _- V! Q% i% G# S+ O$ }
    }3 r9 r( |$ F( ^

    " v1 c# F5 t2 Y+ u6 agcc -g -o cpu0_clock_gettime cpu_getdate.c -lrt: [. [, G9 t6 b! J: y9 ^
    , I! e+ K3 i( @% X
    ret: 0, time: 1332992815 537481000
    " F3 a/ _$ M. W0 K$ N" k* _% Rret: 0, time: 1332992816 506704000
    : n2 H9 Y  ]3 yret: 0, time: 1332992817 99100007 i3 I# E3 ]. B
    ret: 0, time: 1332992817 480431000
    + b4 e! g4 _6 d# g, R( Uret: 0, time: 1332992817 936965000
    % t! Z8 R/ V8 C5 z9 Fret: 0, time: 1332992818 4166340007 [3 m+ R9 D6 R$ O' C
    ret: 0, time: 1332992818 850319000
    ' }+ |* m3 t9 Sret: 0, time: 1332992819 305075000+ k; ]9 l3 o% H  i
    ret: 0, time: 1332992819 729108000
    ; E  J2 Q! A0 wret: 0, time: 1332992820 164726000
    % {8 J# G5 a# g( U) O5 yret: 0, time: 1332992820 577573000" j$ {' y7 \8 b1 A; ~# H
    ret: 0, time: 1332992820 985025000) ^: A" m6 t% s* U
    ret: 0, time: 1332992821 394070000
    4 n+ x1 B" J9 p; Zret: 0, time: 1332992821 8167080004 n6 b- @/ s2 {* B/ i' Y
    ret: 0, time: 1332992822 2168110006 a! \: Q( E8 ]% c' h
    ret: 0, time: 1332992822 616901000. m8 M6 [$ T1 A3 F' O7 b
    ret: 0, time: 1332992823 18564000w
    * a( _+ Y5 q4 i) fret: 0, time: 1332992823 7275850003 ^$ e% |' n. b/ k( J$ u, L
    通过把程序的调度固定在某一个具体的cpu中,那么clock_gettime的结果也是正确的!
    ! }5 Y+ z' h! k- K1 }! B经过几次测试,很容易找到两个cpu时间不一致的情况,具体这个例子来说就是 cpu0和
    6 q1 X' x& E7 W8 tcpu5,把上面的程序修改一下,可以看到结果基本上每次都会跳一把。* y, r: L- X1 U( {( H3 B
    问题解决:: ~: W! a# H; ]* r8 e* `7 `
    那么,tsc在多核cpu中的值不同,应该怎么解决呢? 如 【4】所示,在高版本的linux: F6 u1 p2 z. D  g+ ^4 s" [! C$ `+ o
    内核中,有人提交了补丁,可以在内核启动时检查并且同步一把,那么升级一下内核到 2
    5 l6 t6 K2 C$ j& S3 z.6.38。) ~# Q# _8 q' @2 U' k6 Q* r0 m  F
    重新启动到新内核之后,问题解决!
    2 A* f: k* _4 m& g总结一下:5 I( d0 o6 Q9 B  ^3 _
    1. apache的时间统计方式,没具体看实现,但是显然前后的两个时间可能是在不同的cpu
    * O. W* \; H# R& {1 ^1 c核上计算出来的。5 E: K- [. L! B
    2. tsc的值,并不能保证是同步的,特别是在某些Intel cpu
    7 o0 X9 ]+ ]0 a* ~1 n1 L的情况下;但是新的内核会做一些检查和resync的工作;
    0 O0 `+ D8 o7 j4 X  b3. time()调用直接读取内核的xtime值,不会触及硬件;但是 gettimeofday(),   w+ O* q9 E4 A( K; [) K
    clock_gettime() 在 x86_64下都会读取tsc值;! I5 K' X& z# L! ]( M# b
    4. hwclock读的硬件时钟显然不是 tsc,应该是实时时钟RTC;% @. P% P; h8 t6 y% c( R+ b. m
    【1】 http://en.wikipedia.org/wiki/Time_Stamp_Counter
    % {+ D( ~9 l0 q8 {【2】 http://juliusdavies.ca/posix_clocks/clock_realtime_linux_faq.html
    % w% k  _$ P- ]: X( n$ o# b" C  [+ `, P3 f【3】http://www.linuxforum.net/forum/gshowflat.php?Cat=&Board=driver&Number=7 q2 b0 b! H$ b# l4 }5 c
    385219&page=0&view=collapsed&sb=5&o=all&fpart , linux时钟中断机制,Copyright
    1 {$ j  z  y+ l* {$ J! Q' J2003 by 詹荣开1 I  c7 h$ g3 t4 g
    【4】http://lwn.net/Articles/211051/ x86: unify/rewrite SMP TSC sync code  

    该用户从未签到

    2#
    发表于 2020-3-9 17:20 | 只看该作者
    时间问题追查,牛逼了
    您需要登录后才可以回帖 登录 | 注册

    本版积分规则

    关闭

    推荐内容上一条 /1 下一条

    EDA365公众号

    关于我们|手机版|EDA365电子论坛网 ( 粤ICP备18020198号-1 )

    GMT+8, 2025-11-25 18:45 , Processed in 0.156250 second(s), 23 queries , Gzip On.

    深圳市墨知创新科技有限公司

    地址:深圳市南山区科技生态园2栋A座805 电话:19926409050

    快速回复 返回顶部 返回列表