框架篇:7.使用Redis加速session读写

PHP DIY系列–一起手写一个api框架


大家都知道,默认的session是存储在文件里的,一般情况下这是没什么问题的,然而一旦访问很多,session的使用就会频繁读写文件,必然会影响应用的性能。另外,假如是多机部署,session的共享也是个问题。

既然我们知道有Redis这个利器,而PHP也是支持session自定义的,那么为何不要性能更好又能实现共享session的Redis呢?

Redis是什么?

Redis 是完全开源免费的,遵守 BSD 协议,是一个高性能的 key - value 数据库

Redis数据结构

  • string
  • hash
  • set
  • sorted set
  • pub/sub
  • list

另外还有HyperLogLog,geo….

Redis的优势

  • 性能极佳,官网显示QPS能达到100k/s
  • 数据结构丰富,不止于字符串,hash
  • 稳定性不错,持久化
  • 支持集群
  • 社区不错,使用率高,对于PHP程序员,除了LNMP/LAMP,然后应该就是Redis了

Redis应用

  • 缓存
  • 分布式锁
  • 计数器
  • 队列
  • geo
  • ……

Redis命令

使用Redis存储session

我介绍两种方法给大家:

  1. session_start带参
  2. session_set_save_handler托管session

下面我们里一一说明:

session_start

session_start ([ array $options = array() ] ) : bool —— 启动新会话或者重用现有会话(点击查看更多参数

来看demo:

session_start([
    'save_path' => 'tcp://127.0.0.1:6379',
    'save_handler' => 'Redis',
]);

$_SESSION['user_id'] = 10001;
$_SESSION['userInfo'] = ['name' => 'sai'];
p($_SESSION['user_id']);
p($_SESSION['userInfo']);

输出如下:

10001
Array
(
    [name] => sai
)

我们可以使用Redis-cli连接查看:

Redis-cli -h 127.0.0.1 -p 6379
// 如设置密码再输入(auth 你设置的密码)即可

image

我们可以看到Redis里有了PHPREDIS_SESSION:nmo65igogqnq8ur2gia94jt15u,里面存储了我们的session信息。

session_set_save_handler

建议session.serialize_handler = php_serialize,默认php写入和读取略微繁琐。

这里说明我们成功了将session信息通过Redis进行了读写。下面我们使用session_set_save_handler来实现:

<?php


namespace Library\Sessions;

use SessionHandler;

class RedisSession extends SessionHandler
{
    private $Redis;
    
    private $lifeTime = 7200;
    
    private $config;

    private $prefix = 'PHPREDIS_SESSION:';

    public function __construct($config)
    {
        $this->config = $config;
    }
    
    private function getRedisInstance()
    {
        if (empty($this->Redis)) {
            $Redis = new \Redis();
            $Redis->connect($this->config['host'], $this->config['port'], $this->config['timeout']);
            if (!$this->config['auth']) {
                $Redis->auth($this->config['auth']);
            }

            $this->Redis = $Redis;
        }
        return $this->Redis;
    }

    public function read($id)
    {
        return $this->getRedisInstance()->get($this->prefix.$id);
    }

    public function write($id, $data)
    {
        if ($this->getRedisInstance()->setex($this->prefix.$id, $this->lifeTime, $data)) {
            return true;
        }

        return false;
    }

    public function destroy($id)
    {
        if($this->getRedisInstance()->delete($id)){//删除Redis中的指定记录
            return true;
        }
        return false;
    }

    public function gc($maxlifetime)
    {
        return true;
    }

    public function __destruct()
    {
        session_write_close();
    }
}


$handler = new RedisSession([
            'host' => '127.0.0.1',
            'port' => 6379,
            'auth' => null,
            'timeout' => 5,
        ]);
session_set_save_handler($handler, true);
session_start();

$_SESSION['user_id'] = 10001;
$_SESSION['userInfo'] = ['name' => 'sai'];
p($_SESSION['user_id']);
p($_SESSION['userInfo']);

知识点:

  1. 这里需要注意下read方法,里面需要加一下serialize,以便于我们存储复杂的session结构。如果不加会报错(Warning: session_start(): Failed to read session data: user (path: ))这是因为Redis无法直接存储array结构,需要转化为string类型存储。

我们也来看看Redis客户端存储情况:

image

通用我们也看到Redis存储了session,与前面略有不同的只是存储的key不一样。但是我们可以定义一个私有属性:

private $prefix = ‘PHPREDIS_SESSION:’;

然后做一下调整即可:

public function read($id)
{
    return serialize($this->getRedisInstance()->get($this->prefix.$id));
}

public function write($id,$data)
{
    if ($this->getRedisInstance()->setex($this->prefix.$id, $this->lifeTime, $data)) {
        return true;
    }

    return false;
}

运行后会发现把之前第一种设置的session覆盖掉。

当然我比较建议使用第二种方法,便于我们定制化编码。

总结

总的来说,两种方法配置都比较简单,个人建议使用第二种方式实现,这样也比较适合集成到框架,后期我们可以在进行扩展。


框架篇:7.使用Redis加速session读写
https://blog.puresai.com/2020/02/15/phpframe10/
作者
puresai
许可协议