php session的鎖和并發(fā)
本文分享PHP的session在使用過程中的鎖和并發(fā)的問題,與之相關的現(xiàn)象有請求阻塞、session數(shù)據(jù)丟失、session數(shù)據(jù)讀不到。
我登錄不了了
某天,我準備登錄我們一個后臺系統(tǒng),前去解決一個bug,在賬戶密碼驗證碼都準確輸入的情況下,我登錄不上,經(jīng)過多次實驗發(fā)現(xiàn)主要有兩個錯誤信息:
- csrf驗證失敗
- 驗證碼錯誤【我對碼神起誓我用半角輸入了我看到的驗證碼,且順序一致,無多加字符】
我們的系統(tǒng)
我們的系統(tǒng)是基于phalcon 2.0.8 開發(fā)的,如你所見,我們在表單域加入了防止csrf攻擊的域。也啟用了驗證碼。
<input type="hidden" name="{{ security.getTokenKey() }}" value="{{ security.getToken() }}"/> <img src="/login/getCaptcha" id="img-captcha"/>
我首先對這兩個組件進行查閱,發(fā)現(xiàn)他們都是將數(shù)據(jù)存于session:
# phalcon/security.zep # Security::getToken() let session = <SessionInterface> dependencyInjector->getShared("session"); session->set(this->_tokenValueSessionID, token); $this->session->set('admin_get_captcha_action', $captcha);
然后我又查閱了我們session的實現(xiàn),發(fā)現(xiàn)是將數(shù)據(jù)存儲于redis的。
找啊找
什么問題導致我登錄不上呢?既然是數(shù)據(jù)驗證上出現(xiàn)問題,就從數(shù)據(jù)著手吧,我登陸我們測試環(huán)境的redis機器,執(zhí)行 redis-cli monitor,然后走一遍登錄流程,發(fā)現(xiàn)輸出如下(意思意思):
- GET sessionId
- GET sessionId
- SETEX sessionId 3600 csrf=xxxx
- SETEX sessionId 3600 captcha=abcd
我們可以看到:
1、這里存在兩次請求,一次是表單加載,一次是生成驗證碼的。
2、存在“并發(fā)”的情況,這兩個請求應該是表單加載渲染后才請求驗證碼的,也就是session順序應該是get->set->get->set,看起來怎么是并發(fā)請求了。
3、后面那個SETEX沒有csrf的內(nèi)容,也就是覆蓋掉前面的數(shù)據(jù)了
整個世界都不好了,不過也稍微明白是什么問題了。什么問題呢,說來話長,要從PHP的session數(shù)據(jù)的存取說起。
php的session數(shù)據(jù)的存取
session的數(shù)據(jù)是經(jīng)過編碼成字符串存儲在存儲器【file、db、redis、memcache等】的,在我們使用session的時候,是什么時候去儲存器取數(shù)據(jù)的?又是什么時候?qū)?shù)據(jù)寫入存儲器的?
這個問題的答案可能和一些朋友想的不一樣,一個請求里面,PHP只會讀取一次存儲器,在session_start的時候,然后也只會寫入一次存儲器,在請求結(jié)束的時候,或調(diào)用session_write_close的時候,將數(shù)據(jù)刷回存儲器,關閉session。
那么問題來了:
1、如果一個會話,同時出現(xiàn)兩個讀寫session請求,沒有保證獲取1-寫入1-獲取2-寫入2,同時沒有cas版本管理機制的情況下,這些并發(fā)請求就會彼此讀取不到對方的寫入,最后寫入的會把前面請求寫入的session覆蓋掉。
2、如果請求是串行的,像登錄頁面的表單和驗證碼,也有可能前面的請求已經(jīng)輸出內(nèi)容了,但是session還沒寫入,后面的請求就已經(jīng)發(fā)起了。
鎖與不鎖
解決這種資源的并發(fā)一般會通過鎖或版本管理來處理。但是版本管理我看不到好的方法。就聊聊鎖吧。
其實鎖是不大適合,有弊端的。
php的session,默認是用文件存儲的,在打開session的時候,會對文件加獨占鎖,這樣,其它請求就無法獲取鎖了,只能等待直到前面的鎖解了。
這樣保證了 讀取-寫入,讀取-寫入的順序。
其它存儲器,例如mysql,可以借助select for update進行行鎖。redis可以通過一個自增鍵,返回1的獲取到鎖等來實現(xiàn)。
這個實現(xiàn)的話,對數(shù)據(jù)流來說很理想,但是,對于目前這種頁面大量應用ajax的情況,所有請求排隊處理,將大大加大頁面展現(xiàn)的耗時,甚至出現(xiàn)請求超時等不可用故障。
沒有解決的解決
不建議過多使用session,其一次讀取一次寫入的機制所引發(fā)的問題,會造成坑的存在。
在模版渲染前,或請求輸出前調(diào)用session_write_close
# 立刻回寫session,避免session覆蓋 $eventManager = $this->view->getEventsManager(); if (!$eventManager) { $eventManager = new Manager(); $this->view->setEventsManager($eventManager); } $eventManager->attach("view:afterRender",function(){ session_write_close(); }); return $this->view;
if($login) { # 立刻回寫session,避免session讀取不到 $eventManager = $this->dispatcher->getEventsManager(); if (!$eventManager) { $eventManager = new Manager(); $this->dispatcher->setEventsManager($eventManager); } $eventManager->attach('dispatch:afterDispatchLoop',function(){ session_write_close(); }); return $this->response->setHeader('Location', '/'); }
以上就是關于php session的鎖和并發(fā),希望對大家的學習有所幫助。
相關文章
PHP 開發(fā)環(huán)境配置(Zend Server安裝)
運行安裝文件(ZendServer-CE-php-5.3.2-5.0.1-Windows_x86.exe)開始安裝,選項請參照我的選擇。2010-04-04PHP結(jié)合jquery ajax實現(xiàn)上傳多張圖片,并限制圖片大小操作示例
這篇文章主要介紹了PHP結(jié)合jquery ajax實現(xiàn)上傳多張圖片,并限制圖片大小操作,涉及php結(jié)合jQuery ajax文件上傳及文件屬性相關操作技巧,需要的朋友可以參考下2019-03-03php中foreach結(jié)合curl實現(xiàn)多線程的方法分析
這篇文章主要介紹了php中foreach結(jié)合curl實現(xiàn)多線程的方法,結(jié)合實例形式分析了foreach語句結(jié)合curl循環(huán)調(diào)用模擬多線程的原理與實現(xiàn)技巧,需要的朋友可以參考下2016-09-09golang與php實現(xiàn)計算兩個經(jīng)緯度之間距離的方法
這篇文章主要介紹了golang與php實現(xiàn)計算兩個經(jīng)緯度之間距離的方法,結(jié)合實例形式對比分析了Go語言與php進行經(jīng)緯度計算的相關數(shù)學運算技巧,需要的朋友可以參考下2016-07-07PHP入門教程之字符串處理技巧總結(jié)(轉(zhuǎn)換,過濾,解析,查找,截取,替換等)
這篇文章主要介紹了PHP入門教程之字符串處理技巧,結(jié)合實例形式總結(jié)分析了php針對字符串的刪除空格、特殊字符轉(zhuǎn)換、過濾、解析、分割、查找、截取、替換、獲取、判斷、計算等操作技巧,需要的朋友可以參考下2016-09-09