纲要:
laravel中redis集群的应用
predis对redis集群模式的底层实现
laravel中redis集群的应用
这部分我想分享下laravel5.2中redis集群的配置(官网也有redis集群的配置讲解,但是5.2版还是有点不足,只是说了将cluster配置项设为true,但光这样一个选项不能代表,一个新手直接可用redis集群,这部分还包括predis客户端的事,所以后面我也会分享下关于predis的源码分析)。
redis—cluster的搭建:Easy Building Redis-cluster (轻松搭建reids集群)
系统软件清单:
配置文件:config/database.php
'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');就存到集群中了.
predis对redis集群的底层实现
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;
}