Laravel + predis + Redis 集群 - 已移动/未连接到 127.0.0.1:6379

Laravel + predis + Redis cluster - MOVED / no connection to 127.0.0.1:6379

我有一个 laravel (5.3) 应用程序,其中 redis 用于会话(使用 predis)。只要我使用单个 redis 节点(使用 config/database.php 中的默认方法,一切都有效。一旦我切换到 Redis 集群,尽管我开始有 50% 的时间出现 MOVED 错误(基于谷歌搜索,我知道这应该由 predis 管理,但不知何故不是)。

我尝试将集群参数更改为 true,但随后出现奇怪的错误

No connection could be made because the target machine actively refused it. [tcp://127.0.0.1:6379] 

虽然我用的redis集群部署在Azure中(并且是通过.env文件配置的),单节点时参数接受没有问题

配置

这是我的laravel配置(如前所述,这是标准默认值)

'redis' => [

        'client' => 'predis',
        'cluster' => false,

        'default' => [
            'host' => env('REDIS_HOST', 'localhost'),
            'password' => env('REDIS_PASSWORD', null),
            'port' => env('REDIS_PORT', 6379),
            'database' => 0,
        ],

    ],

对于 Redis,我使用 Azure Redis 缓存集群 Premium P1,2 个分片(如 here 所述)。

更新 2

到目前为止,我还尝试了以下配置变体:

  1. 将群集设置为 true
  2. 正在将集群设置为 redis
  3. 添加默认->集群设置为redis
  4. 添加默认设置 -> 选项设置为数组('cluster', 'redis')

我一直收到 MOVED 错误...

我的Redis版本是3.2,predis/predis包1.1.1

predis 1.1+的工作配置

'redis' => [
        'cluster' => true,

        'default' => [
            'host' => env('REDIS_HOST', 'localhost'),
            'password' => env('REDIS_PASSWORD', null),
            'port' => env('REDIS_PORT', 6379),
            'database' => 0,
        ] ,
        'options' => [
            'cluster' => 'redis',
             'parameters' => ['password' => env('REDIS_PASSWORD', null)],
        ],
    ],

非常感谢您的帮助:)

长话短说:

  • 'cluster' => true 应为真以创建一个处理多个节点的聚合客户端。
  • 'options' => ['cluster' => 'redis'] 需要作为 default 的同级(不是子级)添加到配置中,以便告诉 Predis 处理 Azure 提供的服务器端集群。
  • 如果对服务器端集群使用身份验证,将需要 'options' => [ 'cluster' => 'redis', 'parameters' => ['password' => env('REDIS_PASSWORD', null)], ] 来对新发现的集群节点进行身份验证。

全文

在redis配置中,可以设置多个连接到多个redis实例。 cluster 选项告诉 Laravel 如何处理那些多个定义的连接。

如果 cluster 设置为 false,Laravel 将为每个连接创建单独的 \Predis\Client 实例。每个连接都可以单独访问,不会与另一个连接有任何关系。

如果 cluster 设置为 true,Laravel 将使用所有定义的连接创建聚合 \Predis\Client 实例。没有其他配置,这是一种“假”集群。它使用客户端分片来分配密钥空间,并且可能需要外部监控和维护以确保适当的密钥负载平衡。

但是,您 运行 遇到的问题是 Azure 实现了(大概)一个真正的服务器端 Redis 集群,它处理键空间的自动分片。在这种情况下,节点相互了解并相互交谈,并且可以上下移动。这是 MOVEDASK 响应的来源。

Predis 库可以自动处理这些响应,但只有在您告诉它需要时才可以。在这种情况下,您需要告诉 Predis 客户端它需要处理集群,这是由 Laravel 通过 redis 配置上的 options 数组完成的。

redis 配置中,options 键应该是您连接的同级(即 default),而不是子级。此外,选项应指定为 key => value 对。

因此,您的配置应如下所示:

'redis' => [
    'cluster' => true,

    'default' => [
        'host' => env('REDIS_HOST', 'localhost'),
        'password' => env('REDIS_PASSWORD', null),
        'port' => env('REDIS_PORT', 6379),
        'database' => 0,
    ],

    'options' => [
        'cluster' => 'redis',
    ],
],

redis 配置下的 cluster 键将告诉 Laravel 创建一个可以处理多个节点的聚合 Predis\Client 实例,而 cluster options 数组下的键将告诉该实例它需要处理服务器端集群,而不是客户端集群。

授权

原始连接参数(包括身份验证)不会与通过 -MOVED-ASK 响应发现的新节点的连接共享。因此,您之前从 -MOVED 响应中得到的任何错误现在只会转换为 NOAUTH 错误。但是,服务器端 'cluster' 配置允许 'parameters' 同级,它定义了一个参数列表,用于新发现的节点。这是您可以将您的身份验证参数用于新节点的地方。

我相信这看起来像:

'redis' => [
    'cluster' => true,

    'default' => [
        'host' => env('REDIS_HOST', 'localhost'),
        'password' => env('REDIS_PASSWORD', null),
        'port' => env('REDIS_PORT', 6379),
        'database' => 0,
    ],

    'options' => [
        'cluster' => 'redis',
        'parameters' => ['password' => env('REDIS_PASSWORD', null)],
    ],
],

公平警告,这是我刚刚从研究和代码潜水中获得的所有信息。虽然我已经将 Redis 与 Laravel 一起使用,但我还没有使用服务器端集群(还),所以这仍然可能行不通。

我在调查此问题时发现的一些有用信息:

讨论连接到 redis 集群的 Predis 问题:
https://github.com/nrk/predis/issues/259#issuecomment-117339028

It looks like you did not configure Predis to use redis-cluster but instead you are using it with the plain old client-side sharding logic (which is also the default behaviour). You should configure the client setting the option cluster with the value redis to let the client know it must play along with redis-cluster. Quick example:

$client = new Predis\Client([$node1, $node2, ...], ['cluster' => 'redis']);

Doing so will make it possible for the client to automatically handle -MOVED or -ASK responses coming from Redis nodes.

讨论 redis 缓存集群的 MS 文章:
https://docs.microsoft.com/en-us/azure/redis-cache/cache-how-to-premium-clustering#how-do-i-connect-to-my-cache-when-clustering-is-enabled

You can connect to your cache using the same endpoints, ports, and keys that you use when connecting to a cache that does not have clustering enabled. Redis manages the clustering on the backend so you don't have to manage it from your client.

Laravel 创建 Predis\Client 个实例的代码:
https://github.com/laravel/framework/blob/v5.3.28/src/Illuminate/Redis/Database.php#L25-L66

对于 AWS elasticache redis 集群,上面的配置不起作用,但是下面的配置对我有用。文档中还提到:https://laravel.com/docs/5.4/redis#configuration

'redis' => [
    'client' => 'predis',
    'options' => [
        'cluster' => 'redis',
    ],
    'clusters' => [
        'default' => [
            [
                'host' => env('REDIS_HOST', 'localhost'),
                'password' => env('REDIS_PASSWORD', null),
                'port' => env('REDIS_PORT', 6379),
                'database' => 0,
            ],
        ],
    ],
],

相关:

我在这里回答了:

相关详情如下:

如果您有集群 TLS,那么您将需要以下配置(使用 AWS Elasticache 测试):

'redis' => [
        'client' => 'predis',
        'cluster' => env('REDIS_CLUSTER', false),

        // Note! for single redis nodes, the default is defined here.
        // keeping it here for clusters will actually prevent the cluster config
        // from being used, it'll assume single node only.
        //'default' => [
        //    ...
        //],

        // #pro-tip, you can use the Cluster config even for single instances!
        'clusters' => [
            'default' => [
                [
                    'scheme'   => env('REDIS_SCHEME', 'tcp'),
                    'host'     => env('REDIS_HOST', 'localhost'),
                    'password' => env('REDIS_PASSWORD', null),
                    'port'     => env('REDIS_PORT', 6379),
                    'database' => env('REDIS_DATABASE', 0),
                ],
            ],
            'options' => [ // Clustering specific options
                'cluster' => 'redis', // This tells Redis Client lib to follow redirects (from cluster)
            ]
        ],
        'options' => [
            'parameters' => [ // Parameters provide defaults for the Connection Factory
                'password' => env('REDIS_PASSWORD', null), // Redirects need PW for the other nodes
                'scheme'   => env('REDIS_SCHEME', 'tcp'),  // Redirects also must match scheme
            ],
            'ssl'    => ['verify_peer' => false], // Since we dont have TLS cert to verify
        ]
    ]

解释以上内容:

  • 'client' => 'predis':指定要使用的 PHP 库 Redis 驱动程序 (predis)。
  • 'cluster' => 'redis':这告诉 Predis 假设 server-side 聚类。这仅意味着 "follow redirects"(例如 -MOVED 响应)。当 运行 一个集群时,一个节点将用 -MOVED 响应您必须要求特定密钥的节点。
    • 如果您没有为 Redis 集群启用此功能,Laravel 将抛出 -MOVED 异常 1/n 次,n 是 Redis 集群中的节点数(它会很幸运,每隔一段时间就会询问正确的节点)
  • 'clusters' => [...]:指定一个节点列表,但只设置一个'default'并将其指向AWS 'Configuration endpoint'将让它动态找到any/all其他节点(推荐对于 Elasticache,因为您不知道节点何时进入或离开)。
  • 'options':对于Laravel,可以在top-level、cluster-level和节点选项中指定。 (在传递给 Predis 之前,它们在 Illuminate 中合并)
  • 'parameters':这些 'override' Predis 用于新连接的默认连接 settings/assumptions。由于我们为 'default' 连接明确设置了它们,因此未使用它们。但是对于集群设置,它们是至关重要的。 'master' 节点可能发回重定向 (-MOVED),除非为 passwordscheme 设置参数,否则它将采用默认值,并且新连接到新节点将失败。

请参考[https://laravel.com/docs/5.5/redis]。

确保你有合适的库

composer require predis/predis

在config/database.php以及config/queue.php[如果你的队列也在使用集群reddis]

'redis' => [

    'client' => 'predis',
    'options' => [
        'cluster' => 'redis',
    ],
    'clusters' => [
        'default' => [
            [
                'host' => env('REDIS_HOST', 'localhost'),
                'password' => env('REDIS_PASSWORD', null),
                'port' => env('REDIS_PORT', 6379),
                'database' => 0,
            ],
        ],
    ],

]

这对我有用:

'redis' => [
    'client' => 'predis',
    'cluster' => true,
    'options' => [
        'cluster' => 'redis',
        'parameters' => [
            'host' => env('REDIS_DEFAULT_HOST', '127.0.01'),
            'password' => env('REDIS_PASSWORD', null),
            'port' => env('REDIS_DEFAULT_PORT', 6379),
            'database' => 0,
            ],
        ],
    'clusters' => [
         'default' => [
            'host' => env('REDIS_DEFAULT_HOST', '127.0.01'),
            'password' => env('REDIS_PASSWORD', null),
            'port' => env('REDIS_DEFAULT_PORT', 6379),
            'database' => 0,
        ],
        'jobs' => [
            'host' => env('REDIS_JOBS_HOST', '127.0.01'),
            'password' => env('REDIS_PASSWORD', null),
            'port' => env('REDIS_JOBS_PORT', 6379),
            'database' => 0,
        ],
        'content' => [
            'host' => env('REDIS_CONTENT_HOST', '127.0.01'),
            'password' => env('REDIS_PASSWORD', null),
            'port' => env('REDIS_CONTENT_PORT', 6379),
            'database' => 0,
        ],
        'options' => [
            'cluster' => 'redis'
        ],
    ]
]

要查看我是如何到达那里的,请查看我的回答

这个结果出现在 Google for predis+redis-cluster(没有 laravel)。我和我的团队在这个问题上苦苦挣扎了几个小时,所以我把这个答案放在这里是为了那些在尝试直接连接到 Redis 集群时发现这个页面出现错误的人:

$redis = new Predis\Client(
   ['tcp://127.0.0.1:6379'],
   ['cluster' => 'redis']
);

在应用了很多建议之后,我终于找到了使用 phpredis 而不是 Redis 的解决方案。使用以下代码:

    'redis' => [

    'client' => 'phpredis',
    'clusters' => [
        'default' => [
            [
                'host' => env('REDIS_HOST', 'localhost'),
                'password' => env('REDIS_PASSWORD', null),
                'port' => env('REDIS_PORT', 6379),
                'database' => 0,
            ],
        ],
    ]

],

使用官方文档更好地理解Laravel Redis with clusters