TA的每日心情 | 怒 2019-11-20 15:22 |
|---|
签到天数: 2 天 [LV.1]初来乍到
|
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 |
|