在Redis中保存PHP会话

Sessions allow a web-based application to maintain state across multiple HTTP requests. You can register any number of variables as session variables which are then typically stored in a temporary directory on the server or passed to the client browser through cookies. Subsequent requests have access to the saved information and the application’s state is preserved across multiple requests.

会话允许基于Web的应用程序跨多个HTTP请求维护状态。 您可以将任意数量的变量注册为会话变量,然后通常将其存储在服务器上的临时目录中或通过cookie传递给客户端浏览器。 后续请求可以访问保存的信息,并且跨多个请求保留应用程序的状态。

PHP’s default handling of session data is probably sufficient for most applications, but sometimes a project will demand a different storage approach. Luckily, the session handling routines can be overridden either by a series of functions (in older versions of PHP) or a class with methods (in newer versions) which handle various aspects of session management.

PHP对会话数据的默认处理对于大多数应用程序来说可能已经足够,但是有时项目需要使用不同的存储方法。 幸运的是,可以通过一系列功能(在PHP的较早版本中)或具有处理会话管理各个方面的方法的类(在新版本中)来覆盖会话处理例程。

In this article we’ll learn how to create a custom session handler which implements PHP’s SessionHandlerInterface interface and stores the session data in a Redis database.

在本文中,我们将学习如何创建自定义会话处理程序,该处理程序实现PHP的SessionHandlerInterface接口并将会话数据存储在Redis数据库中。

为什么要自定义存储? (Why Custom Storage?)

You may be wondering why someone might want (or need) to go through the trouble of writing a custom handler. If so, it may be beneficial to consider the following.

您可能想知道为什么有人可能想要(或需要)解决编写自定义处理程序的麻烦。 如果是这样,考虑以下内容可能是有益的。

It’s common when applications reside within a server farm for each host to handle requests for load balancing and redundancy. Session data stored in temporary files would only be accessible to their particular host. Since the server handling a request may not be the same one that handled the previous request, guaranteed access to state information is impossible without an alternative handling mechanism. Session data could be stored to a central storage mechanism and be made available to all machines in the cluster.

当应用程序驻留在服务器场中并且每个主机都可以处理负载平衡和冗余请求时,这很常见。 临时文件中存储的会话数据只能由其特定主机访问。 由于处理请求的服务器可能与处理先前请求的服务器不同,因此,如果没有其他处理机制,就不可能保证对状态信息的访问。 会话数据可以存储到中央存储机制,并可供群集中的所有计算机使用。

On a shared hosting server, it’s likely that all temporary session files are stored within the same directory. The directory and its contents is accessible to any processes initiated under the web service account which has the potential to pose a serious security risk. A malicious user who can access someone else’s session data has the ability to impersonate the legitimate user.

在共享托管服务器上,所有临时会话文件可能都存储在同一目录中。 在Web服务帐户下启动的任何进程都可能访问该目录及其内容,这可能会带来严重的安全风险。 可以访问其他人的会话数据的恶意用户可以模拟合法用户。

Custom session handling also provides a greater degree of flexibility. Because you’re specifying a new method of storage and access for the information, you can manipulate the data any way you want to meet your needs. Perhaps you may want to retain information for security auditing or troubleshooting. Custom handlers can be used to as hooks to expand an application in terms of size and functionality in any number of ways.

自定义会话处理还提供了更大程度的灵活性。 因为您正在为信息指定一种新的存储和访问方法,所以您可以按照想要的任何方式操纵数据。 也许您可能希望保留信息以进行安全审核或故障排除。 自定义处理程序可以用作挂钩,以多种方式扩展应用程序的大小和功能。

会话管理动作 (Session Management Actions)

Since we’re going to manage our own session data instead of letting PHP handle it for us, any code we write needs to address each of the six session management tasks.

由于我们将管理自己的会话数据而不是让PHP为我们处理数据,因此我们编写的任何代码都需要解决六个会话管理任务中的每一个。

  • open – opens a resource to the storage mechanism designed to save session information.

    open –打开用于保存会话信息的存储机制的资源。

  • close – closes the storage resource and initiates any other necessary cleanup activities.

    close –关闭存储资源并启动任何其他必要的清理活动。

  • read – retrieves previously saved session data from the storage mechanism.

    读取 –从存储机制中检索以前保存的会话数据。

  • write – stores new session data which the application needs to remember.

    –存储应用程序需要记住的新会话数据。

  • destroy – resets a session and discards any information from it.

    销毁 –重置会话并丢弃会话中的任何信息。

  • gc – purges data from the storage mechanism after it has become stale and is no longer needed.

    gc –在过时且不再需要的存储机制中清除数据。

