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