Laravel Horizon - Redis - HAProxy - 从服务器读取行时出错
Posted
技术标签:
【中文标题】Laravel Horizon - Redis - HAProxy - 从服务器读取行时出错【英文标题】:Laravel Horizon - Redis - HAProxy - Error while reading line from the server 【发布时间】:2020-10-17 09:52:30 【问题描述】:很抱歉标题听起来像是“已回答”的话题,但我相信我的情况是独一无二的。
另外,这是我的第一篇文章,如果我没有使用正确的频道,我深表歉意,因为我不确定我的问题是在服务器管理方面还是在 Laravel 的配置方面。
我正在尝试获得一些关于如何解决 Horizon / 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/horizon": 3.7.2 redis/predis: 1.1.1地平线配置
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 服务器定义了两个连接。 该应用程序将队列用于 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 Horizon 的 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 + Horizon -> 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 Horizon - Redis - HAProxy - 从服务器读取行时出错的主要内容,如果未能解决你的问题,请参考以下文章
Laravel Horizon 抛出错误:调用未定义的函数 Laravel\Horizon\Console\pcntl_async_signals()
在 Windows 上安装 Laravel Horizon 时出错