背景
之前我接手一个CMS项目,目的在于开发一个可支持多域名绑定多站点设置而公用一套php代码的项目。为了提高用户的体验技术经理要求首页,列表页详情页等页面必须使用ajax异步加载。因此,开启了踩坑之旅... 。很快第一个坑就出现了:页面加载特别的慢,是不是出现504 Time Out 。
原因
纳闷,既无高并发亦无大数据,为什么页面加载那么慢?而且还有两台机器做负载均衡,还做了CDN加载。每次观察都是ajax响应的速度非常慢。有经验的同学应该想到了,是session储存的搞的鬼,session默认是文件存储的,文件的读取存在文件锁这个东西,如果一个请求需要处理耗时的操作,碰巧又读写到session那么这个锁就会锁住session文件,进而影响了整个回话啦,其他的请求只能排队等候了(因为绝多数请求都要读写session)
session文件锁解决方法
1.在读取session操作之后加上session_write_close();
php官网对session_write_close()是这么说的
会话数据通常在脚本终止后存储而不需要调用session_write_close(),但是由于会话数据被锁定以防止并发写入,因此任何时候只有一个脚本可以在会话上运行。将框架集与会话一起使用时,由于此锁定,您将体验到逐个加载的框架。只要对会话变量进行了所有更改,就可以通过结束会话来减少加载所有帧所需的时间
2.session_start(['read_and_close '=>true]) 需要php>=7.0
http://php.net/manual/zh/function.session-start.php
除了常规的会话配置指示项, 还可以在此数组中包含 read_and_close 选项。如果将此选项的值设置为 TRUE, 那么会话文件会在读取完毕之后马上关闭, 因此,可以在会话数据没有变动的时候,避免不必要的文件锁
3.把session存储在redis,memcache中
以下是一个session存储在redis的一个小案例
<?php
class RedisSessionHandler{
public $timeout = 3600;
public $handle = "";
public $redis;
public function __construct($timeout=3600){
if($timeout)
{
$this->timeout = $timeout;
}
if ($this->getIsActive()) {
return true;
}
session_set_save_handler(
array($this, 'open'),
array($this, 'close'),
array($this, 'read'),
array($this, 'write'),
array($this, 'destroy'),
array($this, 'gc')
);
// 下面这行代码可以防止使用对象作为会话保存管理器时可能引发的非预期行为
register_shutdown_function('session_write_close');
session_start();
}
function getRedis()
{
$this->redis = new Redis();
$this->redis->connect('127.0.0.1', 6379);
return $this->redis;
}
function open($savePath, $sessionName)
{
return true;
}
public function getIsActive()
{
return session_status() === PHP_SESSION_ACTIVE;
}
function close()
{
return true;
}
function read($id)
{
$this->getRedis();
$value = $this->redis->get($id);
return $value?$value:"";
}
function write($id, $data)
{
$this->getRedis();
return $this->redis->set($id,$data,$this->timeout);
}
function destroy($id)
{
return (bool)$this->redis->del($id);
}
function gc($maxlifetime)
{
return true;
}
function set($key,$val){
$_SESSION[$key] = $val;
return isset($_SESSION[$key]) ? true: false;
}
function get($key){
return isset($_SESSION[$key]) ? $_SESSION[$key] : null;
}
}
$handler = new RedisSessionHandler();
$handler->set('name','feng');
print_r($_SESSION);
/**
Array
(
[name] => feng
)
*/