框架篇: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
我介绍两种方法给大家:
- session_start带参
- 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 你设置的密码)即可
我们可以看到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']);
知识点:
- 这里需要注意下read方法,里面需要加一下serialize,以便于我们存储复杂的session结构。如果不加会报错(Warning: session_start(): Failed to read session data: user (path: ))这是因为Redis无法直接存储array结构,需要转化为string类型存储。
我们也来看看Redis客户端存储情况:
通用我们也看到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覆盖掉。
当然我比较建议使用第二种方法,便于我们定制化编码。
总结
总的来说,两种方法配置都比较简单,个人建议使用第二种方式实现,这样也比较适合集成到框架,后期我们可以在进行扩展。