Each of these tasks must be addressed when writing custom session handlers; PHP will not let us override the open process but forget to override the garbage collection process, for example. It’s all or nothing.

在编写自定义会话处理程序时,必须解决所有这些任务; 例如,PHP不会让我们覆盖打开过程,而忘记覆盖垃圾回收过程。 全部或全无。

In versions of PHP prior to 5.4, we would need to specify six callables (one function or method for handling each task) in the order above as arguments to the session_set_save_handler() function. In 5.4 and later, we can create a class that implements the SessionHandlerInterface interface and pass an instance instead. The interface exposes six methods with the same signatures as would be used for the functions in the pre-5.4 approach.

在5.4之前PHP版本中,我们需要按上述顺序指定六个可调用对象(用于处理每个任务的一个函数或方法)作为session_set_save_handler()函数的参数。 在5.4及更高版本中,我们可以创建一个实现SessionHandlerInterface接口的类,并传递一个实例。 该接口公开了具有与5.4之前的方法中的功能相同的签名的六个方法。

储存机制 (Storage Mechanism)

Your choice of a storage mechanism is dictated by your needs. It could be anything—remote temporary files, a MySQL database, an LDAP server, shared memory segments, an XML-RPC service, an IMAP inbox, etc. For this article I’m going to illustrate storing session data in Redis.

您可以根据需要选择存储机制。 它可以是任何东西-远程临时文件,MySQL数据库,LDAP服务器,共享内存段,XML-RPC服务,IMAP收件箱等。在本文中,我将说明在Redis中存储会话数据。

PHP automatically generates a unique identifier to track the session and link it to a specific client. Because this token is unique for each session, the identifier is well suited for use as a key (in fact, this token usually serves as the filename in PHP’s default disk-based handling approach).

PHP自动生成一个唯一的标识符来跟踪会话并将其链接到特定的客户端。 由于此令牌在每个会话中都是唯一的,因此标识符非常适合用作键(实际上,该令牌通常在PHP的默认基于磁盘的处理方法中用作文件名)。

The session data is also automatically serialized and unserialized by PHP. That is, the method that receives the data for storing is passed the data already serialized, and the method that retrieves the data is expected to return serialized data. The functions session_encode() and session_decode() are also available if we need them for any reason, but generally we can simply store and retrieve the session data as provided.

会话数据也会由PHP自动序列化和反序列化。 即,接收要存储的数据的方法将已经序列化的数据传递给该方法,并且检索数据的方法期望返回序列化的数据。 如果出于任何原因需要使用session_encode()session_decode()函数,通常也可以根据需要简单地存储和检索会话数据。

Of course it’s important to clean up stale sessions that are no longer needed. For example, if we’re storing session data in a MySQL database for example, we’d want to include a timestamp with each record. The timestamp would be checked by our method that overrides the garbage collection behavior. But with Redis, we can take advantage of its EXPIRE command. EXPIRE sets a timeout or a TTL (time to live) on a key, and the key is automatically deleted after the timeout expires.

当然,清理不再需要的过时会话非常重要。 例如,如果我们将会话数据存储在MySQL数据库中,则希望在每个记录中都包含一个时间戳。 时间戳将由我们的方法检查,该方法将覆盖垃圾回收行为。 但是使用Redis,我们可以利用它的EXPIRE命令。 EXPIRE设置密钥的超时或TTL(生存时间),并且在超时到期后会自动删除密钥。

给我看代码! (Show Me the Code!)

We now know what functionality the SessionHandlerInterface interface promises, and we have a rough idea how the methods should interact with Redis, we can write our code. Without further ado, here’s the class:

现在,我们知道SessionHandlerInterface接口具有什么功能,并且大致了解方法应如何与Redis交互,从而可以编写代码。 事不宜迟,这里是课程:

<?php
class RedisSessionHandler implements SessionHandlerInterface
{
    public $ttl = 1800; // 30 minutes default
    protected $db;
    protected $prefix;

    public function __construct(PredisClient $db, $prefix = 'PHPSESSID:') {
        $this->db = $db;
        $this->prefix = $prefix;
    }

    public function open($savePath, $sessionName) {
        // No action necessary because connection is injected
        // in constructor and arguments are not applicable.
    }

    public function close() {
        $this->db = null;
        unset($this->db);
    }

    public function read($id) {
        $id = $this->prefix . $id;
        $sessData = $this->db->get($id);
        $this->db->expire($id, $this->ttl);
        return $sessData;
    }

    public function write($id, $data) {
        $id = $this->prefix . $id;
        $this->db->set($id, $data);
        $this->db->expire($id, $this->ttl);
    }

