系列文章---框架篇:7.使用redis加速session读写


大家都知道,默认的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:

1
2
3
4
5
6
7
8
9
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来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
<?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:’;

然后做一下调整即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
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覆盖掉。

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

总结

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