2

纲要:

  • 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;  
    }
    
    

lcoding
317 声望40 粉丝

The quality of code is everything...