TA的每日心情 | 怒 2019-11-20 15:22 |
|---|
签到天数: 2 天 [LV.1]初来乍到
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
! p1 r3 k' e7 x4 I
问题引入:7 t l) X$ U5 x0 I
问题是我们重装了一台电脑之后,发现apache
+ |+ ?$ M# v$ H; `2 i N的日志里突然增加了很多服务时间超长的请求,如下面两条:
8 U4 |1 i \$ w g** - - 4913229 11227 [29/Mar/2012:09:59:58 +0800] "GET*****
8 v. v: ]! }: |* C# W- v** - - 4793564 9846 [29/Mar/2012:09:59:58 +0800] "GET ****** _- c" ]2 u2 F5 \6 b% U
请求服务时间都在4s以上!这绝对无法忍受啊!% c% G' D: N5 \
4 h1 a, M1 k# v0 Y7 }6 P2 h' ^分析过程:) a- V7 E8 n# } y/ R2 U
6 `4 G# p0 j: z) V! \但是,当我们:
6 ^( M( [7 H6 f* v& P" s$ L4 r4 V * 手动访问这台服务器之后,发现基本上请求都是正常响应的;
$ `8 ~& N: j; V6 j! n * 从前端反向代理的日志来看,服务时间也是正常的;3 u0 K+ U0 k) W
那这样推断,真相只有一个,那就是apache的日志时间记录有问题!6 A/ t& @+ g2 f3 S5 B
) W6 V* b: U& j- C$ Y/ b! E
什么问题呢?不清楚,但是apache
( v. _1 H; Z6 T4 _+ Y' E6 d# h的时间记录不外乎是在收到请求时记个时间,请求处理完成之后再记一个时间,既然是时?
5 b, m2 p" y7 h4 o$ i6 t x1 Y0 W涞奈侍猓敲淳涂纯聪低呈奔浒桑幼罴虻サ?date 开始,多执行几次:
: s7 d2 U7 e* n# | apache2 # date
, R' g) G7 K. [2 E$ \Wed Mar 28 17:25:08 CST 2012
b( E) ^6 |+ A; _0 A5 oapache2 # date
7 A; K1 D6 \) R" V& j q$ O4 NWed Mar 28 17:25:04 CST 2012 时间减少了?- Y6 Z6 C5 `2 k8 G: Z5 V$ a U
apache2 # date
1 ^- S* l5 U9 \3 L2 {9 `Wed Mar 28 17:25:06 CST 2012. `: m3 T; s+ ?( E( y
apache2 # date$ G3 A9 `: k0 V \$ R2 E5 M
Wed Mar 28 17:25:12 CST 20125 d! {/ g: a+ Q% l+ k: }- o0 X" d
apache2 # date/ O% S9 x* R$ r C/ L: Z3 a
Wed Mar 28 17:25:13 CST 2012
% a8 L+ O6 a# A. n4 Q" ^/ v4 {apache2 # date8 y; b; K3 I/ |5 k3 ^
Wed Mar 28 17:25:09 CST 2012 时间减少了?
* k4 D! C& b' u% papache2 # date
7 z$ L" F* f' @9 y( }( lWed Mar 28 17:25:09 CST 20125 @3 F; K# V' e, a
apache2 # date
7 @( n1 x, L7 f" c& ]6 P, QWed Mar 28 17:25:15 CST 2012
$ w. w- L9 g6 A0 L那么第一个怀疑就是系统里有进程在自己不断的调时间,比如ntpdate, ntpd' m9 \9 V/ h7 I3 d
,那么我们首先把 ntpd给停掉;然后系统也没有任何地方会调用ntpdate;
8 n6 L) `: c+ |- O3 N; h继续观察,发现停掉ntpd后,现象依旧!
/ B' y$ @# o& j0 L还有什么可能呢? date% C, O5 X" f2 q. P4 s
程序是从内核里面读取时间还是说需要读取硬件时间?到底是怎么计算的呢?
# Q8 A* j Q; I请教下同事们,据说 sys_time 是读的kernel里的时间,而 sys_gettimeofday
9 G6 u' ]. O. ? l5 P' l2 X是需要读硬件时钟的。
9 L6 g J) t, s" ^' V) R) Z# F那么就测试一把吧:' M3 f/ R- r% v( w* Z
先写个程序调用 time(), 然后打印结果,结果是这样的:
% N8 Q- [7 B; v! N$ A0 V) e. _8 E13329866909 c+ x3 U2 w# n8 ~8 K. E; W
1332986692
3 t1 F; O7 h7 i% U1 E: R+ Z' s1332986693+ _ f6 ~9 ?6 n3 C' ?
1332986693/ B5 p: E: ?: z3 F8 h: j
1332986694
) t% `0 n8 o! T$ S1332986694
; Z% c* m: U6 B+ S3 S: t; ]1332986695
; t; H. P& u8 c x% Z* m1332986695
- f# q6 B. v' U; {1332986696* b- E+ t/ y; ]
1332986696& O' F% `4 C! I( n# }
1332986697
6 e& c8 a8 {( \6 r1 ^8 z13329866976 _* B' y5 A1 T6 \ W8 R. @
1332986698
; P3 \+ c1 ~2 Z3 c6 A4 y1332986698 D5 x/ z; P# }2 [9 q! |& g7 Y
1332986699
% ^! A3 B- @4 K: x- i- ^# P. y s1 Y确实没有问题了!看来有可能是硬件问题了,再看看clock_gettime 的调用:
) W. F g2 j1 D5 i: T{6 a8 D3 c' i! e" i5 h1 x& [- e* |
struct timespec ts;
4 g/ Y3 t; W8 j/ @; u int ret = 0; ! d. I* k& X4 D- ~
ret = clock_gettime(CLOCK_REALTIME, ts);
5 o3 i' ]- M0 p" P. ` printf("ret: %d, time: %d %d", ts.tv_sec, ts.tv_ns
, R2 ~/ W* D2 x}
$ ~6 c0 z% j4 ?% Z0 g编译: gcc -g -o clock_gettime getdate.c -lrt
. {* ?" y- e: y* } 5 g# c( i' S) O4 j7 _9 Q3 u
~ # ./clock_gettime
1 ?: \7 g: P: J+ ]% `5 n& Dret: 1332991087, time: 594076000 -1
: G& l9 ~. c% y- ^, Tret: 1332991088, time: 379009000 -1* _* v3 P5 w( W7 _ z9 O* Z
ret: 1332991089, time: 68561000 -1: c" s) {8 b+ a+ z
ret: 1332991089, time: 714013000 -11 n) y: |) l1 Y
ret: 1332991086, time: 511250000 -1 时间在跳2 v3 I: X- T! k; w5 {' F
ret: 1332991091, time: 945626000 -1
+ @4 R8 M8 w8 hret: 1332991092, time: 650479000 -1
# K( z( o4 B9 V) w0 ?4 {0 oret: 1332991088, time: 734780000 -10 s% J; w+ W% o" m
ret: 1332991094, time: 506114000 -1时间在跳 ( I" A5 |6 ~2 m; H f
看来果然有可能是硬件问题!# U2 b: K6 ~* @. y0 e- }5 a- |
那么,检查一下,直接调用一下 hwclock --show ,看看是不是可以验证这个问题:
/ K9 N" z( F: N; B. V1 e% d4 @+ y ~ # hwclock --show + I' ~! p* d) |
Thu 29 Mar 2012 10:22:21 AM CST -0.948923 seconds - Q- l4 T/ d: ? N' z& g
~ # hwclock --show ; H9 ^" h3 Q1 p( r
Thu 29 Mar 2012 10:22:22 AM CST -0.188950 seconds
+ ?. F: s) H/ s) _# R. R1 b ~ # hwclock --show
' A/ h; ^0 d: g" X* kThu 29 Mar 2012 10:22:23 AM CST -0.244766 seconds , S+ H. P! ~! C7 I* J
~ # hwclock --show
F; D! r8 M, m0 l% bThu 29 Mar 2012 10:22:24 AM CST -0.336868 seconds
+ G1 v4 K* y9 \ ~ # hwclock --show ( g4 h a* h. K; s+ s0 C4 k
Thu 29 Mar 2012 10:22:25 AM CST 4.237159 seconds
4 h# D7 k2 {& R# }# i ~ # hwclock --show + a8 M& S [$ r: M
Thu 29 Mar 2012 10:22:26 AM CST 4.238672 seconds 4 S% G1 Y5 X0 f8 D7 z' m
~ # hwclock --show
9 f9 S9 R( L r. z% IThu 29 Mar 2012 10:22:27 AM CST -0.379418 seconds - m, d/ k0 L5 R* Y% ^* L
时间居然都是正确的!显然问题没有那么简单。。。那到底是怎么回事??还是从date M1 C* M3 V- I% U, C$ J
开始查起吧:' |; b: L( H0 {8 Q: |. n
date是一个glibc里面的程序,那它的时间源是从哪里来的?查看一下glibc6 m4 j7 h! ^& \; c) j+ R; f) A' K
的源码,它的时间从这里来:
/ o! B" o+ K3 k) `0 C5 R// glibc coreutils gettime.c
- N4 J$ G- ?0 e6 z7 G$ W/* Get the system time into *TS. */
0 \$ y, o F+ j4 _void# Y* Z( I, l7 {) Y1 `/ q& k6 T
gettime (struct timespec *ts)
0 a/ }: H6 n$ V1 s' p/ p- F{
2 U4 f- Q8 e6 [- L/ i& w#if HAVE_NANOTIME
# P8 M5 q8 q( X/ P2 d& B) Z nanotime (ts);
) ]' X) T( J1 Q#else
. q+ V) ]! D- r/ @! O
$ e! O6 A0 X3 d1 |8 L: h- u/ ~& `: ~# if defined CLOCK_REALTIME && HAVE_CLOCK_GETTIME' ~6 ]1 L% @+ _
if (clock_gettime (CLOCK_REALTIME, ts) == 0)) ~' s7 j1 V4 ?. z0 t5 U6 D. _) k. K+ o
return;
# k5 @2 D2 m0 F: }5 n# endif/ L; f3 @& [, j
/ E( ]$ V5 r. X1 f7 j7 H
# if HAVE_GETTIMEOFDAY$ p$ L% }( h/ z W' h5 H/ [
{8 d; C1 @8 }* |- d
struct timeval tv;: x1 J9 L E( L) S; o8 N* V% k
gettimeofday (&tv, NULL);' U3 @- T% _, d1 B
ts->tv_sec = tv.tv_sec;
! I1 b8 {1 j) h* G' G1 f. L ts->tv_nsec = tv.tv_usec * 1000;
9 f7 z7 i/ J$ {" x7 b- U# i }0 ^4 M R' h5 H6 O" ]. W
# else: d: k7 c9 _6 ?8 [3 O
ts->tv_sec = time (NULL);# [. s3 E4 o) G/ Y; g3 s) G" u
ts->tv_nsec = 0;
/ n9 T. w# h/ F- ~% _; |# endif# o5 |. y! p% {' y
5 |/ g9 k5 |) I
#endif6 e( t" A$ b6 \$ W4 ` n% W
}
1 J1 K* h1 G4 U5 N: c按照 nanotime, clock_gettime, gettimeofday的顺序去读取,我们这个系统中就是
1 L5 z" }2 g- Q& a( a' [/ Eclock_gettime,从上面的小程序中也可以看到clock_gettime的时间是有问题的,那么1 C+ R$ f; x. z4 x# y' A
clock_gettime的时间源是怎么来的呢?$ I% i+ Z# |" g: _0 `& \& I) C
// linux kernel 2.6.18 arch/x86_64/kernel/time.c
$ `, z% t! X# k7 m% Xvoid do_gettimeofday(struct timeval *tv), L# q6 r' k r8 O+ k' r
{: `1 ~* E: v+ d7 w2 I7 T( W
unsigned long seq, t;
& w+ N$ ^+ n2 _8 a1 J8 y$ p4 \ unsigned int sec, usec;
, A# O' C& E$ k1 X& _ # B$ ?$ l- k* h& g9 P8 }6 \! N8 j
do {$ H ?1 D8 _) a/ e
seq = read_seqbegin(&xtime_lock);
8 y& K- Q0 a7 J2 Q , f. I. I3 A/ J e
sec = xtime.tv_sec;
9 v9 {$ o1 u; k, `* M; b! qusec = xtime.tv_nsec / NSEC_PER_USEC;
) x" G& Y% B8 X/ r* A* xt = (jiffies - wall_jiffies) * USEC_PER_TICK +
6 ~' L C2 {$ `/ odo_gettimeoffset();
6 k5 K0 o( s( S4 | Y; j' ?usec += t;$ Q9 l2 _ x' h+ a4 d# f3 X: _
/ z K# l% E6 j# S7 w
} while (read_seqretry(&xtime_lock, seq));, ~9 B5 \* }4 p) r
3 g- f* \4 U! |! S% w5 ^1 F- P
tv->tv_sec = sec + usec / USEC_PER_SEC;
- X3 {+ X, q0 h4 C! c& `tv->tv_usec = usec % USEC_PER_SEC;5 C" @% }, \" ?. ]! ]2 n
}
: y& c b* W$ Q$ c4 B9 _; k# M反正就是一通计算,其中比较重要的是 do_gettimeoffset( o" ^0 p# {/ I! o
,这个函数的目的是计算上一次更新 xtime
) s4 j) m# p+ v到当前执行这一段时间的偏移量,然后用这个偏移量去加到 xtime
% U4 ~, G4 |5 d上去,就是当前的时间(因为xtime的精度不够,clock_gettime
9 R6 G/ N9 Y! W$ X/ g的精度要求更高,所以必须计算一个偏移量)。
4 c+ E8 F5 f) h" S* k/ J那么,这个 do_gettimeoffset又是从哪来的?同样的文件里面有:( Y0 C1 u( _0 m) {4 i
static inline unsigned int do_gettimeoffset_tsc(void)* i: J+ d" z( g$ D9 w8 M8 Q
{/ w) U( A- o; z8 Y
unsigned long t;
* P8 R8 [( b. sunsigned long x;# C! A0 g; B7 d! I4 a& G4 I
t = get_cycles_sync();
! \5 [* a% Q( jif (t < vxtime.last_tsc)
4 y' [7 }1 w* C+ X* U5 V" mt = vxtime.last_tsc; /* hack */" L6 \4 u- M. N! B2 ~+ A' e
x = ((t - vxtime.last_tsc) * vxtime.tsc_quot) >> US_SCALE;6 `6 W; V! F' q# Z3 \ r
return x;6 ]; ^9 H$ \9 r: P
}
4 ~0 k% U, n1 [/ P- T
* g! z) c* D" R( Zstatic inline unsigned int do_gettimeoffset_hpet(void)
9 d8 L& @$ u: y5 F, p{5 p% t3 S B( `6 P* w( ]' q
/* cap counter read to one tick to avoid inconsistencies */
" C: ^/ d; l, K junsigned long counter = hpet_readl(HPET_COUNTER) - vxtime.last;
9 x4 y) @4 M( E0 `5 y9 zreturn (min(counter,hpet_tick) * vxtime.quot) >> US_SCALE;
3 }+ h# r* ]) p}
, ]+ \. d+ }" V1 v4 |6 I0 O 1 O' |1 L: o9 }
unsigned int (*do_gettimeoffset)(void) = do_gettimeoffset_tsc;
0 h& A& Q) A* i7 r7 o) o6 ~也就是说,go_gettimeoffset 是从 tsc寄存器从拿到的,这又是神马玩意呢? 4 g( O6 U2 [+ j3 A7 V
参考一下下面的几篇文章【1】【2】【3】,基本就明白了:2 `' v, h, h8 u
tsc是一个cpu内的寄存器,用来累加从系统启动到目前,所有的时钟周期数,全称是 " V& I/ g, \# R! |& P5 M
Timestamp Counter;
O6 C) `$ `7 n5 u% D. b而且,最重要的是我们发现了一个重要的线索,那就是:' X" M+ S! o1 P0 k7 z7 b
在多核cpu的架构中,tsc寄存器是很有可能不一致的,特别是在intel的比较老的多核CPU
& f3 ?' V7 x0 s! x% u1 @中!坑爹....
+ n# z0 I; `" G* t' n a很多文章提到过这个问题,比如【1】【2】【4】,显然如果不同cpu L% N2 V, _1 ^# v9 Y
读到的值不同的话,多次执行,显然时间是不一致的。4 W3 `, E1 m: n+ z$ c
看起来就是这个问题了,那么就来验证一把:. `; Q: O" x; M8 |- ^2 W
#include<stdlib.h>
, Y* a$ w- b! k" F, N3 z#include<stdio.h>) c' x3 h: n! s: w
#include<sys/types.h>/ o2 u: v+ W. K: Q8 b
#include<sys/sysinfo.h>
' @, h7 g9 d6 P) L8 Y#include<unistd.h>
( V! |/ t5 Q/ T/ Z. Q$ ]: g#include<time.h>
) U- z, a+ |9 @* ?$ m( V
! a% E4 G2 v0 {; c* A#define __USE_GNU
+ @; E: S( |) K/ T' n5 ]$ d9 N# H! a#include<sched.h>1 r+ a @0 I- t9 y! a5 O
#include<ctype.h>1 L2 R, T' T }( A1 v
#include<string.h>
6 X8 k* ^" C+ b/ T" j
5 o) E5 c" _, M2 k" ]) L( }) V. _' D . P% v5 }2 J( }4 n2 l# B/ N
Z9 H8 h9 n2 T/ M+ Y# J+ Y
int main(int argc, char **argv){
, ]4 Z) p+ E5 Q# o4 J ( E6 V+ v# z! B/ \4 p6 Y( H
cpu_set_t cs;& m6 G# ]5 A, O) w% D; S1 u
CPU_ZERO(&cs); l7 B- m! U" [' [5 N
CPU_SET(0, &cs);
0 o& N- ?: X" b # v3 K* i% a7 v0 ~8 h# p3 {
sched_setaffinity(0, sizeof(cpu_set_t), &cs);
! n; U3 k8 P- U* @
/ G% X- y, F9 E struct timespec ts;9 n3 F, ^6 e& S# m1 {+ k) ~
int ret = 0;
3 |' {. y* ]* r B$ }* ^ ret = clock_gettime(CLOCK_REALTIME, &ts);
4 H _# l- {& C$ e printf("ret: %d, time: %lld %lld", ret, ts.tv_sec, ts.tv_nsec);
* c: M# X1 g2 z5 k0 m
5 S8 Q3 e4 ?. v# c/ w: ]+ @+ Z}6 p; L" x( A& g, g" x
5 j k; G' c1 ?# Egcc -g -o cpu0_clock_gettime cpu_getdate.c -lrt
, r7 o' v1 ]/ C; _$ |8 c# C ( Z2 E# I7 v$ o- m( c4 ?0 H X
ret: 0, time: 1332992815 537481000
# }, O `, X& O! i* C& k. Fret: 0, time: 1332992816 506704000* C. _' k% E& _% R+ h0 z7 G
ret: 0, time: 1332992817 9910000
0 V, ~& S9 a6 t+ Y% n' c$ Oret: 0, time: 1332992817 4804310002 r1 ~, R3 F3 Q# q% Y8 Z: X, H
ret: 0, time: 1332992817 936965000, Z8 z3 o% V" _+ P D* {4 d
ret: 0, time: 1332992818 416634000- b* ?4 Y$ n/ ]# b
ret: 0, time: 1332992818 8503190001 ^$ v/ w/ a" ^2 \9 D
ret: 0, time: 1332992819 305075000
7 Y/ e5 ?' J; f. Uret: 0, time: 1332992819 7291080002 `3 a. g3 o- Q8 e" j9 ?, z, T
ret: 0, time: 1332992820 164726000
/ w( [% K9 Y- ?0 \8 |/ S7 zret: 0, time: 1332992820 577573000
- E+ z0 ]6 _" M. Bret: 0, time: 1332992820 9850250001 z+ p8 u* d+ X0 @1 T& G
ret: 0, time: 1332992821 394070000% ~* u1 T$ V2 M: q2 d0 s
ret: 0, time: 1332992821 816708000
& U$ l% h& U" @: k. Nret: 0, time: 1332992822 216811000) f0 g8 c/ J& p9 T* K" ?
ret: 0, time: 1332992822 616901000
9 H6 k. O# u7 ?1 L3 T, pret: 0, time: 1332992823 18564000w5 X, a$ p: }7 H0 i4 K
ret: 0, time: 1332992823 727585000
" k0 H) R9 i/ U% k) N. {% v通过把程序的调度固定在某一个具体的cpu中,那么clock_gettime的结果也是正确的!7 W, L0 M4 z$ W+ F, ~$ w
经过几次测试,很容易找到两个cpu时间不一致的情况,具体这个例子来说就是 cpu0和
) e$ }2 Y% P8 T# O! \. L6 ecpu5,把上面的程序修改一下,可以看到结果基本上每次都会跳一把。
4 R* }2 o# _8 o& C; g$ `0 _7 \3 @问题解决:
2 W* C! V, B/ P6 \% O) e. i那么,tsc在多核cpu中的值不同,应该怎么解决呢? 如 【4】所示,在高版本的linux
: }2 M* o, j* n* L内核中,有人提交了补丁,可以在内核启动时检查并且同步一把,那么升级一下内核到 2
6 d1 s( y: M. b- ~7 ?9 j.6.38。
0 E6 M2 \- ? f1 J) C( o6 T5 L8 f重新启动到新内核之后,问题解决!. ?! J) `3 Q8 }+ C8 ?. v
总结一下:
) k+ E p' ]& w* @0 Z: D0 {% E: n1. apache的时间统计方式,没具体看实现,但是显然前后的两个时间可能是在不同的cpu, r' s% D7 ^, z* L1 E. d9 w) r
核上计算出来的。
1 s2 a b8 w6 A% f- H1 B2. tsc的值,并不能保证是同步的,特别是在某些Intel cpu
. g% ?. y+ E) w* e3 Q的情况下;但是新的内核会做一些检查和resync的工作;9 S! s/ E6 @# f$ k
3. time()调用直接读取内核的xtime值,不会触及硬件;但是 gettimeofday(), ( Z q; x; u! q3 N8 r }
clock_gettime() 在 x86_64下都会读取tsc值;
; m4 G Q0 E: J+ ~4. hwclock读的硬件时钟显然不是 tsc,应该是实时时钟RTC;
( {( j1 h/ S8 _! b【1】 http://en.wikipedia.org/wiki/Time_Stamp_Counter2 j) U# T# D1 h% c
【2】 http://juliusdavies.ca/posix_clocks/clock_realtime_linux_faq.html7 @9 O1 d2 R# c& P' o. p
【3】http://www.linuxforum.net/forum/gshowflat.php?Cat=&Board=driver&Number=
& g e( D6 g, S. I8 }8 h385219&page=0&view=collapsed&sb=5&o=all&fpart , linux时钟中断机制,Copyright
& H- C: Z, D( L ~2003 by 詹荣开9 x, Y1 C& ]$ ^& G7 Q
【4】http://lwn.net/Articles/211051/ x86: unify/rewrite SMP TSC sync code |
|