1. 程式人生 > >PHP中使用Redis長連線筆記

PHP中使用Redis長連線筆記

pconnect函式宣告

其中time_out表示客戶端閒置多少秒後,就斷開連線。函式連線成功返回true,失敗返回false:

    pconnect(host, port, time_out, persistent_id, retry_interval)
        host: string. can be a host, or the path to a unix domain socket
        port: int, optional 
        timeout: float, value in seconds (optional, default is 0 meaning unlimited) 
        persistent_id: string
. identity for the requested persistent connection retry_interval: int, value in milliseconds (optional)

下面的例子詳細介紹了pconnect連線的重用情況。

    $redis->pconnect('127.0.0.1', 6379); 
    $redis->pconnect('127.0.0.1'); // 預設埠6379,跟上面的例子使用相同的連線。
    $redis->pconnect('127.0.0.1', 6379, 2.5); // 設定了2.5秒的過期時間。將是不同於上面的新連線
$redis->pconnect('127.0.0.1', 6379, 2.5, 'x'); //設定了持久連線的id,將是不同於上面的新連線 $redis->pconnect('/tmp/redis.sock'); // unix domain socket - would be another connection than the four before.

pconnect使用介紹

對pconnect方法簡單描述

使用該方法建立連線,連線不會在呼叫close方法之後關閉,只有在程序結束之後該連線才會被關閉。

[待驗證]如果使用的是長連線,Redis配置檔案中的timeout配置項需要設定為0,否則連線池中的連線會因為超時而失效

針對PHP-FPM來說明一下pconnect

長連線只會在PHP-FPM程序結束之後結束,連線的生命週期就是PHP-FPM程序的生命週期。
相比較短連線而言,在每一個PHP-FPM呼叫過程中都會產生一個redis的連線,在伺服器上的表性形式就是過多的time_out連線狀態。
而長連線相反,PHP-FPM呼叫的所有CGI都只會共用一個長連線,所以也就是隻會產生固定數量的time_out。

關閉長連線

可以呼叫close和unset方法,但兩則差異很大:

- close的作用僅僅是使當前PHP程序不能再進行redis請求,但無法真正關閉redis長連線,連線在後續請求中仍然會被重用,直FPM程序生命週期結束。所以close 並不會銷燬redis物件,只是斷開連線而已。

- unset 變數才會銷燬。也需要注意並不是使用了 pconnect 就不要 close 了,如果當前指令碼執行時間很長 那麼也會一直佔用一個連線的。

如何判斷當前Redis是否處於連線狀態

等效的問題是,在單例模式中,判斷當前例項是否有效。

習慣上呼叫echo,判斷是否正常返回字串本身,或者呼叫ping,檢視返回值是否為 +PONG。

但是需要特別小心的是,在redis斷開連線之後,呼叫echo以及ping(返回'+POMG')時,均會丟擲異常。所以要通過異常捕獲機制來處理。

程式碼分析pconnect連線重用的問題

情況一:非單例模式。

說明:ab例項共用了一條連線,ba例項的連線修改了:
所以下面的例子導致最終$a例項得到的值變成了2,需要特別注意。

$a = pconnect(host, port, time_out);
select(3);
$a -> setex(id, 3);
echo $a -> get(id);

//之後執行下面的連線
$b = pconnect(host, port, time_out);
select(2);
$b->set(id,2) 
echo $a->get(id);    //這個id操作的db變成了2,不再是之前的3了。因為這兩個連線共用了一個連線通道。

情況二:單例模式。

將上述的程式碼修改,ab都通過getInstance來生成。生成的前提是判斷當前例項是否存在。單例模式的混淆點在於:

$a生成了一個例項,這時候生成$b, $b使用了$a的例項,然後修改了$a的連線,之後呼叫$a肯定是呼叫的$b修改之後的例項。跟情況二一致。
單例模式的程式碼如下:
public static function getInstance($db = 0)
{
    if (!isset(self::$_instance)) {
        self::$_instance = new Redis();
    }
    self::_connect();
    self::$_instance->select($db);
    return self::$_instance;
}

兩種情況都說明了連線重用的問題。如何修復這個bug?兩點:

1.為每一個db生成一個單例。 
2.避免連線重用問題。

所以程式碼可以做調整為返回一個單例陣列:

public static function getInstance($db = 0)
{
    try{
        if (isset(self::$_instance[$db]) && self::$_instance[$db]->Ping() == 'Pong') {
            return self::$_instance[$db];
        }   
    } catch (Exception $e) {

    }

    self::$_instance[$db] = new Redis();
    self::_connect($db);
    return self::$_instance[$db];
}

需要注意的地方

避免在Task類成員變數中使用redis物件。

在redis的單例模式中,聲明瞭time_out的過期時間。如果redis處理的場合是一個任務,而任務呼叫redis間隔時間又比較長。當間隔大於time_out時候,redis就會斷開連線,這時候所有對redis的操作都會失效。解決的辦法就是避免這種呼叫方式,通過在呼叫的地方動態宣告redis類來執行。這種問題對於長連線和短連結是沒有區分,屬於呼叫的方式錯誤。