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

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

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

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

    [LV.1]初来乍到

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

    EDA365欢迎您登录!

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

    x
    ' \4 a! B( h7 c4 F! t
    问题引入:
    % o% S+ x7 n  S2 i) H" u1 f" \6 u问题是我们重装了一台电脑之后,发现apache- j, p5 \8 i  t8 B/ R
    的日志里突然增加了很多服务时间超长的请求,如下面两条:0 X' e! a9 i% D' V
    ** - - 4913229 11227 [29/Mar/2012:09:59:58 +0800] "GET*****8 S) q# q% K$ k8 R
    ** - - 4793564 9846 [29/Mar/2012:09:59:58 +0800] "GET *****
    & r9 [3 ]3 Z- u! L3 H请求服务时间都在4s以上!这绝对无法忍受啊!
    1 \' Q4 x" M) W9 \: x1 C
    7 s; s3 E4 n- Y* s1 a: f# w/ I分析过程:/ {# U0 d) b2 }  |% U1 I! W$ ^
    / c8 f' d8 l- s9 i
    但是,当我们:
    1 @6 |. j- \! _& {4 R  F! C, k * 手动访问这台服务器之后,发现基本上请求都是正常响应的;. t3 U0 K( Z: Z
    * 从前端反向代理的日志来看,服务时间也是正常的;
      W% n. `+ ]. v& f' [8 j那这样推断,真相只有一个,那就是apache的日志时间记录有问题!
    . n2 G1 q0 H+ ^& c8 V2 M' E8 x
    8 [- `2 [: D2 p, X9 D* Y什么问题呢?不清楚,但是apache
    % U& ~- }# V3 X* p$ S/ [) B的时间记录不外乎是在收到请求时记个时间,请求处理完成之后再记一个时间,既然是时?* G! B. {8 d8 s( c) `; |/ q& H
    涞奈侍猓敲淳涂纯聪低呈奔浒桑幼罴虻サ?date 开始,多执行几次:6 u' ^) A4 a7 ^. t! v
    apache2 # date
    / T1 y8 m0 T: O, M$ v# x8 {  kWed Mar 28 17:25:08 CST 2012; [2 y3 T6 Z' @' S+ k
    apache2 # date
    ; }4 s3 Q$ D6 X; A' j& AWed Mar 28 17:25:04 CST 2012  时间减少了?0 t) t, P) n/ O! l
    apache2 # date
    8 Y( Y5 Q/ j% j) J/ y7 a$ v% KWed Mar 28 17:25:06 CST 20128 t' U. l0 T: X+ x9 V6 @
    apache2 # date
    7 v% Y! h  b( g5 zWed Mar 28 17:25:12 CST 2012- v, f: ~( B" e' b0 \0 I; u
    apache2 # date
    0 G: c2 v0 I" aWed Mar 28 17:25:13 CST 2012
    ) p" P6 V0 u! t/ q$ U. B1 O7 `apache2 # date
      t+ i9 w1 n  A4 P  vWed Mar 28 17:25:09 CST 2012 时间减少了?/ h$ y6 b$ f9 [, u3 S# Q* R
    apache2 # date0 P. D0 r, Y+ I6 I* o/ g4 H
    Wed Mar 28 17:25:09 CST 2012
    # ~0 L/ V9 B1 d% y- Capache2 # date2 L, H+ h2 Q2 ?+ x7 x
    Wed Mar 28 17:25:15 CST 2012
    + M4 j3 R6 y3 ^6 T那么第一个怀疑就是系统里有进程在自己不断的调时间,比如ntpdate, ntpd
    % [9 C" k& v; B6 V0 }& `,那么我们首先把 ntpd给停掉;然后系统也没有任何地方会调用ntpdate;0 c1 F- b* i3 K3 ~
    继续观察,发现停掉ntpd后,现象依旧!" V1 B4 }, G7 x6 i
    还有什么可能呢? date
    7 f. S; D; i* G  {8 i" ~程序是从内核里面读取时间还是说需要读取硬件时间?到底是怎么计算的呢?0 H4 ^6 N" p* w9 @) C
    请教下同事们,据说 sys_time 是读的kernel里的时间,而 sys_gettimeofday5 f! |- N' q; B9 f7 u! K; W; C5 k
    是需要读硬件时钟的。( W9 c0 v9 G5 h$ N& l2 x
    那么就测试一把吧:
    ; m! `$ b8 [) c3 |9 x: S先写个程序调用 time(), 然后打印结果,结果是这样的:8 h7 e0 r1 w! Y+ A% ^' U% G
    13329866905 f& b2 }# T. U3 \4 G# B
    1332986692
    ; W; G$ h+ P! c1 F5 i# T& ?* y  }* O1332986693, [* {8 n: {9 |6 M: ^
    1332986693( N1 ?3 `: l% O7 K
    1332986694; X: D* Q# Y, ?& |
    1332986694
    2 {, S7 F/ @4 O6 N% o0 S; {, {1332986695
    ( [. N1 ?2 L9 x1332986695
    3 U; b$ r8 H9 n; |13329866963 T  H% q$ A6 v: R
    1332986696; B# F, `, L4 ~3 i) T' s
    1332986697$ X* u9 z3 w0 q( _4 C5 \2 {
    13329866971 M; Y  U: P9 Y6 g5 }* E
    1332986698
    $ e$ {% y/ k6 y: X- m5 T8 K1332986698. D& C0 a$ Y  s: Q& w5 Y
    1332986699- b  ?& O! {, e# E4 [/ k
    确实没有问题了!看来有可能是硬件问题了,再看看clock_gettime 的调用:5 k$ W; b; f2 ^# D
    {
    ! E2 D5 ?* c7 e8 K        struct timespec ts;                              
    8 S% y5 h: T7 _        int ret = 0;                                      8 M7 ?: U$ B& X. {, P, \5 V5 }
            ret = clock_gettime(CLOCK_REALTIME, ts);          + c2 A* J" ]* ]# H" J1 X
            printf("ret: %d, time: %d %d", ts.tv_sec, ts.tv_ns' P' D. c' Q( H( s  O6 W
    }                                                         
    4 Y: S  P' D% |% T编译:        gcc -g -o clock_gettime getdate.c -lrt            
    ! G/ C5 p( K- s7 }                                                         
    : K9 p4 d8 ?: V) @) X5 O4 o# }, E1 }~ # ./clock_gettime                                    ) H1 S( U, s# M0 u5 U- }2 I/ g# \. s
    ret: 1332991087, time: 594076000 -1
    ' P# A9 S" G3 n7 R2 Uret: 1332991088, time: 379009000 -1
    ( `! o9 W4 C! Z0 m9 b! T. p! D' nret: 1332991089, time: 68561000 -1
    4 j5 |2 ^2 W. I4 Y; h6 ~ret: 1332991089, time: 714013000 -1/ O4 {& r, F4 c4 E
    ret: 1332991086, time: 511250000 -1 时间在跳
    " Q, a, v& `8 fret: 1332991091, time: 945626000 -1
    + u$ F5 V' Z5 ^( f1 c9 }ret: 1332991092, time: 650479000 -1
    3 ?" g8 A6 C8 k% C. K' K) Zret: 1332991088, time: 734780000 -1, T& H) U1 A- X( ~6 p6 r; ^# p
    ret: 1332991094, time: 506114000 -1时间在跳                    ) \( K6 p  x/ h3 _! n5 h! {) t; k
    看来果然有可能是硬件问题!
    9 i& B, O5 [; i# C/ M% a2 c那么,检查一下,直接调用一下 hwclock --show ,看看是不是可以验证这个问题:6 i! T% W3 w2 f% [8 J
    ~ # hwclock --show                               : x$ F* R4 B& S2 i
    Thu 29 Mar 2012 10:22:21 AM CST  -0.948923 seconds  
    3 r, @$ W) m" G! q ~ # hwclock --show                               " y& ?9 l+ ^- P3 A0 |1 E9 a
    Thu 29 Mar 2012 10:22:22 AM CST  -0.188950 seconds  - L8 m5 `: u( J1 D
    ~ # hwclock --show                               : g9 m4 a. G' ?6 D
    Thu 29 Mar 2012 10:22:23 AM CST  -0.244766 seconds  
    + Z2 n' M1 E6 X8 F+ d+ { ~ # hwclock --show                               9 T) Z9 {5 i6 f! o
    Thu 29 Mar 2012 10:22:24 AM CST  -0.336868 seconds  : j7 H9 o! `! w. S% g7 Q7 V+ }
    ~ # hwclock --show                              
    $ m: V  y; c+ ~: cThu 29 Mar 2012 10:22:25 AM CST  4.237159 seconds   / G! Q# j, d) R7 q2 c3 \" W# d, k
    ~ # hwclock --show                               / O( b: K7 ]0 v% q) p
    Thu 29 Mar 2012 10:22:26 AM CST  4.238672 seconds   
    + M8 j0 ]  f! k ~ # hwclock --show                              
      \+ e# M& X/ \: g, u4 NThu 29 Mar 2012 10:22:27 AM CST  -0.379418 seconds  
    1 e" O9 i5 A. a6 u. e' i  Y0 F+ U; Q2 b& B时间居然都是正确的!显然问题没有那么简单。。。那到底是怎么回事??还是从date  ]! M, n1 v. }5 s6 b  B
    开始查起吧:5 X2 g/ r! h7 m/ r
    date是一个glibc里面的程序,那它的时间源是从哪里来的?查看一下glibc
    2 C7 S/ N3 U, {, ?. R3 w2 {: w的源码,它的时间从这里来:8 _) B& ~4 v/ n+ w7 O7 y" w2 Q
    // glibc coreutils gettime.c
    6 N" x9 q" |0 S" n/* Get the system time into *TS.  */( S& f7 c) j$ L4 M
    void
    2 P  ?6 ^7 ^% ygettime (struct timespec *ts)) N5 O; E/ x2 u1 q
    {( c, G. x/ C( D) R  A) ?
    #if HAVE_NANOTIME
    $ ?4 v' u: u, j- s; y  nanotime (ts);1 A9 o4 I1 G% c1 K5 k
    #else
    4 ~% ^  s, r$ V  c# @: K 1 }2 V6 B: g- X& p1 J" p
    # if defined CLOCK_REALTIME && HAVE_CLOCK_GETTIME! y" T/ }9 U; |! \, }0 |' ]
      if (clock_gettime (CLOCK_REALTIME, ts) == 0)8 _  M! e. A% c. D' `7 q9 s5 `( g
        return;+ _( ?/ [5 u+ t- ?) I% e+ v- B3 R
    # endif1 B: N) \/ j: ~, z' n8 n
    # ]1 a( w! U2 i
    # if HAVE_GETTIMEOFDAY
    : g9 ~% ^0 k8 D) J1 W- r0 [  {+ V& _: R  U" k
        struct timeval tv;$ D; d# u4 m) `. \* d
        gettimeofday (&tv, NULL);* q1 O2 p: M6 i6 R% ?$ w
        ts->tv_sec = tv.tv_sec;
    - T  f( N; k- {9 s7 M( o    ts->tv_nsec = tv.tv_usec * 1000;
    4 c3 u8 a1 [1 x. N, m$ x% s  }: L! F* P" L5 R
    # else6 U+ B2 u1 j0 ~* Y( e
      ts->tv_sec = time (NULL);2 {2 a% ~0 a0 {  D. z
      ts->tv_nsec = 0;
    . H5 m* e* S7 q! B+ B" N8 [# endif- ^5 V) `6 t) _) ?. z

      G5 F  L5 q! C& S. N#endif
    # @! y  m2 q5 u% P  v* z' K}6 y$ Q; w; F9 Y/ d- E* ^# i
    按照 nanotime, clock_gettime, gettimeofday的顺序去读取,我们这个系统中就是 7 D& J6 P( \4 H
    clock_gettime,从上面的小程序中也可以看到clock_gettime的时间是有问题的,那么
    4 s3 ?; _: m: r/ ^clock_gettime的时间源是怎么来的呢?
    5 S" _4 q; M0 ?" B) y// linux kernel 2.6.18 arch/x86_64/kernel/time.c+ @" w- I5 H$ Z5 v! R
    void do_gettimeofday(struct timeval *tv)7 m; J+ u! S* t% w5 e  k
    {  S  s; c5 Q& _# j5 a* e
    unsigned long seq, t;) H; r& q) K9 a: P( e" p
    unsigned int sec, usec;% _0 R. ?: f0 l! }, V3 m

    - d. L$ _: M6 y& ?- hdo {
    2 N' l6 [& \) nseq = read_seqbegin(&xtime_lock);
    # U$ V2 \( x! \7 M
    2 ]6 O% s' `7 J7 M- psec = xtime.tv_sec;
    5 I  B& D' m. R1 d2 J7 Ausec = xtime.tv_nsec / NSEC_PER_USEC;% F5 J2 I% S: S  Q" p  _: |
    t = (jiffies - wall_jiffies) * USEC_PER_TICK +
    * d. z# q% d6 x& R7 c$ ido_gettimeoffset();
    4 D; V: [7 S- G+ m, |8 }7 X, husec += t;2 n1 K# Y6 [5 y
    4 ]2 O( M( d. }! C( X) P
    } while (read_seqretry(&xtime_lock, seq));
    0 k/ u; w& j( c$ a  l4 ^: R
    ( f* T* j, S$ l; [. ?% Ytv->tv_sec = sec + usec / USEC_PER_SEC;
    3 z7 \! E$ ]6 _tv->tv_usec = usec % USEC_PER_SEC;/ \0 o  u" A" F' t) w- I, _; C
    }
    ; M* M+ B$ y, J0 |) \反正就是一通计算,其中比较重要的是 do_gettimeoffset
    7 R! p0 n( r: }* w,这个函数的目的是计算上一次更新 xtime* N4 d' S* {6 A1 W3 l  ~
    到当前执行这一段时间的偏移量,然后用这个偏移量去加到 xtime
    9 p  M$ O, L! z6 R/ _( G上去,就是当前的时间(因为xtime的精度不够,clock_gettime
    + P( C. F& q& c* q$ @+ j0 ?的精度要求更高,所以必须计算一个偏移量)。
    * o7 G) k& @0 B/ o  O: Q+ F- C# Q那么,这个 do_gettimeoffset又是从哪来的?同样的文件里面有:
    $ h' S4 ^3 }- w9 c* H- Vstatic inline unsigned int do_gettimeoffset_tsc(void)
    / z  y; Q! W! A. o{
    5 H8 f/ I- g5 f. |" uunsigned long t;
    9 x( q& ~+ ?* w% V8 T  e0 Wunsigned long x;# w; z, d5 Y6 r6 S4 L
    t = get_cycles_sync();
    8 J( g- u& w5 pif (t < vxtime.last_tsc)
    ' C3 ^$ V" D) ?8 lt = vxtime.last_tsc; /* hack */
    2 q3 y, Q: B8 f' W) C) fx = ((t - vxtime.last_tsc) * vxtime.tsc_quot) >> US_SCALE;
    . ^3 v8 I) P" V& E: ereturn x;
    4 T. J$ |$ k/ `$ x7 r& @, ^}
      Q  X- g( E" q. t7 z$ g* v' A% L9 S + w! o  w1 |4 y2 b; S# D
    static inline unsigned int do_gettimeoffset_hpet(void)1 P& m. p  |9 ^
    {
    $ Z: E: n& I$ I8 m& g' `4 o6 q! d! h/* cap counter read to one tick to avoid inconsistencies */2 s) X2 l- j! U& Z* d% `1 f
    unsigned long counter = hpet_readl(HPET_COUNTER) - vxtime.last;
    ' u2 L& d1 e' k' M9 f; Vreturn (min(counter,hpet_tick) * vxtime.quot) >> US_SCALE;
    + g8 W5 ?1 |) }: {( I+ }4 ]& \}
    . |" l' v  o8 N# R: ? 0 h& z& `4 y' B3 Q# c
    unsigned int (*do_gettimeoffset)(void) = do_gettimeoffset_tsc;# |- Z0 o7 K9 }4 i' ?. C9 h
    也就是说,go_gettimeoffset 是从 tsc寄存器从拿到的,这又是神马玩意呢? ( {" M$ b  p  Q- }+ k1 W, ]: a3 u
    参考一下下面的几篇文章【1】【2】【3】,基本就明白了:9 A( O' n; }4 B$ J
    tsc是一个cpu内的寄存器,用来累加从系统启动到目前,所有的时钟周期数,全称是 . H0 y; e7 w4 _7 W5 b
    Timestamp Counter;
    ( F- I5 h& y" c# y; m3 `+ {而且,最重要的是我们发现了一个重要的线索,那就是:
    : b/ o1 ?* B& O4 W在多核cpu的架构中,tsc寄存器是很有可能不一致的,特别是在intel的比较老的多核CPU
    + s3 k4 P- I: ]7 V& E* |中!坑爹..... c1 D" Z8 g" V! L& V
    很多文章提到过这个问题,比如【1】【2】【4】,显然如果不同cpu
    , R5 r1 C, W  |, v3 r读到的值不同的话,多次执行,显然时间是不一致的。
    ' C* O/ f; t% W看起来就是这个问题了,那么就来验证一把:
    % i# z0 n+ o( p: [#include<stdlib.h>' \9 l3 I, t6 I$ n7 s
    #include<stdio.h>
    : e2 r' o" s9 A) \#include<sys/types.h>
    $ e: @- _6 Q% Q  `2 F) S" w8 o6 D#include<sys/sysinfo.h>
    ' F" |! S3 S* F#include<unistd.h>& r7 r# S) e+ G$ \+ r# q
    #include<time.h>: V5 ?+ G6 ~+ H' x* ^
    * V: H( [  K1 }. y+ B
    #define __USE_GNU
    % ~1 }0 y( ~* o; y#include<sched.h>- i. X5 v: ^4 L/ B; t- C2 w5 {- i
    #include<ctype.h>6 q$ w! g5 i: N  \. ?, X
    #include<string.h>
    + _# R5 P" E7 I& Q  `: R! ]* @' ? # b0 d+ ~6 R1 R1 G

    ( Z) z- b, e0 a6 J) }2 G2 Y
    7 |9 b& J! R7 l9 d/ lint main(int argc, char **argv){1 r/ c1 B, G& v7 }5 [+ ]$ ]

    + m6 z& G# `; z3 t; m* |        cpu_set_t cs;; b7 A3 t  L6 K( @: [8 X
            CPU_ZERO(&cs);
    8 Q( u- N& O! B        CPU_SET(0, &cs);6 x! t9 ?: h: R# n% ?

    ; Q* z# H& y$ D% |6 J        sched_setaffinity(0, sizeof(cpu_set_t), &cs);% D% y0 _  q' y& X  F4 R3 c: V( c

    6 Y, U) b6 z1 o+ ^$ ~  g        struct timespec ts;% {8 }/ K$ G! S3 @# c
            int ret = 0;4 z4 @. [5 R9 F- c2 O0 y: c
            ret = clock_gettime(CLOCK_REALTIME, &ts);
    % m5 a5 P5 K0 V8 n; V        printf("ret: %d, time: %lld %lld", ret,  ts.tv_sec, ts.tv_nsec);
    6 R7 [3 _$ {% R/ [  ? 2 x6 c1 y+ |; u' U1 T
    }$ f0 \1 |9 h4 e
    ( B  _! B* k- h. ^. I
    gcc -g -o cpu0_clock_gettime cpu_getdate.c -lrt8 K( k8 p+ K1 l4 x, ?
    * D- v. ?! \/ e) v
    ret: 0, time: 1332992815 5374810007 V4 R) c1 E% z4 s5 O; V
    ret: 0, time: 1332992816 506704000
    ; A1 c1 @8 A6 Q5 ^ret: 0, time: 1332992817 9910000
    + l+ j& z* X- D, \' y& @: Pret: 0, time: 1332992817 480431000
    * H$ r7 Z! Z/ x- I/ {ret: 0, time: 1332992817 936965000
    . a# d! s" y$ Z: H4 k5 Z9 u% Dret: 0, time: 1332992818 416634000
    , u+ q5 T7 Q) B2 n: Nret: 0, time: 1332992818 850319000
    % G: ^. R6 e0 J' y  I8 mret: 0, time: 1332992819 305075000
    8 G% L8 P5 J, B. h! m' Fret: 0, time: 1332992819 729108000
    : M, I5 ~& G2 K1 o# ~8 iret: 0, time: 1332992820 164726000
    8 t9 |' g7 Y: h: M7 l4 Pret: 0, time: 1332992820 5775730007 M0 z9 T5 {% H- F
    ret: 0, time: 1332992820 985025000' [# W5 Z' Q) _4 @* J& _4 D
    ret: 0, time: 1332992821 394070000& `7 r4 W6 j/ A* `6 ^
    ret: 0, time: 1332992821 816708000
    4 X( G0 H/ F' Z% [; D8 f, oret: 0, time: 1332992822 216811000
    0 n3 f6 P9 _. [9 Q5 vret: 0, time: 1332992822 616901000" Z8 p! w6 T4 {4 i# E6 a
    ret: 0, time: 1332992823 18564000w
    ) }$ C# p. C$ Sret: 0, time: 1332992823 727585000
    0 _/ ~4 G- J1 c& n' `7 ^通过把程序的调度固定在某一个具体的cpu中,那么clock_gettime的结果也是正确的!
    ' v$ ?, c+ ]- w) r! O$ {经过几次测试,很容易找到两个cpu时间不一致的情况,具体这个例子来说就是 cpu0和: w$ [8 h8 O4 O3 T9 u( R
    cpu5,把上面的程序修改一下,可以看到结果基本上每次都会跳一把。7 G% l, y- z. k6 S; e$ a" ]5 N
    问题解决:
    , P" W& t4 K, ]* {, h4 {$ |那么,tsc在多核cpu中的值不同,应该怎么解决呢? 如 【4】所示,在高版本的linux
    ; a5 p3 I' f, D内核中,有人提交了补丁,可以在内核启动时检查并且同步一把,那么升级一下内核到 2: @# Z! D6 \: B8 g
    .6.38。( ^# `6 T$ F8 E' o
    重新启动到新内核之后,问题解决!$ o# T' w+ s1 b
    总结一下:
    : X1 B8 Q9 o! }1. apache的时间统计方式,没具体看实现,但是显然前后的两个时间可能是在不同的cpu
    + |8 t* f. a+ x: q) z8 v3 D8 c核上计算出来的。( b3 K$ C+ M% B
    2. tsc的值,并不能保证是同步的,特别是在某些Intel cpu. Q6 B$ l6 z  z1 E' m, f( _
    的情况下;但是新的内核会做一些检查和resync的工作;, j* f+ m/ Q6 E; u/ T
    3. time()调用直接读取内核的xtime值,不会触及硬件;但是 gettimeofday(), ) c- s% j0 E) Y
    clock_gettime() 在 x86_64下都会读取tsc值;
    , |$ W- e1 f! |- x7 b+ N) [1 v4. hwclock读的硬件时钟显然不是 tsc,应该是实时时钟RTC;* x/ n4 ]1 b0 e1 B1 |
    【1】 http://en.wikipedia.org/wiki/Time_Stamp_Counter" Y* ~) L" V6 y* E5 m9 P5 B# @
    【2】 http://juliusdavies.ca/posix_clocks/clock_realtime_linux_faq.html1 b. ~. H4 ~/ x3 w, e
    【3】http://www.linuxforum.net/forum/gshowflat.php?Cat=&Board=driver&Number=4 }0 W5 E0 L! a: X' D' N
    385219&page=0&view=collapsed&sb=5&o=all&fpart , linux时钟中断机制,Copyright . f. I# \: c) P! T
    2003 by 詹荣开$ X& M1 s9 s; E  }6 l
    【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 23:55 , Processed in 0.156250 second(s), 24 queries , Gzip On.

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

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

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