PHP之session鎖定、並行、覆蓋詳解

小云云
發布: 2023-03-21 21:54:02
原創
1941 人瀏覽過

本文主要和大家介紹了php session的鎖和並發,與之相關的現像有請求阻塞、session資料遺失、session資料讀不到的問題,有興趣的夥伴們可以參考一下,希望能幫忙到大家。

我登入不了了
某天,我準備登入我們一個後台系統,前去解決一個bug,在帳戶密碼驗證碼都準確輸入的情況下,我登入不上,經過多次實驗發現主要有兩個錯誤訊息:

  • csrf驗證失敗

  • #驗證碼錯誤【我對碼神起誓我用半角輸入了我看到的驗證碼,且順序一致,無多加字元】

我們的系統我們的系統是基於phalcon 2.0.8 開發的,如你所見,我們在表單域加入了防止csrf攻擊的域。也啟用了驗證碼。

<input type="hidden" 
  name="{{ security.getTokenKey() }}"
  value="{{ security.getToken() }}"/>
<img src="/login/getCaptcha" id="img-captcha"/>
登入後複製

我先對這兩個元件進行查閱,發現他們都是將資料存於session:

# phalcon/security.zep
# Security::getToken()
let session = <SessionInterface> dependencyInjector->getShared("session"); 
session->set(this->_tokenValueSessionID, token); 
$this->session->set(&#39;admin_get_captcha_action&#39;, $captcha);
登入後複製

然後我又查閱了我們session的實現,發現是將資料儲存於redis的。

找啊找什麼問題導致我登入不上呢?既然是資料驗證上出現問題,就從資料著手吧,我登陸我們測試環境的redis機器,執行redis-cli monitor,然後走一遍登入流程,發現輸出如下(意思):

  • GET sessionId  

  • GET sessionId  

  • ##SETEX sessionId 3600 csrf=xxxx  

  • SETEX sessionId 3600 captcha=abcd  SETEX sessionId 3600 captcha=abcd  

我們可以看到:

1、這裡有兩個請求,一次是表單加載,一次是產生驗證碼的。
2、存在「並發」的情況,這兩個請求應該是表單載入渲染後才請求驗證碼的,也就是session順序應該是get->set->get->set,看起來怎麼是並發請求了。

3、後面那個SETEX沒有csrf的內容,也就是覆蓋掉前面的資料了

整個世界都不好了,不過也稍微明白是什麼問題了。什麼問題呢,說來話長,要從PHP的session資料的存取說起。 php的session資料的存取

session的資料是經過編碼成字串儲存在記憶體【file、db、redis、memcache等】的,在我們使用session的時候,是什麼時候去儲存器取資料的?又是什麼時候將資料寫入記憶體的?

這個問題的答案可能和一些朋友想的不一樣,一個請求裡面,PHP只會讀取一次存儲器,在session_start的時候,然後也只會寫入一次存儲器,在請求結束的時候,或呼叫session_write_close的時候,將資料刷回記憶體,關閉session。

###那麼問題來了:###

1、如果一个会话,同时出现两个读写session请求,没有保证获取1-写入1-获取2-写入2,同时没有cas版本管理机制的情况下,这些并发请求就会彼此读取不到对方的写入,最后写入的会把前面请求写入的session覆盖掉。
2、如果请求是串行的,像登录页面的表单和验证码,也有可能前面的请求已经输出内容了,但是session还没写入,后面的请求就已经发起了。
锁与不锁
解决这种资源的并发一般会通过锁或版本管理来处理。但是版本管理我看不到好的方法。就聊聊锁吧。

其实锁是不大适合,有弊端的。

php的session,默认是用文件存储的,在打开session的时候,会对文件加独占锁,这样,其它请求就无法获取锁了,只能等待直到前面的锁解了。

这样保证了 读取-写入,读取-写入的顺序。

其它存储器,例如mysql,可以借助select for update进行行锁。redis可以通过一个自增键,返回1的获取到锁等来实现。

这个实现的话,对数据流来说很理想,但是,对于目前这种页面大量应用ajax的情况,所有请求排队处理,将大大加大页面展现的耗时,甚至出现请求超时等不可用故障。

没有解决的解决不建议过多使用session,其一次读取一次写入的机制所引发的问题,会造成坑的存在。
在模版渲染前,或请求输出前调用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(&#39;dispatch:afterDispatchLoop&#39;,function(){
    session_write_close();
  });
  return $this->response->setHeader(&#39;Location&#39;, &#39;/&#39;);
}
登入後複製

相关推荐:

php中session锁防止阻塞请求的实例分析

以上是PHP之session鎖定、並行、覆蓋詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!