이 글에서는 주로 PHP 세션의 잠금 및 동시성에 대해 소개합니다. 관련 현상으로는 요청 차단, 세션 데이터 손실 및 세션 데이터를 읽을 수 없는 경우가 있으니 참고하시기 바랍니다.
로그인이 안 돼요
어느 날 버그를 해결하기 위해 백엔드 시스템 중 하나에 로그인하려고 했는데 계정 비밀번호 확인 코드를 정확하게 입력했는데 로그인할 수 없었습니다. 많은 실험 끝에 두 가지 주요 오류 메시지가 있음을 발견했습니다.
csrf 확인 실패
확인 코드 오류 [내가 본 확인 코드를 입력했음을 코드 신에게 맹세합니다. 문자 추가]
저희 시스템저희 시스템은 보시다시피 phalcon 2.0.8을 기반으로 개발되었으며 이를 방지하기 위해 필드를 추가했습니다. 양식 필드에서 CSRF 공격. 보안 문자도 활성화되어 있습니다.
<input type="hidden" name="{{ security.getTokenKey() }}" value="{{ security.getToken() }}"/> <img src="/login/getCaptcha" id="img-captcha"/>
먼저 이 두 구성 요소를 확인한 결과 둘 다 세션에 데이터를 저장한다는 사실을 발견했습니다.
# phalcon/security.zep # Security::getToken() let session = <SessionInterface> dependencyInjector->getShared("session"); session->set(this->_tokenValueSessionID, token); $this->session->set('admin_get_captcha_action', $captcha);
그런 다음 세션 구현을 확인한 결과 데이터가 Redis에 저장되어 있음을 발견했습니다.
찾아보고 검색로그인이 안되는 문제는 무엇인가요? 데이터 검증에 문제가 있어서 먼저 데이터부터 살펴보겠습니다. 우리 테스트 환경에서 redis 머신에 로그인하고 redis-cli monitor를 실행한 후 로그인 과정을 진행해보니 다음과 같이 출력되는 것을 확인했습니다(의미). ):
GET sessionId
GET sessionId
SETEX sessionId 3600 csrf=xxxx
SETEX sessionId 3600 captcha=abcd
다음을 볼 수 있습니다.
1. 여기에는 두 가지 요청이 있습니다. 하나는 양식을 로드하는 것이고, 다른 하나는 인증 코드를 생성하는 것입니다.
2. 이 두 요청은 양식이 로드되고 렌더링된 후 확인 코드를 요청해야 합니다. 즉, 세션 순서는 get->set->get->방법입니다. 동시 요청이 이루어진 것 같나요?
3. 후자의 SETEX에는 csrf 내용이 없기 때문에 이전 데이터를 덮어쓰게 되어 전체적으로 좋지 않은데 문제가 무엇인지 조금은 이해가 됩니다. 문제는 무엇입니까? PHP의 세션 데이터 액세스부터 시작하여 이야기가 길어졌습니다.
PHP 세션 데이터 액세스세션 데이터는 문자열로 인코딩되어 메모리[파일, db, redis, memcache 등]에 저장됩니다. 세션을 사용할 때 언제 메모리로 이동합니까? 데이터를 가져오는 중인가요? 데이터는 언제 메모리에 기록되나요?
이 질문에 대한 대답은 일부 친구들의 생각과 다를 수 있습니다. 요청에서 PHP는 session_start 동안 메모리를 한 번만 읽은 다음 요청이 끝날 때 한 번만 메모리에 씁니다. session_write_close 호출 시간이 되면 데이터를 메모리에 다시 플러시하고 세션을 닫습니다. 그러면 질문이 옵니다: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의 세션 잠금, 동시성 및 적용 범위에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!