PHP DIY 系列------框架篇:7. 使用 Redis 加速 Session 讀寫

13sai發表於2020-02-20

大家都知道,預設的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覆蓋掉。

當然我比較建議使用第二種方法,便於我們定製化編碼。

總結

總的來說,兩種方法配置都比較簡單,個人建議使用第二種方式實現,這樣也比較適合整合到框架,後期我們可以在進行擴充套件。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

分享開發知識,歡迎交流。qq957042781

相關文章