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

时间:2009-11-12 19:05:34

标签: php session caching scalability

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

9 个答案:

答案 0 :(得分:17)

<?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);
    }
}

答案 1 :(得分:10)

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

答案 2 :(得分:9)

简单地将/ tmp磁盘(或者,无论PHP会话文件存储在何处)放到tmpfsramfs等RAM磁盘上也会带来严重的性能提升,并且会更加透明切换,零代码更改。

性能增益可能会显着降低,但仍然会比磁盘上的会话快得多。

答案 3 :(得分:9)

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

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

使用APC作为会话存储

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

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

替代

可能有许多会话处理程序。检查phpinfo()部分Session的输出是否为“已注册的存储处理程序”。

RAM磁盘上的文件存储

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

共享内存(mm)

在启用mm的情况下编译PHP时可用。这是建在窗户上。

内存缓存(d)

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

答案 4 :(得分:3)

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

答案 5 :(得分:2)

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

session.save_handler = mm

但需要提供:http://php.net/manual/en/session.installation.php

答案 6 :(得分:2)

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

session.save_handler = memcache

答案 7 :(得分:2)

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

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

答案 8 :(得分:0)

在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/