Laravel Horizon - Redis - HAProxy - 从服务器读取行时出错

Laravel Horizon - Redis - HAProxy - Error while reading line from the server

抱歉,这个标题听起来像是一个“已经回答完毕”的话题,但我相信我的情况是独一无二的。

此外,这是我的第一个 post,所以如果我不在正确的频道上,我深表歉意,因为我不确定我的问题是在服务器管理方面还是在 Laravel 方面配置一.

我正在尝试获得一些关于如何解决 Horizon / Predis / HAProxy 问题的新想法,我认为它已修复但又出现了。

关于环境的一些细节

图书馆

Horizon配置

Horizon 守护进程是通过 Supervisor 管理的。

这是 config/database.php 中的 Redis 客户端配置:

'redis' => [

    'client' => 'predis',

    'options' => [
        'prefix' => strtoupper(env('APP_NAME') . ':')
    ],

    'default' => [
        'host' => env('REDIS_HOST'),
        'password' => env('REDIS_PASSWORD'),
        'port' => env('REDIS_PORT'),
        'database' => env('REDIS_DB'),
        'read_write_timeout' => -1
    ],
    ...

这是 config/queue.php 中的 Redis 连接配置:

    'redis' => [
        'driver' => 'redis',
        'connection' => 'default',
        'queue' => env('REDIS_QUEUE', 'default'),
        'retry_after' => 110
    ],

    'redis-long-run' => [
        'driver' => 'redis',
        'connection' => 'default',
        'queue' => env('REDIS_QUEUE', 'long-running-queue'),
        'retry_after' => 3620
    ],

如您所见,为同一个物理 Redis 服务器定义了两个连接。 该应用程序将 queues 用于 2 种不同类型的作业:

该应用程序是一个商业 Web 应用程序,供我公司私人使用,负载相当小(每天大约 200 个作业排队),但 运行ning 长流程对业务至关重要:作业失败或双 运行 是不可接受的。

这是 config/horizon.php 文件:

'environments' => [
    'production' => [
        'supervisor-default' => [
            'connection' => 'redis',
            'queue' => ['live-rules', 'solr-cmd', 'default'],
            'balance' => 'simple',
            'processes' => 3,
            // must be lower than /config/queue.php > 'connections.redis'
            'timeout' => 90,
            'tries' => 3,
        ],
        'supervisor-long-run' => [
            'connection' => 'redis-long-run',
            'queue' => ['long-running-queue', 'solr-sync'],
            'balance' => 'simple',
            'processes' => 5,
            // must be lower than /config/queue.php > 'connections.redis-long-run'
            'timeout' => 3600,
            'tries' => 10,
        ],
    ],

    'staging' => [
    ...

初始问题<已解决>

当我们在年初上线时,我们立即在 long-running-queue 连接上遇到作业 运行ning 的问题:

Error while reading line from the server. [tcp://redis_host:6379] 错误开始左右弹出。

这些转化为作业被困在挂起状态,直到它们最终被标记为失败,尽管任务实际上已经成功。

当时应用程序的 运行ning 进程仅限于 Snowflake SELECT 查询。

在 Laravel Horizon 的 github 问题以及 SO 的主题上进行了大量 post 的讨论并测试了没有运气的建议 我们终于发现罪魁祸首是我们的负载均衡器在 90 秒后关闭了连接。

Redis 的 tcp-keepalive 默认配置参数为 300 秒,因此我们调整了 HAProxy 的配置以在 310 秒时关闭 - 噗! -,一段时间内一切正常。

这是 HAProxy 现在应用程序的配置:

listen PROD-redis
    bind                    0.0.0.0:6379
    mode                    tcp
    option                  tcplog
    option                  tcp-check
    balance                 leastconn
    timeout connect         10s
    timeout client          310s
    timeout server          310s
    server 1        192.168.12.34:6379      check inter 5s rise 2 fall 3
    server 2        192.168.43.21:6379      check inter 5s rise 2 fall 3 backup

新问题(初始重生?)

几个月后回来,应用程序已经发展,我们现在有一个工作,它从 Snowflake 批量读取和产生以构建 Solr 更新查询。 Solr 客户端是 solarium/solarium,我们使用 addBuffered 插件。

这在我们没有负载平衡的 pre-production 环境中完美运行

所以接下来我们转移到生产环境并且 Redis 连接问题意外地再次出现,除了这次我们已经正确设置了 HAProxy。

监控Redis中的key可以看到,这些job确实是被reserved了,但是过了一段时间后就进入了delayed状态,等待job超时后再次尝试。

这是一个真正的问题,因为我们最终会检查作业的最大尝试次数,直到它最终被标记为失败,运行将其执行 x 次,因为它从未获得 complete 标志,对环境造成不必要的压力并消耗资源,而实际上这项工作在第一次尝试时就成功了。

这是我们从 HAProxy 的日志中得到的:

Jun 26 11:35:43 apache_host haproxy[215280]: 127.0.0.1:42660 [26/Jun/2020:11:29:02.454] PROD-redis PROD-redis/redis_host 1/0/401323 61 cD 27/16/15/15/0 0/0

Jun 26 11:37:18 apache_host haproxy[215280]: 127.0.0.1:54352 [26/Jun/2020:11:28:23.409] PROD-redis PROD-redis/redis_host 1/0/535191 3875 cD 24/15/14/14/0 0/0

cD 部分是有趣的信息,根据 haProxy's documentation:

c : the client-side timeout expired while waiting for the client to send or receive data.

D : the session was in the DATA phase.

有更多这样的日志,从日期可以看出,连接建立和连接关闭之间的延迟没有明显的模式。

在到达那里之前我们有:

对于如何找出并彻底解决问题,我有点不知所措。 回到关于 client-side 超时的 HAProxy 日志,我想知道客户端配置可能有什么问题以及我接下来应该尝试什么。

也许这里有人会提出建议?感谢阅读。

Laravel documentation开始,最好使用PhpRedis客户端而不是Predis。

Predis has been abandoned by the package's original author and may be removed from Laravel in a future release.

简而言之,PhpRedis 是一个用 C 编写的 php 模块。而 Predis 是用 PHP 编写的 php 库。巨大的性能差异描述 here

顺便说一句,我们有类似的堆栈:Laravel + Horizo​​n -> HAProxy-> Redis Server。我们有 3 个 redis 服务器(1 个主服务器,2 个从服务器)。和哨兵保持实际主人。 在我们从 Predis 迁移到 PhpRedis 之前,redis 也有类似的问题。研究问题时,最好的答案是使用 PhpRedis。

PS。我们刚刚将 .env 中的 REDIS_CLIENT 从 Predis 更改为 phpredis,并且一切正常。