前言
之前找了网上的一套直播系统给客户用, 刚开始是没问题的, 在后面人数上来之后网站开始变得卡顿, 卡的一批. 之后查看 PHP 慢日志发现 session_start() 的身影, 好吧, 原来是万恶的文件存储 session, 跟我之前进的坑一模一样...... 之前做的教务查询系统直接用的 session 没有用 cookie, 结果在高并发的情况下 PHP 原地爆炸.
- [0x00007fff67ee6740] session_start()
- [0x00007fff67ee7b70] +++ dump failed
解决方案
将 session 全面更换为 cookie
使用 MySQL 或 Redis 接管 session
坑中坑
因为这套直播系统一没有用框架, 二没有设计规范, 各种 session 操作散落在不同的文件里, 用第一个解决方案完全属于费力不讨好. 再者直播系统的聊天互动等功能已经涉及大量的 MySQL 操作, 再用 MySQL 接管 session 变相的增加了数据库的压力, 最终确定了使用 Redis 接管 session.
具体实现
PHP 有内置的操作 session 的 save_handler, 使用 session_set_save_handler, 接管所有的 session 管理工作. 在使用该函数前, 先把 PHP.INI 配置文件的 session.save_handler 选项设置为 user, 否则 session_set_save_handle 不会生效. 另外除了安装 Redis 之外, PHP 扩展也需要增加 Redis.
(以下代码来源于网络, 也不知道原创是哪位大佬) 编写一个 session 管理类 sessionManager.PHP, 代码如下:
- <?PHP
- class SessionManager{
- private $Redis;
- private $sessionSavePath;
- private $sessionName;
- private $sessionExpireTime=30;//Redis,session 的过期时间为 30s
- public function __construct(){
- $this->Redis = new Redis();// 创建 phpredis 实例
- $this->Redis->connect('127.0.0.1',6379);// 连接 Redis
- $this->Redis->auth("107lab");// 授权
- $retval = session_set_save_handler(
- array($this,"open"),
- array($this,"close"),
- array($this,"read"),
- array($this,"write"),
- array($this,"destroy"),
- array($this,"gc")
- );
- session_start();
- }
- public function open($path,$name){
- return true;
- }
- public function close(){
- return true;
- }
- public function read($id){
- $value = $this->Redis->get($id);// 获取 Redis 中的指定记录
- if($value){
- return $value;
- }else{
- return '';
- }
- }
- public function write($id,$data){
- if($this->Redis->set($id,$data)){// 以 session ID 为键, 存储
- $this->Redis->expire($id,$this->sessionExpireTime);// 设置 Redis 中数据的过期时间, 即 session 的过期时间
- return true;
- }
- return false;
- }
- public function destroy($id){
- if($this->Redis->delete($id)){// 删除 Redis 中的指定记录
- return true;
- }
- return false;
- }
- public function gc($maxlifetime){
- return true;
- }
- public function __destruct(){
- session_write_close();
- }
- }
SessionManager 构造函数主要用来连接 Redis 服务器, 使用 session_set_save_handler 函数设置 session 回调函数, 并调用 session_start 函数开启 session 功能. 因为本例中 open,close 和 gc 回调函数的作用不是很大, 所以直接返回 true.
在 write 回调函数中, 以 session ID 作为 key, 把 session 的数据作为 value 存储到 Redis 服务器, 设置 session 的过期时间为 30 秒. 在 read 回调函中, 以 session ID 作为 key 从 Redis 服务器中读取数据, 并返回此数据. 而在 destroy 回调函数重, 则以 session ID 作为 key 从 Redis 服务器中删除对应的 session 数据.
使用时, 只需包含 SessionManager 类, 然后实例化一个 SessionManager 对象. 下面建立个 session_set.PHP 文件, 代码如下:
- <?PHP
- include('SessionManager.php');
- new SessionManager();
- $_SESSION['username'] = 'captain';
然后再创建一个 session_get.PHP 文件, 代码如下:
- <?PHP
- include('SessionManager.php');
- new SessionManager();
- echo $_SESSION['username'];
测试时, 首先访问 session_set.PHP, 然后再访问 session_get.PHP, 输出结果如下所示:
再查看 Redis 数据库, 如下所示:
- 127.0.0.1:6379> keys *
- 1) "oe94eic337slnjv1bvlreoa574"
- 127.0.0.1:6379> get oe94eic337slnjv1bvlreoa574
- "username|s:7:\"captain\";"
测试完美~ 然后将原系统中的 session_start() 替换成 session_set.PHP 的前两行, 成功接管, 舒服.
来源: https://juejin.im/post/5bfe15dff265da61542d53ac