讓我糾結的phpredis

在最近的項目中,須要用php訪問redis,咱們選擇了phpredis,下面是讓人糾結的一些問題。 php


redis持久鏈接不靠譜。

能夠說這是php的通病了,無論是mysql、memcache仍是redis,期望由php自己(包含php擴展)來實現持久鏈接都是行不通的。 前端

爲何這麼說呢?

首先,所謂的持久鏈接的實現不外乎在進程(php-fpm)內建一個鏈接池,當php須要鏈接時,先以ip+port等信息爲key在池中查找,找到則直接返回已有鏈接沒有則新建鏈接。而當一個請求執行結束時,不關閉鏈接,而是把鏈接歸還到池中。 mysql

這樣當php須要用到多個redis實例時(分庫),由於一個php-fpm進程會持有每一個redis實例的一個鏈接,因此須要「php-fpm進程數「*「redis實例數"個redis鏈接,而對於每一個redis服務器則有「php-fpm進程數「個客戶端鏈接。 web

舉個例子:一個web應用開了1000個php-fpm進程,有10個redis實例,那麼保持的redis鏈接數就爲1000*10也就是10000,每一個redis實例有1000個客戶端鏈接。若是前端或redis再擴容所須要的鏈接就會以乘積方式增長。一個redis實例有php-fpm進程數個鏈接的狀況下表現如何呢,這就要好好測一測了,反正是每鏈接一線程的mysql是直接堵死了。 redis

RedisArray不靠譜。

RedisArray實現了一致性hash分佈式,可是它在初始化的時候就會鏈接上每一個實例,這在web應用中簡直是胡鬧,它對一致性hash實現得比較完善,結點失效、動態添加結點時從新hash都有處理,在萬不得已進行水平擴容時,可能會用得上。 sql

須要自已關閉redis鏈接。

Redis的析構函數沒有關閉redis鏈接,這會致使redis網絡負載太高,要確保腳本結束時關閉鏈接,最好是可以封裝一下Redis類再使用。 安全

示例封裝 /// 分佈式Redis. class RedisShard {     /// 構造函數.     public function __construct($shards) {         $this->reinit($shards);     }     /// 析構函數.     /// 腳本結束時,phpredis不會自動關閉redis鏈接,這裏添加自動關閉鏈接支持.     /// 能夠經過手動unset本類對象快速釋放資源.     public function __destruct() {         if(isset($this->shard)){             $this->shard['redis']->close();         }     }     /// 從新初始化.     public function reinit($shards){         $index = 0;         $this->shards = array();         foreach($shards as $shard){             $this->shards[$index] = explode(':', $shard); //格式:host:port:db             $this->shards[$index]['index'] = $index;             ++$index;         }             }     /// 轉發方法調用到真正的redis對象.     public function __call($name, $arguments) {         $result = call_user_func_array(array($this->redis($arguments[0]), $name), $arguments);         if($result === false and in_array($name, array('set', 'setex', 'incr'))) {             trigger_error("redis error: " . $this->shard[0] . ':' . $this->shard[1] . ':' .$this->shard[2] . " $name " . implode(' ', $arguments), E_USER_NOTICE);         }         return $result;     }     /// 獲取1至max間的惟一序號name,達到max後會從1開始.     /// redis的遞增到最大值後會返回錯誤,本方法實現安全的遞增。     /// 失敗返回false,最要確保已用redis()方法連到生成序號的某個redis對象.     public function id($name, $max) {         if(isset($this->shard)){             $id = $this->shard['redis']->incr('_id_' . $name);             if($id){                 $max = intval($max/count($this->shards));                 if($id % $max == 0){                     while($this->shard['redis']->decrBy('_id_' . $name, $max) >= $max){                     }                     $id = $max;                 }                 else if($id > $max){                     $id %= $max;                 }                 return ($id - 1)*count($this->shards) + ($this->shard['index'] + 1);             }         }         return false;     }     /// 鏈接並返回key對應的redis對象.     public function redis($key){         //TODO: crc32在32位系統下會返回負數,因咱們是部署在64位系統上,暫時忽略.         assert(PHP_INT_SIZE === 8);         $index = crc32($key) % count($this->shards);         $shard = $this->shards[$index];         if(isset($this->shard)){             //嘗試重用已有鏈接.             if($this->shard[0] == $shard[0] and $this->shard[1] == $shard[1]){                 if($this->shard[2] != $shard[2]){                     if(! $this->shard['redis']->select($shard[2])){                         trigger_error('redis error: select ' . $shard[0] . ':' . $shard[1] . ':' .$shard[2], E_USER_ERROR);                         return false;                     }                     $this->shard[2] = $shard[2];                 }                 return $this->shard['redis'];             }             $this->shard['redis']->close();             unset($this->shard);         }         //新建鏈接.         $shard['redis'] = new Redis();         if(! $shard['redis']->connect($shard[0], $shard[1])){             trigger_error('redis error: connect ' . $shard[0] . ':' . $shard[1], E_USER_ERROR);             return false;         }         $db = intval($shard[2]);         if($db != 0 and !$shard['redis']->select($db)){             trigger_error('redis error: select ' . $shard[0] . ':' . $shard[1] . ':' .$shard[2], E_USER_ERROR);             $shard['redis']->close();             return false;         }         if(ENABLE_DEVELOP){             trigger_error('redis connect success. ' . $shard[0] . ':' . $shard[1] . ':' . $shard[2], E_USER_NOTICE);         }                 $this->shard = $shard;         return $this->shard['redis'];     } }
相關文章
相關標籤/搜索