php session的锁和并发


Posted in PHP onJanuary 22, 2016

本文分享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('admin_get_captcha_action', $captcha);

然后我又查阅了我们session的实现,发现是将数据存储于redis的。

找啊找
什么问题导致我登录不上呢?既然是数据验证上出现问题,就从数据着手吧,我登陆我们测试环境的redis机器,执行 redis-cli monitor,然后走一遍登录流程,发现输出如下(意思意思):

  • GET sessionId 
  • GET sessionId 
  • SETEX sessionId 3600 csrf=xxxx 
  • 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('dispatch:afterDispatchLoop',function(){
    session_write_close();
  });
  return $this->response->setHeader('Location', '/');
}

以上就是关于php session的锁和并发,希望对大家的学习有所帮助。

PHP 相关文章推荐
php替换超长文本中的特殊字符的函数代码
May 22 PHP
Could not load type System.ServiceModel.Activation.HttpModule解决办法
Dec 29 PHP
如何使用PHP计算上一个月的今天
May 23 PHP
深入php函数file_get_contents超时处理的方法详解
Jun 03 PHP
php检测iis环境是否支持htaccess的方法
Feb 18 PHP
教你如何在CI框架中使用 .htaccess 隐藏url中index.php
Jun 09 PHP
PHP随机生成信用卡卡号的方法
Mar 23 PHP
PHP实现批量修改文件后缀名的方法
Jul 30 PHP
laravel 解决paginate查询多个字段报错的问题
Oct 22 PHP
使用PHP+Redis实现延迟任务,实现自动取消订单功能
Nov 21 PHP
php获取是星期几的的一些常用姿势
Dec 15 PHP
php设计模式之抽象工厂模式分析【星际争霸游戏案例】
Jan 23 PHP
php5.4传引用时报错问题分析
Jan 22 #PHP
php实现word转html的方法
Jan 22 #PHP
高质量PHP代码的50个实用技巧必备(上)
Jan 22 #PHP
php中namespace use用法实例分析
Jan 22 #PHP
PHP爬虫之百万级别知乎用户数据爬取与分析
Jan 22 #PHP
两种php实现图片上传的方法
Jan 22 #PHP
php+html5实现无刷新图片上传教程
Jan 22 #PHP
You might like
一个图形显示IP的PHP程序代码
2007/10/19 PHP
深入解析PHP垃圾回收机制对内存泄露的处理
2013/06/14 PHP
PHP垃圾回收机制引用计数器概念分析
2013/06/24 PHP
mcrypt启用 加密以及解密过程详细解析
2013/08/07 PHP
PHP基于双向链表与排序操作实现的会员排名功能示例
2017/12/26 PHP
xml文档转换工具,附图表例子(hta)
2010/11/17 Javascript
JQUERY的属性选择符和自定义选择符使用方法(二)
2011/04/07 Javascript
广泛收集的jQuery拖放插件集合
2012/04/09 Javascript
Ajax异步提交表单数据的说明及方法实例
2013/06/22 Javascript
node.js入门教程迷你书、node.js入门web应用开发完全示例
2014/04/06 Javascript
node.js入门教程
2014/06/01 Javascript
JavaScript控制网页层收起和展开效果的方法
2015/04/15 Javascript
基于JavaScript怎么实现让歌词滚动播放
2015/11/03 Javascript
js中动态创建json,动态为json添加属性、属性值的实例
2016/12/02 Javascript
AngularJs篇:使用AngularJs打造一个简易权限系统的实现代码
2016/12/26 Javascript
原生js仿浏览器滚动条效果
2017/03/02 Javascript
Vue实例中生命周期created和mounted的区别详解
2017/08/25 Javascript
Vue服务器渲染Nuxt学习笔记
2018/01/31 Javascript
讲解vue-router之什么是嵌套路由
2018/05/28 Javascript
Vue中div contenteditable 的光标定位方法
2018/08/25 Javascript
vue使用better-scroll实现下拉刷新、上拉加载
2018/11/23 Javascript
浅谈React Native 传参的几种方式(小结)
2019/05/21 Javascript
python实现bucket排序算法实例分析
2015/05/04 Python
使用python 3实现发送邮件功能
2018/06/15 Python
Python爬虫框架scrapy实现downloader_middleware设置proxy代理功能示例
2018/08/04 Python
python实现横向拼接图片
2020/03/23 Python
哈工大自然语言处理工具箱之ltp在windows10下的安装使用教程
2020/05/07 Python
python将dict中的unicode打印成中文实例
2020/05/11 Python
Django用户登录与注册系统的实现示例
2020/06/03 Python
Python实例方法、类方法、静态方法区别详解
2020/09/05 Python
迪卡侬英国官网:Decathlon英国
2017/04/08 全球购物
日本索尼音乐商店:Sony Music Shop
2018/07/17 全球购物
世界领先的豪华床上用品供应商之一:Bedeck Home
2019/03/18 全球购物
银行简历自我评价
2014/02/11 职场文书
献爱心标语
2014/06/21 职场文书
2015年综治维稳工作总结
2015/04/07 职场文书