1. 程式人生 > >session_write_close() PHP在訪問Session資料時存在互斥情況,導致Ajax請求響應緩慢

session_write_close() PHP在訪問Session資料時存在互斥情況,導致Ajax請求響應緩慢

概要:PHP在訪問Session資料時存在互斥情況,導致Ajax請求響應緩慢。

一,問題分析:

最近在統計網站請求響應時間時,發現有很多請求時間超過0.5秒,login/info最為明顯。
經過分析,login/info本身操作並不複雜,讀取Session中的資料,查詢一次資料庫,然後返回。
進一步跟蹤發現,PHP在訪問Session資料時,首先需要獲取到競爭的鎖,否則就會Sleep一段時間,然後重試。

1,沒有競爭情況下的過程跟蹤:

sendto(4, "incr 5q8of2888he4b7986oqu947qv4.lock 1\r\n", 40, MSG_NOSIGNAL, NULL, 0) = 40
sendto(4, "add 5q8of2888he4b7986oqu947qv4.lock 768 15 1\r\n1\r\n", 49, MSG_NOSIGNAL, NULL, 0) = 49
sendto(4, "get 5q8of2888he4b7986oqu947qv4\r\n", 32, MSG_NOSIGNAL, NULL, 0) = 32
select(5, [4], [], NULL, {1, 0})  = 1 (in [4], left {1, 0})
# 有兩種情況可以成功獲取鎖:1,incr失敗(NOT_FOUND)並且add成功,2,鎖的原始值是0,incr返回1。

# 下面是第二種情況。
recvfrom(4, " 1 \r\nNOT_STORED\r\n ", 32768, 0, NULL, NULL) = 15
recvfrom(4, "VALUE 5q8of2888he4b7986oqu947qv4 0 0\r\n\r\nEND\r\n", 32768, 0, NULL, NULL) = 45

2,存在競爭情況下的過程跟蹤:

17692 sendto(4, "incr 3qvs0ci9nplv43baail60cdbm5.lock 1\r\n", 40, MSG_NOSIGNAL, NULL, 0) = 40
17692 sendto(4, "add 3qvs0ci9nplv43baail60cdbm5.lock 768 15 1\r\n1\r\n", 49, MSG_NOSIGNAL, NULL, 0) = 49
17692 sendto(4, "get 3qvs0ci9nplv43baail60cdbm5\r\n", 32, MSG_NOSIGNAL, NULL, 0) = 32
17692 select(5, [4], [], NULL, {1, 0})  = 1 (in [4], left {1, 0})
# 鎖已經存在,incr返回2,表明鎖已經被其他程序佔有。

# Sleep 5ms後重試。
17692 recvfrom(4, "2\r\nNOT_STORED\r\n", 32768, 0, NULL, NULL) = 15
17692 recvfrom(4, "VALUE 3qvs0ci9nplv43baail60cdbm5 0 0\r\n\r\nEND\r\n", 32768, 0, NULL, NULL) = 45
17692 nanosleep({0, 5000000}, NULL)     = 0
17692 select(5, [4], [4], NULL, {1, 0}) = 1 (out [4], left {1, 0})
17692 sendto(4, "incr 3qvs0ci9nplv43baail60cdbm5.lock 1\r\n", 40, MSG_NOSIGNAL, NULL, 0) = 40
17692 sendto(4, "add 3qvs0ci9nplv43baail60cdbm5.lock 768 15 1\r\n1\r\n", 49, MSG_NOSIGNAL, NULL, 0) = 49
17692 sendto(4, "get 3qvs0ci9nplv43baail60cdbm5\r\n", 32, MSG_NOSIGNAL, NULL, 0) = 32
17692 select(5, [4], [], NULL, {1, 0})  = 1 (in [4], left {0, 996000})
# 依舊沒有成功,Sleep 10ms後重試

17692 recvfrom(4, "3\r\nNOT_STORED\r\nVALUE 3qvs0ci9nplv43baail60cdbm5 0 0\r\n\r\nEND\r\n", 32768, 0, NULL, NULL) = 60
17692 nanosleep({0, 10000000}, NULL)    = 0
17692 select(5, [4], [4], NULL, {1, 0}) = 1 (out [4], left {1, 0})
17692 sendto(4, "incr 3qvs0ci9nplv43baail60cdbm5.lock 1\r\n", 40, MSG_NOSIGNAL, NULL, 0) = 40
17692 sendto(4, "add 3qvs0ci9nplv43baail60cdbm5.lock 768 15 1\r\n1\r\n", 49, MSG_NOSIGNAL, NULL, 0) = 49
17692 sendto(4, "get 3qvs0ci9nplv43baail60cdbm5\r\n", 32, MSG_NOSIGNAL, NULL, 0) = 32
17692 select(5, [4], [], NULL, {1, 0})  = 1 (in [4], left {0, 998000})
# 依舊沒有成功,Sleep 20ms後重試
17692 recvfrom(4, "4\r\n", 32768, 0, NULL, NULL) = 3
17692 recvfrom(4, "NOT_STORED\r\nVALUE 3qvs0ci9nplv43baail60cdbm5 0 0\r\n\r\nEND\r\n", 32768, 0, NULL, NULL) = 57
17692 nanosleep({0, 20000000}, NULL)    = 0
17692 select(5, [4], [4], NULL, {1, 0}) = 1 (out [4], left {1, 0})
17692 sendto(4, "incr 3qvs0ci9nplv43baail60cdbm5.lock 1\r\n", 40, MSG_NOSIGNAL, NULL, 0) = 40
17692 sendto(4, "add 3qvs0ci9nplv43baail60cdbm5.lock 768 15 1\r\n1\r\n", 49, MSG_NOSIGNAL, NULL, 0) = 49
17692 sendto(4, "get 3qvs0ci9nplv43baail60cdbm5\r\n", 32, MSG_NOSIGNAL, NULL, 0) = 32
17692 select(5, [4], [], NULL, {1, 0})  = 1 (in [4], left {1, 0})
# 獲取成功,incr返回1,表明其他程序已經釋放鎖。
17692 recvfrom(4, "1\r\nNOT_STORED\r\nVALUE 3qvs0ci9nplv43baail60cdbm5 0 0\r\n\r\nEND\r\n", 32768, 0, NULL, NULL) = 60

經過統計,目前有30%左右的請求都存在Session鎖競爭的情況,造成請求響應緩慢。
部分請求由於鎖競爭導致延遲的時間超過200 ms。


二,解決方案

鎖機制整合在PHP的Memcache Session模組中,並且是互斥鎖而不是讀寫鎖
只讀操作也需要獲取互斥鎖後才能完成。

造成鎖競爭的根本原因是程式持有鎖的時間太長。根本的解決方案是及時釋放鎖

目前我們的程式基本流程如下:

(1)載入Session模組,執行session_start(),此時程式就開始持有鎖。
(2)訪問Session中的資料,例如member資訊。
(3)執行正常業務邏輯,訪問資料庫,渲染頁面等。
(4)程式結束,此時程式自動釋放Session的鎖。

其中第(3)個步驟耗時最長,大部分情況下,在這個步驟,已經不需要訪問Session資料,已經可以釋放鎖。

具體改進:

1,修改Session類的建構函式,session_start後複製$_SESSION資料到物件內部變數,然後立即呼叫session_write_close。
2,只讀操作的方法,例如userdata,從物件內部變數獲取資料。
3,寫入操作的方法,例如set_userdata,先呼叫session_start,然後更新$_SESSION和內部變數,然後立即呼叫session_write_close。