    public function destroy($id) {
        $this->db->del($this->prefix . $id);
    }

    public function gc($maxLifetime) {
        // no action necessary because using EXPIRE
    }
}

The first thing you might notice is that the open() and gc() methods are empty. I’ve made use of dependency injection to provide the Redis connection which opens the class up to unit testing, so nothing needs to be done in open(). Nothing needs to be done in gc() because Redis will handle expiring stale keys for us.

您可能会注意到的第一件事是open()gc()方法为空。 我已经利用依赖注入提供了Redis连接,该连接打开了类进行单元测试的步骤,因此在open()无需执行任何操作。 gc()不需要做任何事情,因为Redis将为我们处理过期的过期密钥。

In addition to the Redis connection, the constructor also accepts a prefix. The prefix and the session ID generated by PHP are combined and used as the key for storing and retrieving values. This is primarily a means to prevent name collisions, but also provides the benefit that we’ll know what we’re looking at if we issue KEYS * in a Redis client.

除了Redis连接之外,构造函数还接受前缀。 PHP生成的前缀和会话ID组合在一起,并用作存储和检索值的关键字。 这主要是防止名称冲突的一种方法,但是还提供了以下好处:如果在Redis客户端中发出KEYS * ,我们将知道要查看的内容。

When PHP calls the write() method, it passes two values: the session ID and a serialized string of session data. A SET command is used to store the data in Redis and we touch the key’s TTL. Any entries placed in the super global $_SESSION array are now stored.

当PHP调用write()方法时,它传递两个值:会话ID和会话数据的序列化字符串。 使用SET命令将数据存储在Redis中,然后我们触摸按键的TTL。 现在将存储在超级全局$_SESSION数组中的所有条目。

When PHP calls the read() method, it passes the session ID. A GET command is used to retrieves the data and we also touch the TTL again – after all, if we’re accessing the session then it makes sense to consider it still fresh. Note that the session data is returned in its serialized form directly from storage. PHP receives the string, unserializes it, and populates the $_SESSION array.

当PHP调用read()方法时,它将传递会话ID。 GET命令用于检索数据,我们也再次触摸TTL-毕竟,如果我们正在访问会话,则认为它仍然是新鲜的是有道理的。 请注意,会话数据直接以序列化形式从存储中返回。 PHP接收字符串,将其反序列化,然后填充$_SESSION数组。

Using our handler is as simple as creating an instance and passing that instance to session_set_save_handler().

使用我们的处理程序就像创建一个实例并将该实例传递给session_set_save_handler()一样简单。

<?php
$db = new PredisClient();
$sessHandler = new RedisSessionHandler($db);
session_set_save_handler($sessHandler);
session_start();

When session_start() is called, PHP will use our custom handler to manage sessions instead of its default approach; no other modification to our code is necessary.

当调用session_start() ,PHP将使用我们的自定义处理程序来管理会话,而不是默认方法。 无需对我们的代码进行其他修改。

If you’re stuck on a version of PHP earlier than 5.4, you can still use the class above (although you’ll have to either mock SessionHandlerInterface or remove the implements piece entirely). Its registration would look like this:

如果您使用PHP版本早于5.4,则仍可以使用上面的类(尽管您必须模拟SessionHandlerInterface或完全删除implements部分)。 其注册如下所示:

<?php
$db = new PredisClient();
$sessHandler = new RedisSessionHandler($db);
session_set_save_handler(
    array($sessHandler, 'open'),
    array($sessHandler, 'close'),
    array($sessHandler, 'read'),
    array($sessHandler, 'write'),
    array($sessHandler, 'destroy'),
    array($sessHandler, 'gc')
);
session_start();

结论 (Conclusion)

In this article we’ve seen how easy it is to implement the SessionHandlerInterface interface and provide the logic needed by PHP to store session data in a Redis database. Custom session handling is transparent at the code level and the result is an exciting way to increase the security and flexibility of you application with very little effort! For more information, read what the PHP Manual has to say about custom session handling, and explore the accompanying code for this article on GitHub.

在本文中,我们看到了实现SessionHandlerInterface接口并提供PHP将会话数据存储在Redis数据库中所需的逻辑是多么容易。 自定义会话处理在代码级别是透明的,其结果是一种激动人心的方法,可以轻松地提高应用程序的安全性和灵活性! 有关更多信息,请阅读PHP手册中有关自定义会话处理的内容 ,并在GitHub上探索本文的随附代码

Image via Fotolia

图片来自Fotolia

翻译自: https://www.sitepoint.com/saving-php-sessions-in-redis/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值