高性能站点对session的处理方式
在站点的开发中,因为HTTP协议"无连接"的特点,session是我们常用的一个机制,使用方法我相信大家都比较了解。这里,我们主要讲一下在高访问下,一般的session处理会有什么问题,怎么构建一个高性能站点的session机制。
首先,默认情况下session的数据保存在哪里呢?
当然是在服务器端,但不是保存在内存中,而是保存在文件中。
默认情况下,php.ini 中设置的 session 保存方式是 files(session.save_handler = files),即使用读写文件的方式保存 session数据,而 session 文件保存的目录由 session.save_path 指定,文件名以 sess_ 为前缀,后跟 session id,如:sess_c72665af28a8b14c0fe11afe3b59b51b。文件中的数据即是序列化之后的 session数据了。
对于一个访问量不大的站点,这样的处理是足够的。但对一个高访问量,高性能的站点,这样就会产生问题:大量的访问,可能产生的很多的session文件在session.save_path指定的目录下,这样会大大降低系统的IO性能。
我们如何设置session的保存方式才能避免这样的悲剧了?很幸运的是,php本身提供了可以设置分级目录进行 session文件的保存,效率会提高很多,设置方法为:session.save_path="N;/save_path",N 为分级的级数,save_path 为开始目录。
当写入 session数据的时候,php会获取到客户端的 session_id,然后根据这个 session_id 到指定的 session文件保存目录中找到相应的 session文件,不存在则创建之,最后将数据序列化之后写入文件。读取 session数据是也是类似的操作流程,对读出来的数据需要进行解序列化,生成相应的 session变量,这样就解决了在单一目录下session文件过从带来的系统IO上的问题。
但对一个高性能站点,仅仅做这样的处理还不够,为什么了?
1. 一个大的网站,也许有多台服务器,我们怎么做到不同服务器可以对session进行共享
2. 将session文件放在文件系统,对于读取比较慢,我们是否可以把session放在内存中,加快对session的读取速度
对于此,我们可以对session采用不同的保存方法来实现session共享,加快系统对其的读取速度
同时php有session_set_save_handler函数来帮助我们实现,我们可以把session放在database, apc, eaccelerator, memcache, xcache里。
在这里我贴出joomla 1.5在session 保存上的代码, 这个文件定义了一个基类,不同的保存方式可以定义成继承它的子类:
<?php
/**
* @version $Id:sessionstorage.php 6961 2007-03-15 16:06:53Z tcp $
* @package Joomla.Framework
* @subpackage Session
* @copyright Copyright (C) 2005 - 2010 Open Source Matters. All rights reserved.
* @license GNU/GPL, see LICENSE.php
* Joomla! is free software. This version may have been modified pursuant
* to the GNU General Public License, and as distributed it includes or
* is derivative of works licensed under the GNU General Public License or
* other free or open source software licenses.
* See COPYRIGHT.php for copyright notices and details.
*/
// Check to ensure this file is within the rest of the framework
defined('JPATH_BASE') or die();
/**
* Custom session storage handler for PHP
*
* @abstract
* @package Joomla.Framework
* @subpackage Session
* @since 1.5
* @see http://www.php.net/manual/en/function.session-set-save-handler.php
*/
class JSessionStorage extends JObject
{
/**
* Constructor
*
* @access protected
* @param array $options optional parameters
*/
function __construct( $options = array() )
{
$this->register($options);
}
/**
* Returns a reference to a session storage handler object, only creating it
* if it doesn't already exist.
*
* @access public
* @param name $name The session store to instantiate
* @return database A JSessionStorage object
* @since 1.5
*/
function &getInstance($name = 'none', $options = array())
{
static $instances;
if (!isset ($instances)) {
$instances = array ();
}
$name = strtolower(JFilterInput::clean($name, 'word'));
if (empty ($instances[$name]))
{
$class = 'JSessionStorage'.ucfirst($name);
if(!class_exists($class))
{
$path = dirname(__FILE__).DS.'storage'.DS.$name.'.php';
if (file_exists($path)) {
require_once($path);
} else {
// No call to JError::raiseError here, as it tries to close the non-existing session
jexit('Unable to load session storage class: '.$name);
}
}
$instances[$name] = new $class($options);
}
return $instances[$name];
}
/**
* Register the functions of this class with PHP's session handler
*
* @access public
* @param array $options optional parameters
*/
function register( $options = array() )
{
// use this object as the session handler
session_set_save_handler(
array($this, 'open'),
array($this, 'close'),
array($this, 'read'),
array($this, 'write'),
array($this, 'destroy'),
array($this, 'gc')
);
}
/**
* Open the SessionHandler backend.
*
* @abstract
* @access public
* @param string $save_path The path to the session object.
* @param string $session_name The name of the session.
* @return boolean True on success, false otherwise.
*/
function open($save_path, $session_name)
{
return true;
}
/**
* Close the SessionHandler backend.
*
* @abstract
* @access public
* @return boolean True on success, false otherwise.
*/
function close()
{
return true;
}
/**
* Read the data for a particular session identifier from the
* SessionHandler backend.
*
* @abstract
* @access public
* @param string $id The session identifier.
* @return string The session data.
*/
function read($id)
{
return;
}
/**
* Write session data to the SessionHandler backend.
*
* @abstract
* @access public
* @param string $id The session identifier.
* @param string $session_data The session data.
* @return boolean True on success, false otherwise.
*/
function write($id, $session_data)
{
return true;
}
/**
* Destroy the data for a particular session identifier in the
* SessionHandler backend.
*
* @abstract
* @access public
* @param string $id The session identifier.
* @return boolean True on success, false otherwise.
*/
function destroy($id)
{
return true;
}
/**
* Garbage collect stale sessions from the SessionHandler backend.
*
* @abstract
* @access public
* @param integer $maxlifetime The maximum age of a session.
* @return boolean True on success, false otherwise.
*/
function gc($maxlifetime)
{
return true;
}
/**
* Test to see if the SessionHandler is available.
*
* @abstract
* @static
* @access public
* @return boolean True on success, false otherwise.
*/
function test()
{
return true;
}
}
这是保存在database里的子类文件:
<?php
/**
* @version $Id:database.php 6961 2007-03-15 16:06:53Z tcp $
* @package Joomla.Framework
* @subpackage Session
* @copyright Copyright (C) 2005 - 2010 Open Source Matters. All rights reserved.
* @license GNU/GPL, see LICENSE.php
* Joomla! is free software. This version may have been modified pursuant
* to the GNU General Public License, and as distributed it includes or
* is derivative of works licensed under the GNU General Public License or
* other free or open source software licenses.
* See COPYRIGHT.php for copyright notices and details.
*/
// Check to ensure this file is within the rest of the framework
defined('JPATH_BASE') or die();
/**
* Database session storage handler for PHP
*
* @package Joomla.Framework
* @subpackage Session
* @since 1.5
* @see http://www.php.net/manual/en/function.session-set-save-handler.php
*/
class JSessionStorageDatabase extends JSessionStorage
{
var $_data = null;
/**
* Open the SessionHandler backend.
*
* @access public
* @param string $save_path The path to the session object.
* @param string $session_name The name of the session.
* @return boolean True on success, false otherwise.
*/
function open($save_path, $session_name)
{
return true;
}
/**
* Close the SessionHandler backend.
*
* @access public
* @return boolean True on success, false otherwise.
*/
function close()
{
return true;
}
/**
* Read the data for a particular session identifier from the
* SessionHandler backend.
*
* @access public
* @param string $id The session identifier.
* @return string The session data.
*/
function read($id)
{
$db =& JFactory::getDBO();
if(!$db->connected()) {
return false;
}
$session = & JTable::getInstance('session');
$session->load($id);
return (string)$session->data;
}
/**
* Write session data to the SessionHandler backend.
*
* @access public
* @param string $id The session identifier.
* @param string $session_data The session data.
* @return boolean True on success, false otherwise.
*/
function write($id, $session_data)
{
$db =& JFactory::getDBO();
if(!$db->connected()) {
return false;
}
$session = & JTable::getInstance('session');
if ($session->load($id)) {
$session->data = $session_data;
$session->store();
} else {
// if load failed then we assume that it is because
// the session doesn't exist in the database
// therefore we use insert instead of store
$app = &JFactory::getApplication();
$session->data = $session_data;
$session->insert($id, $app->getClientId());
}
return true;
}
/**
* Destroy the data for a particular session identifier in the
* SessionHandler backend.
*
* @access public
* @param string $id The session identifier.
* @return boolean True on success, false otherwise.
*/
function destroy($id)
{
$db =& JFactory::getDBO();
if(!$db->connected()) {
return false;
}
$session = & JTable::getInstance('session');
$session->delete($id);
return true;
}
/**
* Garbage collect stale sessions from the SessionHandler backend.
*
* @access public
* @param integer $maxlifetime The maximum age of a session.
* @return boolean True on success, false otherwise.
*/
function gc($maxlifetime)
{
$db =& JFactory::getDBO();
if(!$db->connected()) {
return false;
}
$session = & JTable::getInstance('session');
$session->purge($maxlifetime);
return true;
}
}
这是保存在apc里的子类文件:
<?php
/**
* @version $Id:apc.php 6961 2007-03-15 16:06:53Z tcp $
* @package Joomla.Framework
* @subpackage Session
* @copyright Copyright (C) 2005 - 2010 Open Source Matters. All rights reserved.
* @license GNU/GPL, see LICENSE.php
* Joomla! is free software. This version may have been modified pursuant
* to the GNU General Public License, and as distributed it includes or
* is derivative of works licensed under the GNU General Public License or
* other free or open source software licenses.
* See COPYRIGHT.php for copyright notices and details.
*/
// Check to ensure this file is within the rest of the framework
defined('JPATH_BASE') or die();
/**
* APC session storage handler for PHP
*
* @package Joomla.Framework
* @subpackage Session
* @since 1.5
* @see http://www.php.net/manual/en/function.session-set-save-handler.php
*/
class JSessionStorageApc extends JSessionStorage
{
/**
* Constructor
*
* @access protected
* @param array $options optional parameters
*/
function __construct( $options = array() )
{
if (!$this->test()) {
return JError::raiseError(404, "The apc extension is not available");
}
parent::__construct($options);
}
/**
* Open the SessionHandler backend.
*
* @access public
* @param string $save_path The path to the session object.
* @param string $session_name The name of the session.
* @return boolean True on success, false otherwise.
*/
function open($save_path, $session_name)
{
return true;
}
/**
* Close the SessionHandler backend.
*
* @access public
* @return boolean True on success, false otherwise.
*/
function close()
{
return true;
}
/**
* Read the data for a particular session identifier from the
* SessionHandler backend.
*
* @access public
* @param string $id The session identifier.
* @return string The session data.
*/
function read($id)
{
$sess_id = 'sess_'.$id;
return (string) apc_fetch($sess_id);
}
/**
* Write session data to the SessionHandler backend.
*
* @access public
* @param string $id The session identifier.
* @param string $session_data The session data.
* @return boolean True on success, false otherwise.
*/
function write($id, $session_data)
{
$sess_id = 'sess_'.$id;
return apc_store($sess_id, $session_data, ini_get("session.gc_maxlifetime"));
}
/**
* Destroy the data for a particular session identifier in the
* SessionHandler backend.
*
* @access public
* @param string $id The session identifier.
* @return boolean True on success, false otherwise.
*/
function destroy($id)
{
$sess_id = 'sess_'.$id;
return apc_delete($sess_id);
}
/**
* Garbage collect stale sessions from the SessionHandler backend.
*
* @access public
* @param integer $maxlifetime The maximum age of a session.
* @return boolean True on success, false otherwise.
*/
function gc($maxlifetime)
{
return true;
}
/**
* Test to see if the SessionHandler is available.
*
* @static
* @access public
* @return boolean True on success, false otherwise.
*/
function test() {
return extension_loaded('apc');
}
}
这是保存在eaccelerator有子类文件:
<?php
/**
* @version $Id:eaccelerator.php 6961 2007-03-15 16:06:53Z tcp $
* @package Joomla.Framework
* @subpackage Session
* @copyright Copyright (C) 2005 - 2010 Open Source Matters. All rights reserved.
* @license GNU/GPL, see LICENSE.php
* Joomla! is free software. This version may have been modified pursuant
* to the GNU General Public License, and as distributed it includes or
* is derivative of works licensed under the GNU General Public License or
* other free or open source software licenses.
* See COPYRIGHT.php for copyright notices and details.
*/
// Check to ensure this file is within the rest of the framework
defined('JPATH_BASE') or die();
/**
* eAccelerator session storage handler for PHP
*
* @package Joomla.Framework
* @subpackage Session
* @since 1.5
* @see http://www.php.net/manual/en/function.session-set-save-handler.php
*/
class JSessionStorageEaccelerator extends JSessionStorage
{
/**
* Constructor
*
* @access protected
* @param array $options optional parameters
*/
function __construct( $options = array() )
{
if (!$this->test()) {
return JError::raiseError(404, "The eaccelerator extension is not available");
}
parent::__construct($options);
}
/**
* Open the SessionHandler backend.
*
* @access public
* @param string $save_path The path to the session object.
* @param string $session_name The name of the session.
* @return boolean True on success, false otherwise.
*/
function open($save_path, $session_name)
{
return true;
}
/**
* Close the SessionHandler backend.
*
* @access public
* @return boolean True on success, false otherwise.
*/
function close()
{
return true;
}
/**
* Read the data for a particular session identifier from the
* SessionHandler backend.
*
* @access public
* @param string $id The session identifier.
* @return string The session data.
*/
function read($id)
{
$sess_id = 'sess_'.$id;
return (string) eaccelerator_get($sess_id);
}
/**
* Write session data to the SessionHandler backend.
*
* @access public
* @param string $id The session identifier.
* @param string $session_data The session data.
* @return boolean True on success, false otherwise.
*/
function write($id, $session_data)
{
$sess_id = 'sess_'.$id;
return eaccelerator_put($sess_id, $session_data, ini_get("session.gc_maxlifetime"));
}
/**
* Destroy the data for a particular session identifier in the
* SessionHandler backend.
*
* @access public
* @param string $id The session identifier.
* @return boolean True on success, false otherwise.
*/
function destroy($id)
{
$sess_id = 'sess_'.$id;
return eaccelerator_rm($sess_id);
}
/**
* Garbage collect stale sessions from the SessionHandler backend.
*
* @access public
* @param integer $maxlifetime The maximum age of a session.
* @return boolean True on success, false otherwise.
*/
function gc($maxlifetime)
{
eaccelerator_gc();
return true;
}
/**
* Test to see if the SessionHandler is available.
*
* @static
* @access public
* @return boolean True on success, false otherwise.
*/
function test() {
return (extension_loaded('eaccelerator') && function_exists('eaccelerator_get'));
}
}
这是保存在memcache的子类文件:
<?php
/**
* @version $Id:eaccelerator.php 6961 2007-03-15 16:06:53Z tcp $
* @package Joomla.Framework
* @subpackage Session
* @copyright Copyright (C) 2005 - 2010 Open Source Matters. All rights reserved.
* @license GNU/GPL, see LICENSE.php
* Joomla! is free software. This version may have been modified pursuant
* to the GNU General Public License, and as distributed it includes or
* is derivative of works licensed under the GNU General Public License or
* other free or open source software licenses.
* See COPYRIGHT.php for copyright notices and details.
*/
// Check to ensure this file is within the rest of the framework
defined('JPATH_BASE') or die();
/**
* Memcache session storage handler for PHP
*
* -- Inspired in both design and implementation by the Horde memcache handler --
*
* @package Joomla.Framework
* @subpackage Session
* @since 1.5
* @see http://www.php.net/manual/en/function.session-set-save-handler.php
*/
class JSessionStorageMemcache extends JSessionStorage
{
/**
* Resource for the current memcached connection.
*
* @var resource
*/
var $_db;
/**
* Use compression?
*
* @var int
*/
var $_compress = null;
/**
* Use persistent connections
*
* @var boolean
*/
var $_persistent = false;
/**
* Constructor
*
* @access protected
* @param array $options optional parameters
*/
function __construct( $options = array() )
{
if (!$this->test()) {
return JError::raiseError(404, "The memcache extension isn't available");
}
parent::__construct($options);
$config =& JFactory::getConfig();
$params = $config->getValue('config.memcache_settings');
if (!is_array($params))
{
$params = unserialize(stripslashes($params));
}
if (!$params)
{
$params = array();
}
$this->_compress = (isset($params['compression'])) ? $params['compression'] : 0;
$this->_persistent = (isset($params['persistent'])) ? $params['persistent'] : false;
// This will be an array of loveliness
$this->_servers = (isset($params['servers'])) ? $params['servers'] : array();
}
/**
* Open the SessionHandler backend.
*
* @access public
* @param string $save_path The path to the session object.
* @param string $session_name The name of the session.
* @return boolean True on success, false otherwise.
*/
function open($save_path, $session_name)
{
$this->_db = new Memcache;
for ($i=0, $n=count($this->_servers); $i < $n; $i++)
{
$server = $this->_servers[$i];
$this->_db->addServer($server['host'], $server['port'], $this->_persistent);
}
return true;
}
/**
* Close the SessionHandler backend.
*
* @access public
* @return boolean True on success, false otherwise.
*/
function close()
{
return $this->_db->close();
}
/**
* Read the data for a particular session identifier from the
* SessionHandler backend.
*
* @access public
* @param string $id The session identifier.
* @return string The session data.
*/
function read($id)
{
$sess_id = 'sess_'.$id;
$this->_setExpire($sess_id);
return $this->_db->get($sess_id);
}
/**
* Write session data to the SessionHandler backend.
*
* @access public
* @param string $id The session identifier.
* @param string $session_data The session data.
* @return boolean True on success, false otherwise.
*/
function write($id, $session_data)
{
$sess_id = 'sess_'.$id;
if ($this->_db->get($sess_id.'_expire')) {
$this->_db->replace($sess_id.'_expire', time(), 0);
} else {
$this->_db->set($sess_id.'_expire', time(), 0);
}
if ($this->_db->get($sess_id)) {
$this->_db->replace($sess_id, $session_data, $this->_compress);
} else {
$this->_db->set($sess_id, $session_data, $this->_compress);
}
return;
}
/**
* Destroy the data for a particular session identifier in the
* SessionHandler backend.
*
* @access public
* @param string $id The session identifier.
* @return boolean True on success, false otherwise.
*/
function destroy($id)
{
$sess_id = 'sess_'.$id;
$this->_db->delete($sess_id.'_expire');
return $this->_db->delete($sess_id);
}
/**
* Garbage collect stale sessions from the SessionHandler backend.
*
* -- Not Applicable in memcache --
*
* @access public
* @param integer $maxlifetime The maximum age of a session.
* @return boolean True on success, false otherwise.
*/
function gc($maxlifetime)
{
return true;
}
/**
* Test to see if the SessionHandler is available.
*
* @static
* @access public
* @return boolean True on success, false otherwise.
*/
function test()
{
return (extension_loaded('memcache') && class_exists('Memcache'));
}
/**
* Set expire time on each call since memcache sets it on cache creation.
*
* @access private
*
* @param string $key Cache key to expire.
* @param integer $lifetime Lifetime of the data in seconds.
*/
function _setExpire($key)
{
$lifetime = ini_get("session.gc_maxlifetime");
$expire = $this->_db->get($key.'_expire');
// set prune period
if ($expire + $lifetime < time()) {
$this->_db->delete($key);
$this->_db->delete($key.'_expire');
} else {
$this->_db->replace($key.'_expire', time());
}
}
}
这是保存在xcache里的子类文件:
<?php
/**
* @version $Id:apc.php 6961 2007-03-15 16:06:53Z tcp $
* @package Joomla.Framework
* @subpackage Session
* @copyright Copyright (C) 2005 - 2010 Open Source Matters. All rights reserved.
* @license GNU/GPL, see LICENSE.php
* Joomla! is free software. This version may have been modified pursuant
* to the GNU General Public License, and as distributed it includes or
* is derivative of works licensed under the GNU General Public License or
* other free or open source software licenses.
* See COPYRIGHT.php for copyright notices and details.
*/
// Check to ensure this file is within the rest of the framework
defined('JPATH_BASE') or die();
/**
* XCache session storage handler
*
* @package Joomla.Framework
* @subpackage Cache
* @since 1.5
*/
class JSessionStorageXcache extends JSessionStorage
{
/**
* Constructor
*
* @access protected
* @param array $options optional parameters
*/
function __construct( $options = array() )
{
if (!$this->test()) {
return JError::raiseError(404, "The xcache extension isn't available");
}
parent::__construct($options);
}
/**
* Open the SessionHandler backend.
*
* @access public
* @param string $save_path The path to the session object.
* @param string $session_name The name of the session.
* @return boolean True on success, false otherwise.
*/
function open($save_path, $session_name)
{
return true;
}
/**
* Close the SessionHandler backend.
*
* @access public
* @return boolean True on success, false otherwise.
*/
function close()
{
return true;
}
/**
* Read the data for a particular session identifier from the
* SessionHandler backend.
*
* @access public
* @param string $id The session identifier.
* @return string The session data.
*/
function read($id)
{
$sess_id = 'sess_'.$id;
//check if id exists
if( !xcache_isset( $sess_id ) ){
return;
}
return (string)xcache_get($sess_id);
}
/**
* Write session data to the SessionHandler backend.
*
* @access public
* @param string $id The session identifier.
* @param string $session_data The session data.
* @return boolean True on success, false otherwise.
*/
function write($id, $session_data)
{
$sess_id = 'sess_'.$id;
return xcache_set($sess_id, $session_data, ini_get("session.gc_maxlifetime") );
}
/**
* Destroy the data for a particular session identifier in the
* SessionHandler backend.
*
* @access public
* @param string $id The session identifier.
* @return boolean True on success, false otherwise.
*/
function destroy($id)
{
$sess_id = 'sess_'.$id;
if( !xcache_isset( $sess_id ) ){
return true;
}
return xcache_unset($sess_id);
}
/**
* Garbage collect stale sessions from the SessionHandler backend.
*
* @access public
* @param integer $maxlifetime The maximum age of a session.
* @return boolean True on success, false otherwise.
*/
function gc($maxlifetime)
{
return true;
}
/**
* Test to see if the SessionHandler is available.
*
* @static
* @access public
* @return boolean True on success, false otherwise.
*/
function test() {
return (extension_loaded('xcache'));
}
}
OK,希望大家借鉴和学习。