laravel中redis集羣的應用php
predis對redis集羣模式的底層實現laravel
這部分我想分享下laravel5.2中redis集羣的配置(官網也有redis集羣的配置講解,可是5.2版仍是有點不足,只是說了將cluster配置項設爲true,但光這樣一個選項不能表明,一個新手直接可用redis集羣,這部分還包括predis客戶端的事,因此後面我也會分享下關於predis的源碼分析)。redis
redis—cluster的搭建:Easy Building Redis-cluster (輕鬆搭建reids集羣)算法
系統軟件清單:segmentfault
配置文件:config/database.phpide
'redis' => [ 'cluster' => env('REDIS_CLUSTER', true), **'options'=>['cluster'=>'redis']**, //官網沒說這個,這是必須的!!後面會講爲何這麼配? 'default' => [ 'host' => env('REDIS_HOST', '127.0.0.1'), //任選一個master節點 'port' => env('REDIS_PORT',6379), 'database' => 0, 'timeout'=>15, 'read_write_timeout'=>1800 //redis客戶端鏈接之後的讀寫超時時間(默認是60s) ], 'extra'=>[ 'host'=>env('REDIS_EXTRA_HOST','127.0.0.1'), //任意一個集羣中的節點便可 'port'=>env('REDIS_EXTRA_PORT',7001) ] ]
ok,配完上面的步驟,redis集羣就能夠用了.
具體使用redis集羣的應用場景根據業務需求有不少種,好比集羣存session等.
app('request')->session()->put('key','value');就存到集羣中了.源碼分析
ok,想要了解配置文件中的參數,仍是得看源代碼,固然也是predis,上代碼.
Illuminate\Support\ServiceProvider\RedisServiceProvider; public function register() { $this->app->singleton('redis', function ($app) { return new Database($app['config']['database.redis']); }); } Illuminate\Redis\Database; public function __construct(array $servers = []) { $cluster = Arr::pull($servers, 'cluster'); //獲取'cluster'的鍵值 $options = (array) Arr::pull($servers, 'options'); //options 就是database.php中'options'的鍵值,是一個數組(但官網沒有提到,是個坑.) if ($cluster) { $this->clients = $this->createAggregateClient($servers, $options); //集羣模式'cluster=true' } else { $this->clients = $this->createSingleClients($servers, $options); //單機模式 'cluster=false' } } protected function createAggregateClient(array $servers, array $options = []) { return ['default' => new Client(array_values($servers), $options)]; //predis的Client類 } ---------- 注意:這裏提醒一下各參數的值: 此時$servers=[ [ 'host' => env('REDIS_HOST', '127.0.0.1'), 'port' => env('REDIS_PORT',6379), 'database' => 0, 'timeout'=>15, 'read_write_timeout'=>1800 ], [ 'host'=>env('REDIS_EXTRA_HOST','127.0.0.1'), 'port'=>env('REDIS_EXTRA_PORT',7001) ] ] $options = ['cluster'=>'redis'] 其實到這兒,就能夠解釋在database.php中增長options選項,並且是必選項,由於底層代碼須要判斷數據切片的方式. 除了看源碼, predis的包文檔也作了解釋.https://packagist.org/packages/predis/predis -------
接下來咱們看看這些底層要初始化的類吧.
Predis\Client; public function __construct($parameters = null, $options = null) { $this->options = $this->createOptions($options ?: array()); #$this->connection = $this->createConnection($parameters ?: array()); #$this->profile = $this->options->profile; } protected function createOptions($options) { if (is_array($options)) { return new Options($options); //如你所見,實例化Options類 } if ($options instanceof OptionsInterface) { return $options; } throw new \InvalidArgumentException('Invalid type for client options.'); } public function __construct(array $options = array()) { $this->input = $options; $this->options = array(); $this->handlers = $this->getHandlers(); }
$this->connection = $this->createConnection($parameters ?: array())
Predis\Client 文件 protected function createConnection($parameters) { # if ($parameters instanceof ConnectionInterface) { # return $parameters; # } # if ($parameters instanceof ParametersInterface || is_string($parameters)) { # return $this->options->connections->create($parameters); # } # if (is_array($parameters)) { # if (!isset($parameters[0])) { # return $this->options->connections->create($parameters); # } $options = $this->options; # if ($options->defined('aggregate')) { # $initializer = $this->getConnectionInitializerWrapper($options->aggregate); # $connection = $initializer($parameters, $options); # } else { # if ($options->defined('replication') && $replication = $options->replication) { # $connection = $replication; # } else { $connection = $options->cluster; // # } $options->connections->aggregate($connection, $parameters); # } return $connection; # } # if (is_callable($parameters)) { # $initializer = $this->getConnectionInitializerWrapper($parameters); # $connection = $initializer($this->options); # return $connection; # } # throw new \InvalidArgumentException('Invalid type for connection parameters.'); } Predis\Configuration\Options; protected function getHandlers() { return array( 'cluster' => 'Predis\Configuration\ClusterOption', 'connections' => 'Predis\Configuration\ConnectionFactoryOption', #'exceptions' => 'Predis\Configuration\ExceptionsOption', #'prefix' => 'Predis\Configuration\PrefixOption', #'profile' => 'Predis\Configuration\ProfileOption', #'replication' => 'Predis\Configuration\ReplicationOption', ); } public function __get($option) { #if (isset($this->options[$option]) || array_key_exists($option, $this->options)) { # return $this->options[$option]; #} if (isset($this->input[$option]) || array_key_exists($option, $this->input)) { $value = $this->input[$option]; unset($this->input[$option]); # if (is_object($value) && method_exists($value, '__invoke'){ # $value = $value($this, $option); # } if (isset($this->handlers[$option])) { $handler = $this->handlers[$option]; $handler = new $handler(); //會實例化Predis\Configuration\ClusterOption類 $value = $handler->filter($this, $value); } return $this->options[$option] = $value; } # if (isset($this->handlers[$option])) { # return $this->options[$option] = $this->getDefault($option); # } # return; } Predis\Configuration\ClusterOption文件 public function filter(OptionsInterface $options, $value) { if (is_string($value)) { $value = $this->createByDescription($options, $value); } # if (!$value instanceof ClusterInterface) { # throw new \InvalidArgumentException( # "An instance of type 'Predis\Connection\Aggregate\ClusterInterface' was expected." # ); # } return $value; } protected function createByDescription(OptionsInterface $options, $id) { switch ($id) { * Abstraction for a cluster of aggregate connections to various Redis servers * implementing client-side sharding based on pluggable distribution strategies. # case 'predis': # case 'predis-cluster': # return new PredisCluster(); //這個模式是客戶端經過CRC16算法在客戶端進行數據切片, 顯然這種模式的集羣是脆弱的,若是一個master節點掛了, 那其備節點若也掛了,那麼獲取數據就成問題了; 再有這種模式擴展性不好,維護成本高, 所以這個模式不推薦.固然用最新predis不存在這個問題. 我這邊predis,1.0算比較老了. case 'redis': case 'redis-cluster': return new RedisCluster($options->connections); //這種模式是基於服務端的數據切片,相較於第一種模式,優勢也顯而易見,維護成本低,擴展性好等. default: return; } } public function __get($option) { #if (isset($this->options[$option]) || array_key_exists($option, $this->options)) { # return $this->options[$option]; #} # if (isset($this->input[$option]) || array_key_exists($option, $this->input)) { # $value = $this->input[$option]; # unset($this->input[$option]); # if (is_object($value) && method_exists($value, '__invoke'){ # $value = $value($this, $option); # } # if (isset($this->handlers[$option])) { # $handler = $this->handlers[$option]; # $handler = new $handler(); # $value = $handler->filter($this, $value); # } # return $this->options[$option] = $value; #} if (isset($this->handlers[$option])) { //$options='connections' return $this->options[$option] = $this->getDefault($option); # } # return; } public function getDefault($option) { if (isset($this->handlers[$option])) { $handler = $this->handlers[$option]; //$handler = 'Predis\Configuration\ConnectionFactoryOption'; $handler = new $handler(); return $handler->getDefault($this); } } Predis\Configuration\ConnectionFactoryOption文件 public function getDefault(OptionsInterface $options) { return new Factory(); //最後實例化了一個'工廠'類 }
$this->profile = $this->options->profile;
Predis\Configuration\ProfileOption文件 public function __get($option) { #if (isset($this->options[$option]) || array_key_exists($option, $this->options)) { # return $this->options[$option]; #} # if (isset($this->input[$option]) || array_key_exists($option, $this->input)) { # $value = $this->input[$option]; # unset($this->input[$option]); # if (is_object($value) && method_exists($value, '__invoke'){ # $value = $value($this, $option); # } # if (isset($this->handlers[$option])) { # $handler = $this->handlers[$option]; # $handler = new $handler(); # $value = $handler->filter($this, $value); # } # return $this->options[$option] = $value; #} if (isset($this->handlers[$option])) { //$options='profile' return $this->options[$option] = $this->getDefault($option); # } # return; } public function getDefault($option) { if (isset($this->handlers[$option])) { $handler = $this->handlers[$option]; //$handler = 'Predis\Configuration\ProfileOption'; $handler = new $handler(); return $handler->getDefault($this); } } Predis\Configuration\ProfileOption文件 public function getDefault(OptionsInterface $options) { $profile = Factory::getDefault(); //實例化了Predis\Profile\RedisVersion300類 $this->setProcessors($options, $profile); return $profile; }