Laravel Horizon - Redis - HAProxy - 从服务器读取行时出错
Laravel Horizon - Redis - HAProxy - Error while reading line from the server
抱歉,这个标题听起来像是一个“已经回答完毕”的话题,但我相信我的情况是独一无二的。
此外,这是我的第一个 post,所以如果我不在正确的频道上,我深表歉意,因为我不确定我的问题是在服务器管理方面还是在 Laravel 方面配置一.
我正在尝试获得一些关于如何解决 Horizon / Predis / HAProxy 问题的新想法,我认为它已修复但又出现了。
关于环境的一些细节
- 2x Apache 服务器:PHP 版本 7.2.29-1+ubuntu18.04.1+deb.sury.org+1
- 线程安全被禁用,我们使用 FPM
- 2x Redis 服务器使用简单的 master-slave 设置(没有高可用性,没有哨兵):redis 版本 4.0.9
- 使用 HAProxy 版本 1.9 进行负载平衡
图书馆
- laravel/framework: 6.14.0
- laravel/horizon": 3.7.2
- redis/predis: 1.1.1
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 种不同类型的作业:
- 快速/短流程,如广播、通知或某些 Artisan 命令调用。
这些使用具有低超时设置的第一个连接配置。
- 长 运行ning 进程,本质上是在 Snowflake 数据库(基于云的 SQL 类似数据库)上查询大量数据和/或在 Solr 服务器上更新/插入文档。
这些进程使用第二个连接配置,因为它们可能需要相当长的时间才能完成(对于结合从 Snowflake 读取和写入 Solr 的进程,通常需要大约 20 分钟)
该应用程序是一个商业 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.
有更多这样的日志,从日期可以看出,连接建立和连接关闭之间的延迟没有明显的模式。
在到达那里之前我们有:
- 切换到 Redis 版本 5.0.3 服务器:同样的问题。
- 从等式中删除了 HAProxy 并建立了 d客户端和 Redis 之间的 rect 连接:工作完美.
对于如何找出并彻底解决问题,我有点不知所措。
回到关于 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 + Horizon -> HAProxy-> Redis Server。我们有 3 个 redis 服务器(1 个主服务器,2 个从服务器)。和哨兵保持实际主人。
在我们从 Predis 迁移到 PhpRedis 之前,redis 也有类似的问题。研究问题时,最好的答案是使用 PhpRedis。
PS。我们刚刚将 .env 中的 REDIS_CLIENT 从 Predis 更改为 phpredis,并且一切正常。
抱歉,这个标题听起来像是一个“已经回答完毕”的话题,但我相信我的情况是独一无二的。
此外,这是我的第一个 post,所以如果我不在正确的频道上,我深表歉意,因为我不确定我的问题是在服务器管理方面还是在 Laravel 方面配置一.
我正在尝试获得一些关于如何解决 Horizon / Predis / HAProxy 问题的新想法,我认为它已修复但又出现了。
关于环境的一些细节
- 2x Apache 服务器:PHP 版本 7.2.29-1+ubuntu18.04.1+deb.sury.org+1
- 线程安全被禁用,我们使用 FPM
- 2x Redis 服务器使用简单的 master-slave 设置(没有高可用性,没有哨兵):redis 版本 4.0.9
- 使用 HAProxy 版本 1.9 进行负载平衡
图书馆
- laravel/framework: 6.14.0
- laravel/horizon": 3.7.2
- redis/predis: 1.1.1
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 种不同类型的作业:
- 快速/短流程,如广播、通知或某些 Artisan 命令调用。 这些使用具有低超时设置的第一个连接配置。
- 长 运行ning 进程,本质上是在 Snowflake 数据库(基于云的 SQL 类似数据库)上查询大量数据和/或在 Solr 服务器上更新/插入文档。 这些进程使用第二个连接配置,因为它们可能需要相当长的时间才能完成(对于结合从 Snowflake 读取和写入 Solr 的进程,通常需要大约 20 分钟)
该应用程序是一个商业 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.
有更多这样的日志,从日期可以看出,连接建立和连接关闭之间的延迟没有明显的模式。
在到达那里之前我们有:
- 切换到 Redis 版本 5.0.3 服务器:同样的问题。
- 从等式中删除了 HAProxy 并建立了 d客户端和 Redis 之间的 rect 连接:工作完美.
对于如何找出并彻底解决问题,我有点不知所措。 回到关于 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 + Horizon -> HAProxy-> Redis Server。我们有 3 个 redis 服务器(1 个主服务器,2 个从服务器)。和哨兵保持实际主人。 在我们从 Predis 迁移到 PhpRedis 之前,redis 也有类似的问题。研究问题时,最好的答案是使用 PhpRedis。
PS。我们刚刚将 .env 中的 REDIS_CLIENT 从 Predis 更改为 phpredis,并且一切正常。