TA的每日心情 | 怒 2019-11-20 15:22 |
|---|
签到天数: 2 天 [LV.1]初来乍到
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
9 E6 \6 c% F z. q4 W0 S
问题引入:9 ]$ z, @* j* K/ c5 r
问题是我们重装了一台电脑之后,发现apache# E. M o _* K# D, H$ J
的日志里突然增加了很多服务时间超长的请求,如下面两条:
2 B3 j9 m9 @/ x; [** - - 4913229 11227 [29/Mar/2012:09:59:58 +0800] "GET*****
5 t4 n+ S: g7 r, |2 y j** - - 4793564 9846 [29/Mar/2012:09:59:58 +0800] "GET *****
- h0 s1 e `- m+ R5 {请求服务时间都在4s以上!这绝对无法忍受啊!0 A- J3 }2 x, m* j! s1 U
# H A y4 z0 ?# V分析过程:
" P- s z3 Q9 z/ f0 J+ e 3 q% G2 b& s* `9 Y8 }
但是,当我们:
) y; g/ F% B$ \7 v * 手动访问这台服务器之后,发现基本上请求都是正常响应的;+ Y/ c# O4 o: p& p* v
* 从前端反向代理的日志来看,服务时间也是正常的;
. }6 Y Z* |$ Z# `' j/ T) ~那这样推断,真相只有一个,那就是apache的日志时间记录有问题!
% M5 H$ B1 \ D, z$ P 3 h4 m2 _" p0 x" O; \: A
什么问题呢?不清楚,但是apache
7 [1 s- B3 Z$ ?, a+ {的时间记录不外乎是在收到请求时记个时间,请求处理完成之后再记一个时间,既然是时?6 l" `9 s( |4 s6 ]
涞奈侍猓敲淳涂纯聪低呈奔浒桑幼罴虻サ?date 开始,多执行几次:
% y& B' W' P. O apache2 # date
& y* A* V& B4 s3 s7 H9 \1 H+ lWed Mar 28 17:25:08 CST 2012
+ t) J2 P1 p4 W' s' I$ [! Capache2 # date
2 [8 _$ [7 [* d& A" YWed Mar 28 17:25:04 CST 2012 时间减少了?
" b- ^- B& f7 Q. o. L" }% Z apache2 # date
8 v* u2 T8 _/ `: Z6 n! {& |Wed Mar 28 17:25:06 CST 2012
+ f$ k" i* ^( d" h6 G$ `/ X7 m) gapache2 # date
' F% E5 X* R% Y$ jWed Mar 28 17:25:12 CST 20121 q/ ^+ W5 D0 }2 Z9 p% [/ k- L D' l
apache2 # date! O @% e* w7 w
Wed Mar 28 17:25:13 CST 2012
9 k1 t! g3 G2 |5 C+ q5 ~ Qapache2 # date
8 t* g, L, I4 t& G. S( y$ o( m1 {Wed Mar 28 17:25:09 CST 2012 时间减少了?
6 r+ ]: Z8 m9 E6 D0 _apache2 # date1 `$ B* l2 d8 Z1 u6 t# e3 @/ s
Wed Mar 28 17:25:09 CST 20122 F9 @- N! @0 R( C2 Z' A
apache2 # date
2 ]5 ]- j7 I9 R- C( n; pWed Mar 28 17:25:15 CST 20125 I n" z F) z. x+ O, g- e
那么第一个怀疑就是系统里有进程在自己不断的调时间,比如ntpdate, ntpd
2 {: f! j1 ^/ G+ ~! a/ J,那么我们首先把 ntpd给停掉;然后系统也没有任何地方会调用ntpdate;
! z: ~4 B$ P4 }* M- @6 W6 {继续观察,发现停掉ntpd后,现象依旧!
& @( D4 J& I) c$ D还有什么可能呢? date- [: q. c b- C p) {
程序是从内核里面读取时间还是说需要读取硬件时间?到底是怎么计算的呢?; C$ ]( I6 Z; i& @2 e( K
请教下同事们,据说 sys_time 是读的kernel里的时间,而 sys_gettimeofday- Y8 @0 ]* a2 j2 \3 S0 u! X
是需要读硬件时钟的。
# B: k+ [% J( |- G那么就测试一把吧:* R& ?, y' a" H! b6 Z% V
先写个程序调用 time(), 然后打印结果,结果是这样的:
. V1 m1 F$ t/ s C7 c0 E" b1332986690
. G) w: d8 s5 p6 q1332986692% O! W5 Q. K: \/ f+ S+ }
1332986693
- J/ [8 \; ?4 }+ D+ Y' X1332986693
& d- W6 U/ A3 q6 \1332986694
5 _+ ^, i5 w$ K1332986694
1 B1 e) a! Y) j1332986695; K/ E, ~. n- q6 t; G8 A! j( p6 {
1332986695
1 `+ r" [- z8 J6 \: U( z6 d) p1332986696
( ]- Y, @* `5 `% z, p1332986696
" B0 C) z4 a7 @5 l) c# |& A# k1332986697
: W. S; h U |/ m1 Z( W* }1332986697; Y: [ a& a4 z9 j; e
1332986698
7 i, v% K$ l4 Y/ N! u1 W1332986698
8 |* x* T" e! m( g" l13329866993 R) L+ p/ A2 h2 V2 i
确实没有问题了!看来有可能是硬件问题了,再看看clock_gettime 的调用:
, {0 v: |; r" C# b. ?& s/ Z. {6 P{
* O" T" o! ^# i struct timespec ts;
: f4 c6 l4 K: K7 | int ret = 0;
( v, V7 o/ @; f5 B- z ret = clock_gettime(CLOCK_REALTIME, ts); 8 U$ W- f) ~& f
printf("ret: %d, time: %d %d", ts.tv_sec, ts.tv_ns
% o+ u9 O4 Y9 ^4 f! L& G1 Y4 e& N} / g* G2 k5 R- X, e' }3 G; t' F
编译: gcc -g -o clock_gettime getdate.c -lrt
% N" a- D/ }. b5 r3 ~ $ U; F! P3 R, l2 O+ Y8 d# T
~ # ./clock_gettime
1 v- u. h) P6 p% a5 bret: 1332991087, time: 594076000 -1 3 Z2 o4 ]2 N5 M
ret: 1332991088, time: 379009000 -1
* j' L% k+ x0 H7 U+ Z* u5 Cret: 1332991089, time: 68561000 -1
- l; S/ G% \4 t+ l' H+ c* l3 o/ Oret: 1332991089, time: 714013000 -1
# M) N- s* _$ u- v1 \% Yret: 1332991086, time: 511250000 -1 时间在跳
, `6 ?# d4 ~: j9 ~( E% O% rret: 1332991091, time: 945626000 -1
. n0 Q6 P% ~2 S0 Eret: 1332991092, time: 650479000 -1
8 {! s- l, N# c$ N. Tret: 1332991088, time: 734780000 -1 R5 V- B) v/ P4 W7 _
ret: 1332991094, time: 506114000 -1时间在跳
# _% a) a f4 t( E) i! R8 j- V看来果然有可能是硬件问题!
8 f1 |7 s- N- P5 H那么,检查一下,直接调用一下 hwclock --show ,看看是不是可以验证这个问题:+ V, N* L! a1 e
~ # hwclock --show ' {4 U0 ]0 Z9 a8 s S& _2 q% A
Thu 29 Mar 2012 10:22:21 AM CST -0.948923 seconds
- C9 D" S$ e* n- v) u& @0 { ~ # hwclock --show 2 v6 v% @9 S( O5 w( m/ x% F- h
Thu 29 Mar 2012 10:22:22 AM CST -0.188950 seconds 3 l) h6 U! s3 [
~ # hwclock --show
& q7 G1 ]5 w6 ~Thu 29 Mar 2012 10:22:23 AM CST -0.244766 seconds
8 @ K* |1 T, e( S7 o& a! M ~ # hwclock --show
' X8 f$ b: H1 M% f1 h+ T, zThu 29 Mar 2012 10:22:24 AM CST -0.336868 seconds / Y' p- |3 x3 |+ V5 t
~ # hwclock --show 4 H; P: A+ V' U8 I
Thu 29 Mar 2012 10:22:25 AM CST 4.237159 seconds
+ {: r# Q1 V' G4 n( i ~ # hwclock --show
L0 t' I# f. _! O. oThu 29 Mar 2012 10:22:26 AM CST 4.238672 seconds
# A7 a; b3 W4 O- t ~ # hwclock --show 4 {, ~) M2 A& Z' k3 k4 ?6 ?5 ]
Thu 29 Mar 2012 10:22:27 AM CST -0.379418 seconds
6 O+ x: I/ m% F时间居然都是正确的!显然问题没有那么简单。。。那到底是怎么回事??还是从date
[. a6 k% R) ^+ d开始查起吧:. X0 O, o* a, |) A/ R# F
date是一个glibc里面的程序,那它的时间源是从哪里来的?查看一下glibc
9 q- C; W4 c+ u& {3 L2 U6 B- Y的源码,它的时间从这里来:
; L3 f$ B( `( K3 f/ k# Z& _3 s; \, z// glibc coreutils gettime.c, l5 o- {4 J0 s# Y9 u
/* Get the system time into *TS. */
; u" g4 }" @# |/ {8 ^0 evoid! w" Y* }; W. V. M7 h# ?
gettime (struct timespec *ts)8 x! \! D' B x0 R. M+ h8 W0 {
{: l( K1 E2 Y" Y( j* q
#if HAVE_NANOTIME! S ]9 ^" m- H5 @9 g. Z
nanotime (ts);! i( I1 p4 n a0 r! e
#else
7 T* D2 A1 z/ j* L % g+ w m- }% F7 j/ s
# if defined CLOCK_REALTIME && HAVE_CLOCK_GETTIME
2 n# e* S* F" {/ L if (clock_gettime (CLOCK_REALTIME, ts) == 0)0 ?7 Q* Z0 M p) J, R2 M
return;
$ n$ R. i. R' @# endif T: w" V# ]9 t5 z3 ~
. _7 i9 f6 E5 x2 D6 A `# if HAVE_GETTIMEOFDAY3 N3 a# P3 W* W
{
1 _7 v. K* \6 u, u, |) m struct timeval tv;
, f+ |) ~3 b6 n: m7 X5 T! B5 p. B gettimeofday (&tv, NULL);- |7 N9 _' P6 i; B( v
ts->tv_sec = tv.tv_sec;# ~# \- M3 X+ b
ts->tv_nsec = tv.tv_usec * 1000;* Q2 M: v$ o" P8 I
}
/ Y' r6 i7 W h: C- ]- R9 t# else
# a" u- ^( ]& J8 U c. z1 Z, r. o ts->tv_sec = time (NULL);$ `+ d' L- y5 s
ts->tv_nsec = 0;0 X& Z0 O3 c( L- |. V! ~% K
# endif8 K' s& ?0 X9 X' f0 O
7 K0 e6 X# S( C; h/ T ^#endif
, f1 y6 N) S& [3 c5 n}* r3 g- _. ~% ?: i$ q7 j, M) a
按照 nanotime, clock_gettime, gettimeofday的顺序去读取,我们这个系统中就是 $ I9 L3 U( R7 X8 D) f# E X T
clock_gettime,从上面的小程序中也可以看到clock_gettime的时间是有问题的,那么# G# M( a/ _) [5 H, e% Z
clock_gettime的时间源是怎么来的呢?. W7 X8 Y- c( }7 f! c6 A2 Y
// linux kernel 2.6.18 arch/x86_64/kernel/time.c
3 g3 ~( x; Y+ P0 y: o9 W8 u' m- Rvoid do_gettimeofday(struct timeval *tv)$ j7 A8 ]- m2 ]2 |" V- D
{
! }+ x8 ^% T0 m' m5 ^unsigned long seq, t;2 }* j s- ^: {
unsigned int sec, usec;
I9 k T# l" X3 q% |3 {8 J/ Z6 {
# a9 _3 T2 F$ K3 `4 ^6 y& gdo {
. b: x8 y1 c/ I, cseq = read_seqbegin(&xtime_lock);
4 y# H. L6 Y" J) e! D1 d& W k/ F
+ z$ o/ C# E; w; H( Lsec = xtime.tv_sec;
0 H2 ]" V3 l8 s$ G5 P7 Lusec = xtime.tv_nsec / NSEC_PER_USEC;5 ]) l G; i8 H5 Y2 n6 i
t = (jiffies - wall_jiffies) * USEC_PER_TICK +
3 _; g9 m. Z8 r/ r1 ~8 ddo_gettimeoffset();
8 l* A8 F% K5 [7 f, H! L6 w4 r7 Jusec += t;
2 ~7 r& J+ }7 M- V- S5 f
' s: b9 t! i0 n3 j8 l h4 j} while (read_seqretry(&xtime_lock, seq));
' ^! r5 Z- ^, }+ Z5 T$ ~
" ^' w/ ~( F0 `& `9 o+ }tv->tv_sec = sec + usec / USEC_PER_SEC;
& B; S' c3 Q. [: U; a- R* n ~/ Ctv->tv_usec = usec % USEC_PER_SEC;7 @$ w. v; F5 D C" ?; x Z) _
}
6 n1 J! ]9 W& `. u/ h+ q% |反正就是一通计算,其中比较重要的是 do_gettimeoffset/ w: F' L4 S- g/ _9 m
,这个函数的目的是计算上一次更新 xtime g- u u* M x. D
到当前执行这一段时间的偏移量,然后用这个偏移量去加到 xtime
# x0 B; l# _2 F) @4 X上去,就是当前的时间(因为xtime的精度不够,clock_gettime" [7 E5 o8 _8 k5 T" j8 B$ _7 w
的精度要求更高,所以必须计算一个偏移量)。
, p6 n t# a' R那么,这个 do_gettimeoffset又是从哪来的?同样的文件里面有:
# A" O2 B) m8 l, ^static inline unsigned int do_gettimeoffset_tsc(void)0 Z7 j1 ?5 f' O
{( U1 n6 G, E% \4 Q: y P6 X9 y( J
unsigned long t;
$ h- L( l d9 ?0 Q9 R2 b) i' [unsigned long x;
, E% h. C9 ?9 u' yt = get_cycles_sync();' |& c) [1 a' o# A' I9 L6 P
if (t < vxtime.last_tsc)
! p; S9 @+ ]1 U2 ~- Yt = vxtime.last_tsc; /* hack */- J1 v9 Q y+ l+ n
x = ((t - vxtime.last_tsc) * vxtime.tsc_quot) >> US_SCALE;
3 o) x6 u/ e) h5 |% H" Yreturn x;! Z4 ?1 C3 T2 s" w& M5 I& ?4 W
}
. k7 f4 u5 L2 V* F, B3 x7 J / ]! d" ?) R, o2 S2 K
static inline unsigned int do_gettimeoffset_hpet(void)
6 Q2 r, }6 S! z5 L9 C3 B{( ?( A8 H5 o6 m! K" c
/* cap counter read to one tick to avoid inconsistencies */( @, W7 s1 C; A0 S0 `' i6 W3 m
unsigned long counter = hpet_readl(HPET_COUNTER) - vxtime.last;1 `4 o8 B0 d0 [6 M4 p
return (min(counter,hpet_tick) * vxtime.quot) >> US_SCALE;
2 F g9 C+ z( `! w}
4 F' j5 L8 j- z/ C0 P5 v# @ 2 g# q" Y; c' o3 i0 N/ k0 Y
unsigned int (*do_gettimeoffset)(void) = do_gettimeoffset_tsc;
1 \- K: t7 H! A: \3 g, {也就是说,go_gettimeoffset 是从 tsc寄存器从拿到的,这又是神马玩意呢? - J4 T4 C$ P c) S/ e; R4 q
参考一下下面的几篇文章【1】【2】【3】,基本就明白了:
" h" t& I' V! q2 k3 i( n% Ztsc是一个cpu内的寄存器,用来累加从系统启动到目前,所有的时钟周期数,全称是 * H3 _$ z* r k) d
Timestamp Counter;
" X0 b* J; c8 I( r2 G e而且,最重要的是我们发现了一个重要的线索,那就是:4 w& x; n& d8 d8 O6 \
在多核cpu的架构中,tsc寄存器是很有可能不一致的,特别是在intel的比较老的多核CPU
+ N$ z2 J- G: `) f$ y8 j+ x. j% I中!坑爹....0 z1 \0 m. C X7 D6 l; W$ B$ E
很多文章提到过这个问题,比如【1】【2】【4】,显然如果不同cpu
; `1 [5 ^; B E: b读到的值不同的话,多次执行,显然时间是不一致的。1 Z2 ?, o- B9 Q% k
看起来就是这个问题了,那么就来验证一把:% S4 m; n, I0 V) [
#include<stdlib.h>
; x: B4 G9 V5 C* `#include<stdio.h>
0 @4 p% O) X% z: s) t& H#include<sys/types.h>
- U4 {( h1 d1 e" t! e: \#include<sys/sysinfo.h>! \# T) V' D& }8 [, _
#include<unistd.h>9 e0 ]& w' V- E2 |( E: F
#include<time.h>
. g& ~5 p Z; L& o8 p, r' @: F
/ ~& k) s" L2 G/ u n: r4 S#define __USE_GNU
/ J$ F' F7 \. \, _& _#include<sched.h>
/ q# h* V% c/ t5 p9 X+ Z4 [7 K#include<ctype.h>
4 B5 `8 l! N( g9 X0 m" C4 I/ {#include<string.h>
0 k4 @: f# N( _ 8 k' O- X" Q4 o3 q
4 f2 v: [% C4 q
. N- Q2 X& {. V v; jint main(int argc, char **argv){# E! R r+ h. z2 {+ ?! o- C
) F1 Z% B' h. J" R4 \1 {' o
cpu_set_t cs;- Q+ U% a5 I0 H: r9 s
CPU_ZERO(&cs);+ _: q- U( ? R5 \
CPU_SET(0, &cs);
5 v: k1 z' P8 _) c : Z8 U7 O; m: A& _& a4 l1 w! L. `5 V2 y
sched_setaffinity(0, sizeof(cpu_set_t), &cs);
. r# z2 n% G1 I! c2 m ! `# N0 P" `8 n; R
struct timespec ts;( ~( M) i1 T( z/ N, q
int ret = 0;9 Z. ? l6 {# ?# ^: I% u
ret = clock_gettime(CLOCK_REALTIME, &ts);
' D ` }' L- c. ` printf("ret: %d, time: %lld %lld", ret, ts.tv_sec, ts.tv_nsec);
. I+ M' T4 S q/ W. w! w
% Y0 C8 [6 d: F& q) Q7 D3 a}
" r- Q; C& X: `
1 G) Y/ Z/ w# {0 z9 Hgcc -g -o cpu0_clock_gettime cpu_getdate.c -lrt
, r ?3 [" B$ n: W; x' p! d 7 W8 C6 r. z: K) Z9 V# i! z
ret: 0, time: 1332992815 537481000
8 ?. J0 r) z. ?& J% Q4 _, pret: 0, time: 1332992816 506704000; G6 d) i1 k" h" Q' ?& |3 `- U( B
ret: 0, time: 1332992817 99100009 X$ a% l: m( d) L6 \* F6 x% G
ret: 0, time: 1332992817 480431000
2 y4 _0 ^4 ^; ~5 l) rret: 0, time: 1332992817 936965000 f% [# P1 h6 i* n( D
ret: 0, time: 1332992818 416634000
5 O, {: s: x* _; D1 Q+ @ret: 0, time: 1332992818 850319000% m& N. h% p+ B/ m
ret: 0, time: 1332992819 305075000
, G3 d' p$ k# U3 C! y% }3 v/ Oret: 0, time: 1332992819 7291080005 V l7 e1 \ s; ~% F/ r
ret: 0, time: 1332992820 164726000
; l# I8 v. j1 a; X3 R. iret: 0, time: 1332992820 577573000% C, ^" v- J p' ?; {
ret: 0, time: 1332992820 985025000
, H! J, |* O1 s3 h) ~4 Wret: 0, time: 1332992821 394070000$ j: R0 _, J3 J9 R3 @* N
ret: 0, time: 1332992821 816708000+ i( e9 X9 q2 O# Y7 A; a
ret: 0, time: 1332992822 2168110000 w8 n( m ~4 s1 @
ret: 0, time: 1332992822 616901000
6 a0 Q `. R% Q: x/ vret: 0, time: 1332992823 18564000w4 x: b* Y5 r) I8 ^ t
ret: 0, time: 1332992823 727585000( `% h8 U5 I$ d: v+ \ F
通过把程序的调度固定在某一个具体的cpu中,那么clock_gettime的结果也是正确的!
' | x% r9 G; {" Z9 @9 i经过几次测试,很容易找到两个cpu时间不一致的情况,具体这个例子来说就是 cpu0和/ D( p; u6 e$ ?: R1 y
cpu5,把上面的程序修改一下,可以看到结果基本上每次都会跳一把。 O: p @& l R) d4 g$ g
问题解决:$ ~- K) K" f) }$ p
那么,tsc在多核cpu中的值不同,应该怎么解决呢? 如 【4】所示,在高版本的linux
0 U/ p( s7 `/ Z9 I8 ~) s2 R/ V内核中,有人提交了补丁,可以在内核启动时检查并且同步一把,那么升级一下内核到 20 o8 e8 I) ^! X2 N+ n
.6.38。
& L" E; E* d6 A, |) e3 {1 O重新启动到新内核之后,问题解决!# g2 T+ R7 h i g/ |! L: O. g i
总结一下:
N* k* G" Y+ Y m& I1. apache的时间统计方式,没具体看实现,但是显然前后的两个时间可能是在不同的cpu3 p2 n" A& o4 S( ^
核上计算出来的。
: }' C9 G3 b! B1 C: D2. tsc的值,并不能保证是同步的,特别是在某些Intel cpu1 C, ^3 h+ Y6 Y$ e* |
的情况下;但是新的内核会做一些检查和resync的工作;
6 W+ k8 J( w& j3. time()调用直接读取内核的xtime值,不会触及硬件;但是 gettimeofday(),
, m' a+ L6 Q* [0 q! n+ ^3 i; `9 kclock_gettime() 在 x86_64下都会读取tsc值;$ E+ p8 U! D8 n# I
4. hwclock读的硬件时钟显然不是 tsc,应该是实时时钟RTC;
" C# J4 d8 f# Y【1】 http://en.wikipedia.org/wiki/Time_Stamp_Counter
, m/ \5 g& d9 j【2】 http://juliusdavies.ca/posix_clocks/clock_realtime_linux_faq.html
" x7 @" m: y, N+ c【3】http://www.linuxforum.net/forum/gshowflat.php?Cat=&Board=driver&Number=1 i: p5 O. [2 n% {# {
385219&page=0&view=collapsed&sb=5&o=all&fpart , linux时钟中断机制,Copyright
/ s5 a: m) `* ?5 q2003 by 詹荣开
u6 Z; j) k7 @1 f【4】http://lwn.net/Articles/211051/ x86: unify/rewrite SMP TSC sync code |
|