• 技术文章 >php教程 >php手册

    php session的锁和并发,phpsession

    2016-06-13 08:47:08原创1722

    php session的锁和并发,phpsession


    本文分享PHP的session在使用过程中的锁和并发的问题,与之相关的现象有请求阻塞、session数据丢失、session数据读不到。

    我登录不了了
    某天,我准备登录我们一个后台系统,前去解决一个bug,在账户密码验证码都准确输入的情况下,我登录不上,经过多次实验发现主要有两个错误信息:

    我们的系统
    我们的系统是基于phalcon 2.0.8 开发的,如你所见,我们在表单域加入了防止csrf攻击的域。也启用了验证码。

    
     
    

    我首先对这两个组件进行查阅,发现他们都是将数据存于session:

    # phalcon/security.zep
    # Security::getToken()
    let session =  dependencyInjector->getShared("session"); 
    session->set(this->_tokenValueSessionID, token); 
    $this->session->set('admin_get_captcha_action', $captcha);
    

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

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

    我们可以看到:

    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', '//m.sbmmt.com/m/');
    }
    

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

    您可能感兴趣的文章:

    声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn核实处理。
    专题推荐:php session 并发 session
    上一篇:CodeIgniter辅助之第三方类库third_party用法分析,codeigniter类库 下一篇:自己动手写 PHP MVC 框架(40节精讲/巨细/新人进阶必看)

    相关文章推荐

    • php mysql 数据库类• PHP代码:Http断点续传的实现例子• 基于php实现七牛抓取远程图片• php5.3,5.4,5.5,5.6 中新特性• ThinkPHP控制器里javascript代码不能执行的解决方法,thinkphpjavascript
    1/1

    PHP中文网