Laravel Horizo​​n - Redis - HAProxy - 从服务器读取行时出错

Posted

技术标签:

【中文标题】Laravel Horizo​​n - Redis - HAProxy - 从服务器读取行时出错【英文标题】:Laravel Horizon - Redis - HAProxy - Error while reading line from the server 【发布时间】:2020-10-17 09:52:30 【问题描述】:

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

另外,这是我的第一篇文章,如果我没有使用正确的频道,我深表歉意,因为我不确定我的问题是在服务器管理方面还是在 Laravel 的配置方面。

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

关于环境的一些细节

2x Apache 服务器:php 版本 7.2.29-1+ubuntu18.04.1+deb.sury.org+1 线程安全被禁用,我们使用 FPM 2x Redis 服务器使用简单的主从设置(无高可用性,无哨兵):redis 版本 4.0.9 使用 HAProxy 1.9 版进行负载平衡

laravel/框架:6.14.0 laravel/horizo​​n": 3.7.2 redis/predis: 1.1.1

地平线配置

Horizo​​n 守护进程通过 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 服务器定义了两个连接。 该应用程序将队列用于 2 种不同类型的作业:

广播、通知或某些 Artisan 命令调用等快速/短流程。 这些使用具有低超时设置的第一个连接配置。 长时间运行的进程本质上是在 Snowflake DB(基于云的 SQL,如 DB)上查询大量数据和/或在 Solr 服务器上更新/插入文档。 这些进程使用第二个连接配置,因为它们可能需要相当长的时间才能完成(通常大约 20 分钟用于从 Snowflake 读取和写入 Solr 的组合)

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

这是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' => [
    ...

最初的问题

当我们在年初上线时,我们立即遇到了在长时间运行队列连接上运行的作业的问题:

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

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

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

在浏览了 Laravel Horizo​​n 的 github 问题以及 SO 的主题上关于它的大量帖子并测试了没有运气的建议之后,我们终于发现罪魁祸首是我们的负载均衡器在 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 插件。

这在我们的没有负载平衡的预生产环境中完美运行

所以接下来我们转移到生产环境,Redis 连接问题又出乎意料地出现了,只不过这次我们正确设置了 HAProxy。

监控 Redis 中的键,我们可以看到这些作业确实被保留了,但在一段时间后最终处于延迟状态,等待作业超时后再次尝试。

这是一个真正的问题,因为我们最终会检查作业的最大尝试次数,直到它最终被标记为失败,运行它 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.

这样的日志比较多,从日期看,连接建立和关闭之间的延迟没有明显规律。

在到达那里之前,我们有:

切换到 Redis 5.0.3 版服务器:同样的问题。 从等式中删除了 HAProxy,并在客户端和 Redis 之间建立了直接连接:完美运行

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

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

【问题讨论】:

【参考方案1】:

来自Laravel documentation 最好使用 PhpRedis 客户端而不是 Predis。

Predis 已被包的原作者放弃,可能会在未来的版本中从 Laravel 中删除。

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

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

附言。我们刚刚将 .env 中的 REDIS_CLIENT 从 Predis 更改为 phpredis,一切仍然正常。

【讨论】:

感谢您的建议,我们正在迁移到 1 主 / 2 从 Sentinel 托管集群。我没有考虑迁移到 phpredis,因为它听起来不像是性能问题,这似乎是通常使用它而不是 Predis 的主要原因。假期回来后,我一定会尝试并在这里分享结果? 作者不再支持 Predis。因此,即使您没有执行问题,它也可以解决您的问题。 您好,我们已经切换到 phpredis 5.3.1 并在几周前将我们的基础架构迁移到了 1M+2S 的哨兵设置,我可以肯定地确认问题已经解决。谢谢你的建议。

以上是关于Laravel Horizo​​n - Redis - HAProxy - 从服务器读取行时出错的主要内容,如果未能解决你的问题,请参考以下文章

Laravel Horizo​​n 抛出错误:调用未定义的函数 Laravel\Horizo​​n\Console\pcntl_async_signals()

在 Windows 上安装 Laravel Horizo​​n 时出错

如何编辑 laravel 地平线视图

Laravel 水平管理器在水平:: 终止并运行伪造守护程序后不会重新启动

Laravel 5.7 - 队列作业太慢

css Horizo​​n CSS