如何在 APC 缓存中存储 PHP 会话?

Posted

技术标签:

【中文标题】如何在 APC 缓存中存储 PHP 会话?【英文标题】:How to store PHP sessions in APC Cache? 【发布时间】:2010-12-16 00:36:42 【问题描述】:

在磁盘中存储会话对我来说非常缓慢和痛苦。我的流量非常大。我想将会话存储在高级 php 缓存中,我该怎么做?

【问题讨论】:

...看起来您无缘无故投反对票的“策略”没有奏效... ...当然我的意思是没有为你工作 首选使用 memcached 进行自定义会话处理而不是 APC 是因为 APC 不能跨多台机器扩展。 @cballou,那个和 memcache 的 pecl 包已经附带了一个自定义会话处理程序。 更好的问题是“我应该在哪里存储我的会话”。 APC 不是一个好地方。如果您获得如此高的流量,那么您将不得不拥有大量内存,使用非常短的会话生命周期,或者可以接受大量会话被驱逐并且您的 APC 操作码缓存突然变得有缺陷。您的会话存储不是您的性能瓶颈。保持简单并将会话保留在磁盘上,除非您有集群环境(然后使用 MongoDb、Redis 或 memcached)。 【参考方案1】:
<?php

// to enable paste this line right before session_start():
//   new Session_APC;
class Session_APC

    protected $_prefix;
    protected $_ttl;
    protected $_lockTimeout = 10; // if empty, no session locking, otherwise seconds to lock timeout

    public function __construct($params=array())
    
        $def = session_get_cookie_params();
        $this->_ttl = $def['lifetime'];
        if (isset($params['ttl'])) 
            $this->_ttl = $params['ttl'];
        
        if (isset($params['lock_timeout'])) 
            $this->_lockTimeout = $params['lock_timeout'];
        

        session_set_save_handler(
            array($this, 'open'), array($this, 'close'),
            array($this, 'read'), array($this, 'write'),
            array($this, 'destroy'), array($this, 'gc')
        );
    

    public function open($savePath, $sessionName)
    
        $this->_prefix = 'BSession/'.$sessionName;
        if (!apc_exists($this->_prefix.'/TS')) 
            // creating non-empty array @see http://us.php.net/manual/en/function.apc-store.php#107359
            apc_store($this->_prefix.'/TS', array(''));
            apc_store($this->_prefix.'/LOCK', array(''));
        
        return true;
    

    public function close()
    
        return true;
    

    public function read($id)
    
        $key = $this->_prefix.'/'.$id;
        if (!apc_exists($key)) 
            return ''; // no session
        

        // redundant check for ttl before read
        if ($this->_ttl) 
            $ts = apc_fetch($this->_prefix.'/TS');
            if (empty($ts[$id])) 
                return ''; // no session
             elseif (!empty($ts[$id]) && $ts[$id] + $this->_ttl < time()) 
                unset($ts[$id]);
                apc_delete($key);
                apc_store($this->_prefix.'/TS', $ts);
                return ''; // session expired
            
        

        if (!$this->_lockTimeout) 
            $locks = apc_fetch($this->_prefix.'/LOCK');
            if (!empty($locks[$id])) 
                while (!empty($locks[$id]) && $locks[$id] + $this->_lockTimeout >= time()) 
                    usleep(10000); // sleep 10ms
                    $locks = apc_fetch($this->_prefix.'/LOCK');
                
            
            /*
            // by default will overwrite session after lock expired to allow smooth site function
            // alternative handling is to abort current process
            if (!empty($locks[$id])) 
                return false; // abort read of waiting for lock timed out
            
            */
            $locks[$id] = time(); // set session lock
            apc_store($this->_prefix.'/LOCK', $locks);
        

        return apc_fetch($key); // if no data returns empty string per doc
    

    public function write($id, $data)
    
        $ts = apc_fetch($this->_prefix.'/TS');
        $ts[$id] = time();
        apc_store($this->_prefix.'/TS', $ts);

        $locks = apc_fetch($this->_prefix.'/LOCK');
        unset($locks[$id]);
        apc_store($this->_prefix.'/LOCK', $locks);

        return apc_store($this->_prefix.'/'.$id, $data, $this->_ttl);
    

    public function destroy($id)
    
        $ts = apc_fetch($this->_prefix.'/TS');
        unset($ts[$id]);
        apc_store($this->_prefix.'/TS', $ts);

        $locks = apc_fetch($this->_prefix.'/LOCK');
        unset($locks[$id]);
        apc_store($this->_prefix.'/LOCK', $locks);

        return apc_delete($this->_prefix.'/'.$id);
    

    public function gc($lifetime)
    
        if ($this->_ttl) 
            $lifetime = min($lifetime, $this->_ttl);
        
        $ts = apc_fetch($this->_prefix.'/TS');
        foreach ($ts as $id=>$time) 
            if ($time + $lifetime < time()) 
                apc_delete($this->_prefix.'/'.$id);
                unset($ts[$id]);
            
        
        return apc_store($this->_prefix.'/TS', $ts);
    

【讨论】:

