redis 使用案例

奮程式序猿發表於2017-01-19

1.使用redis訊息列隊釋出資訊

在一些使用者創造使用者的應用中(如SNS,微博),可能出現1秒有上千萬個使用者同時釋出訊息的情況,此時如果使用mysql可能出現‘too many connections’ 錯誤,當然,把mysql的max_connections 引數設定為更大數,不過這是一個治標不治本的方法,這是可以考慮使用redis。

使用redis的list型別作為訊息列隊,把使用者釋出的訊息暫時儲存在訊息列隊中,接著使用一個cron程式把訊息列隊中的訊息插入mysql,這樣有效率降低mysql的併發量。

例如釋出一條微博使用一下介面:

<?php
$uid = get_uid();
$content = get_content();

$timestamp = time();
$weibo = new weibo();
$weibo->pot($uid,$contnet,$timestamp);
?>

weibo物件的post方法就是釋出微博的介面,它直接把微博寫入mysql。引數$uid是使用者的UID,$content是微博的內容,$timestamp是釋出的時間戳。

為了降低mysql的併發數,先把使用者釋出的微博存在redis中,程式碼如下:

<?php
$redis = new redis('127.0.0.1',6379);
$redis -> connect();
$weibo_info = [
'uid' => get_uid(),
'content' => get_cntent(),
'timestamp' => time()
];

$redis->lpush('weibo_list',json_encode($weibo_info));
$redis -> close();
?>

 先把微博資訊使用json_encode編碼成JSON格式。然後使用redis物件的lpush方法把微博資訊插入到weibo_list列隊。

把微博存到redis以後,編寫一個cron程式吧redis中的微博資訊插入到mysql中程式碼如下:

<?php
$redis = new redis('127.0.0.1',6379);
$redis->connect();
$weibo = new weibo();
while(true){
    if($redis->lsize('weibo_list') > 0){
         $info = $redis->rpop('weibo_list');
        $info = json_decode($info);
       $weibo -> post($info->uid,$info->content,$info->timestamp);
    }else{
       sleep(1);
    }

}
$redis ->close();
?>        

在cron程式中,先使用redis物件的rpop()方法從weibo_list列表中取得一條微博資訊,然後使用json_decode ()函式解碼,最後呼叫weibo物件的post方法把微博資訊插入mysql。

使用訊息列隊有一個缺點,就是‘延時’。為了把延時降低,執行多個cron程式同時把訊息列隊中的資料插入mysql。使用訊息列隊的好處是擴充套件性好,當一臺redis伺服器不能應付大量併發時,使用‘一致性Hash演算法’把併發分發到redis伺服器。

2.使用redis代替檔案儲存session

PHP預設使用檔案儲存session,如果併發量大,效率非常低。而redis對高併發的支援非常好,所以,可以使用redis替代檔案儲存session。

在講解例項之前,先了解php的session_set_save_handler函式的作用和使用方法。該函式定義設定使用者自定義會話儲存函式(如開啟、關閉、寫入等)。原型如下:

bool session_set_save_handler(callback open,callback close,callback read,callback write,callback destroy,callback gc,callable create_sid)

sesson_set_save_handler 函式各引數作用如下:

open 回撥函式類似於類的建構函式, 在會話開啟的時候會被呼叫。 這是自動開始會話或者通過呼叫 session_start() 手動開始會話 之後第一個被呼叫的回撥函式。 此回撥函式操作成功返回 TRUE,反之返回 FALSE

close 回撥函式類似於類的解構函式。 在 write 回撥函式呼叫之後呼叫。 當呼叫 session_write_close() 函式之後,也會呼叫 close 回撥函式。 此回撥函式操作成功返回 TRUE,反之返回 FALSE

 

read 如果會話中有資料,read 回撥函式必須返回將會話資料編碼(序列化)後的字串。 如果會話中沒有資料,read 回撥函式返回空字串。

在自動開始會話或者通過呼叫 session_start() 函式手動開始會話之後,PHP 內部呼叫 read 回撥函式來獲取會話資料。 在呼叫 read 之前,PHP 會呼叫 open 回撥函式。

read 回撥返回的序列化之後的字串格式必須與 write 回撥函式儲存資料時的格式完全一致。 PHP 會自動反序列化返回的字串並填充 $_SESSION 超級全域性變數。 雖然資料看起來和 serialize() 函式很相似, 但是需要提醒的是,它們是不同的。 請參考: session.serialize_handler

write在會話儲存資料時會呼叫 write 回撥函式。 此回撥函式接收當前會話 ID 以及 $_SESSION 中資料序列化之後的字串作為引數。 序列化會話資料的過程由 PHP 根據 session.serialize_handler 設定值來完成。

序列化後的資料將和會話 ID 關聯在一起進行儲存。 當呼叫 read 回撥函式獲取資料時,所返回的資料必須要和 傳入 write 回撥函式的資料完全保持一致。

PHP 會在指令碼執行完畢或呼叫 session_write_close() 函式之後呼叫此回撥函式。 注意,在呼叫完此回撥函式之後,PHP 內部會呼叫 close回撥函式。

destroy當呼叫 session_destroy() 函式, 或者呼叫 session_regenerate_id() 函式並且設定 destroy 引數為 TRUE 時, 會呼叫此回撥函式。此回撥函式操作成功返回 TRUE,反之返回 FALSE

gc為了清理會話中的舊資料,PHP 會不時的呼叫垃圾收集回撥函式。 呼叫週期由 session.gc_probability 和 session.gc_divisor 引數控制。 傳入到此回撥函式的 lifetime 引數由 session.gc_maxlifetime 設定。 此回撥函式操作成功返回 TRUE,反之返回 FALSE

create_sid當需要新的會話 ID 時被呼叫的回撥函式。 回撥函式被呼叫時無傳入引數, 其返回值應該是一個字串格式的、有效的會話 ID。

在使用該函式前,先把php.ini配置檔案的session.save_handler選項設定為user,否則session_set_save_handler 不會生效。

編寫一個session管理sessionManager,程式碼如下:

<?php
class SessionManager{
    private $redis;
    private $sessionSavePath;
    private $sessionName;
    private $sessionExpireTime = 30;
    public function __construct(){
        $this->redis = new redis();
        $this->redis->connect('127.0.0.1',6379);
        $retval = session_set_save_handler (
        [$this,'open'],
        [$this,'close'],
        [$this,'read'],
        [$this,'write'],
        [$this,'destroy'],
        [$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);
        if($value){
            return $value;
        }else{
            return '';
        }
    }
    public function write($id,$data){
        if($this->redis->set($id,$data)){
            $this->redis->expire($id,$this->sessionExpireTime);
            return true;
        }
        return false;
    }
    public function destroy($id){
        if($this->redis->delete($id)){
            return false;
        }
        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回撥函式中,以SessionID作為key,把session的資料作為value儲存到redis伺服器,設定session的過期時間為30秒。在read回撥函式中,以SessionID作為key從redis伺服器中讀取資料,並返回資料。而在destroy回撥函式中,則以sessionID 作為key從redis伺服器中刪除對應的session資料。

使用時只需要包含SessionManager類,然後例項化一個SessionManager物件。下面例子使用SessionManager來管理session,首先建立一個session_set.php,輸入程式碼如下:

include('SessionManager.php');
new SessionManager();
$_SESSION['username'] = 'newsession';

然後再建立一個sesion_get.php 檔案程式碼如下:

include('SessionManager.php');
new SessionManager();
echo $_SESSION['username'];

測試時先訪問 session_set.php 然後再訪問 session_get.php

相關文章