您的答案缺少正确的会话锁定。如果与大量使用 Ajax 的站点一起使用,它将造成严重破坏。您可以更新您的答案以获得赏金。 :) 这个课程有可能在 Github 上的某个地方更新吗?我正在尝试在一个项目中使用它来减少大型会话的开始/停止时间,到目前为止它似乎正在奏效。很高兴看到以这种方式处理会话是否有任何改进或是否有任何破坏交易的警告。 @SuperJer 我在这里创建了回购github.com/unirgy/session-apc - 我不知道任何警告。不确定是否为每个会话单独存储 TS 和 LOCK 是否会针对大量会话进行优化【参考方案2】:

理论上,您应该能够编写一个custom session handler,它使用 APC 为您透明地执行此操作。然而,我实际上并没有在五分钟的快速搜索中找到任何真正有希望的东西。大多数人似乎都将 APC 用于字节码缓存并将他们的会话放在 memcached 中。

【讨论】:

虽然使用 memcache 更容易,因为已经有一个可以使用的会话处理程序,但这个答案对于获得赏金是无效的,因为它避免使用 APC。使用就像 ini_set('session.save_handler', 'memcache') 一样简单(或者 memcached、sqlite 或文件,除非其他 PHP 扩展确实提供更多)。【参考方案3】:

我试图通过提供 100 分作为赏金来吸引更好的答案,但没有一个答案真正令人满意。

我会像这样汇总推荐的解决方案:

使用 APC 作为会话存储

APC 不能真正用作会话存储,因为 APC 没有可用的机制允许正确锁定,但是这种锁定对于确保在写回之前没有人更改最初读取的会话数据是必不可少的。

底线:避免它,它不会起作用。

替代方案

可能有许多会话处理程序可用。在 Session 部分检查 phpinfo() 的输出以获取“已注册的保存处理程序”。

RAM 磁盘上的文件存储

开箱即用,但出于显而易见的原因需要将文件系统挂载为 RAM 磁盘。

共享内存(mm)

在启用mm 的情况下编译 PHP 时可用。这是内置在 Windows 上的。

内存缓存(d)

PHP 带有一个专门的会话保存处理程序。需要安装 memcache 服务器和 PHP 客户端。根据安装的两个内存缓存扩展中的哪一个,保存处理程序称为memcachememcached

【讨论】:

我无法接受答案,因为我没有问过。你测试过你的解决方案吗?似乎它会立即锁定所有活动会话,因为传递给 open() 函数的参数尚未获得会话 ID。所以每个会话 id 没有锁 - 坏事。 我不是在谈论正式接受答案,而是关于您在第一句话中写的内容。会话在read() 中锁定并在write() 中解锁。 open() 只是初始化数组。是的,它已经过测试并正在使用中。【参考方案4】:

简单地将您的 /tmp 磁盘(或存储 PHP 会话文件的位置)放到 RAM 磁盘上,例如 tmpfsramfs 也会有显着的性能提升,并且会是一个更加透明的开关,零代码更改。

性能提升可能会明显减少,但仍会比磁盘会话快得多。

【讨论】:

【参考方案5】:

将其存储在 cookie(加密)或 MongoDB 中。 APC 并非真正用于此目的。

【讨论】:

【参考方案6】:

您可以将会话数据存储在 PHP 内部共享内存中。

session.save_handler = mm

但它需要可用:http://php.net/manual/en/session.installation.php

【讨论】:

【参考方案7】:

另一个好的解决方案是将 PHP 会话存储在 memcached 中

session.save_handler = memcache

【讨论】:

【参考方案8】:

在会话开始、打开和写入之后立即关闭显式会话应该解决 Unirgy 答案中的锁定问题(其中会话访问总是循环的(开始/打开-写入-关闭)。我还想象第二类 - APC_journaling 或类似的东西与 Sessions 结合使用最终会更好....会话开始并使用分配给每个会话的唯一外部 Id 写入,该会话关闭,并打开一个日志(通过 _store 和 _add 在 apc 缓存中的数组) /created 用于任何其他打算进入会话的写入,然后可以在下一个方便的机会读取、验证并写入 apc 中的会话(由该唯一 ID 标识!)。

我找到了一篇很好的博文,解释说 Sven 所指的 Locking havoc 来自 Session 阻塞,直到它关闭或脚本执行结束。立即关闭的会话不会阻止阅读和写作。 http://konrness.com/php5/how-to-prevent-blocking-php-requests - 博客文章链接。 希望这会有所帮助。

【讨论】:

【参考方案9】:

在 PHP 中缓存外部数据

教程链接 - http://www.gayadesign.com/diy/caching-external-data-in-php/

如何在 PHP 中使用 APC 缓存

教程链接 - http://www.script-tutorials.com/how-to-use-apc-caching-with-php/

【讨论】:

请注意,不鼓励link-only answers,因此答案应该是搜索解决方案的终点(与另一个参考文献的中途停留相比,随着时间的推移往往会变得陈旧)。请考虑在此处添加独立的概要,并保留链接作为参考。

以上是关于如何在 APC 缓存中存储 PHP 会话?的主要内容,如果未能解决你的问题,请参考以下文章

PHP 的 APC 用户数据缓存的完整替代方案?

APC 操作码缓存的工作原理

php可选缓存APC

PHP之APC缓存详细介绍(学习整理)

PHP require 在清除 APC 缓存后仅工作一次,然后出现 500 错误。为啥?

php apc缓存以及与redis的对比