laravel Job任务中调用Redis发生read error on connection的错误

问题描述

在docker的php-apache容器中运行laravel的job任务中调用redis发生read error on connection的错误。

php artisan queue:work # 运行job
# 任务中调用redis发生错误
# Job 调用Redis的代码
Redis::set('test', 1); // 这句能set到值到redis,但是接着就发生read error on connection的错误了
Redis::expire('test',  2 * 24 * 60 * 60); // 前面报错结束任务了,不会执行该代码

报错信息

报错信息

个人尝试

  • 参考的一些解决方案,但也无法解决
    • 设置php.ini,default_socket_timeout = -1
    • 切换Redis Client,phpredis 更换为 predis
    • 配置database.php,增加timeout=0参数

请问各位大佬,这个问题该如何解决

《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
最佳答案

后来在控制器中调用Redis也出现这种问题了,之前的解决办法总不能一直这样用,尝试了重装容器环境,从而解决了。

php:apache-buster换成php:fpm得以解决

2个月前 评论
讨论数量: 21

说明连不到redis。看redis是否连接正常,改配置了,队列要重新载入不然还是老代码

4个月前 评论
AtmosphereMao (楼主) 4个月前

测了一下,在控制器上调用redis是没问题的,但是在php artisan queue:work的job里运行调用redis就会出现这个问题

4个月前 评论

redis 链接是单例 时间太久没有操作 链接断开了

4个月前 评论

试试把 Redis 容器端口暴露给宿主机

配置 redis:6379 改为宿主机的IP:6397.

比如我本地的容器配置:

// other config
// docker-composer.yml config.
  local-redis:
    image: redis:latest
    restart: always
    ports:
      - "6379:6379"
// other config

运行之后,查下本地的IP:

Laravel

然后修改 .env 文件

# Other config
REDIS_HOST = 192.168.50.50
REDIS_PORT = 6379
REDIS_PASSWORD =
REDIS_SELECT_DB = 0
# Other config
4个月前 评论
AtmosphereMao (楼主) 4个月前

file
可以用eval方式,把set和expire合并成一条请求去执行

或者使用setex命令,set时,一起代入过期时间

4个月前 评论
AtmosphereMao (楼主) 4个月前

大胆猜测一下,大概率可能是 Redis 连接超时,然后使用已经断开的连接。

Redis read error on connection

  • php 下的两种 redis 连接方案
    • predis 基于 PHP 原生,直接 Composer 即可
    • phpredis 基于 C 扩展,可用 PECL 安装
  • Laravel Redis Client
    • 看上面你说一开始用的 phpredis,报错
    • 后来用 predis 也报错
    • :thought_balloon: 首先先 check redis 服务端是否可以正常工作没有重启之类的问题 :question:
    • :thought_balloon: 另外看一下是偶发性,还是必然会运行一段时间就报错 :question:
  • 关于配置超时时间
    • predis by php configure
      • timeout Redis 连接超时时间,默认为5s, 这个应该不会,否则你一开始 set 命令就没成功
      • read_write_timeout Redis 执行读取或写入操作时使用的超时时间,可以设置 60s 试试看
    • phpredis by c configure
      • timeout Redis 连接超时时间 (秒),默认值为 0 表示它将使用 default_socket_timeout
      • read_timeout (Redis::OPT_READ_TIMEOUT)读取超时时间(秒),默认值为 0 表示它将使用 default_socket_timeout
    • php 层面配置
      • ini_set('default_socket_timeout', -1) 默认的套接字超时时间设置为无限大
      • :thought_balloon: 看上面你已经做了这一步 :question:
      • :thought_balloon: 看你 redis 的 timeout 配置 0, 应该限制的是连接时是否超时 :question:
  • 业务逻辑层
    • Laravel 的 队列任务 Work 执行 队列进程
    • 大概可能是队列内执行任务比较耗时?
    • 另外是否可以将两个命令合二为一,因为看你原因说的是后一句执行失败,但是 set 和 expire 是否可以变成 Redis::setex('test', 2 * 24 * 60 * 60, 1); (只是一个建议,如果业务层不支持,那就再看超时原因)
  • 关于长连接与短连接
    • 长连接
      • 是否要在整个脚本周期内进行心跳检测?
      • Laravel Work 多进程执行,也就是每个 JOB 进程维护一个 Redis 连接
    • 短连接
      • 每次使用再连接,使用后主动释放?但往往我们都不会主动释放,如 PHP-FPM下,他自己释放,但是你的环境是 CLI
    • Laravel的封装 Illuminate\Support\Facades\Redis::set()
      • 门面模式,
  • 判断连接是否可用
    • predis
      • $client->isConnected() 判断是否连接
    • phpredis -
  • strace 深入网络层
    • strace 查看当前网络包的情况 strace -p pid -e trace=network
  • 继续深入
    • Redis 线上什么配置,是否主从架构,或者多节点,因为涉及到命令的传播耗时
    • 是不是 Redis 数据写入命令被其他阻塞之类的
4个月前 评论
AtmosphereMao (楼主) 4个月前
Tacks (作者) 4个月前
  • Test Code
// 【1】两个不同 Redis 短连接,需要的时候执行获取连接Redis对象,在进行 set() ,虽然粗暴但一定程度有效
$redis1  = new \Predis\Client($config);
$redis2  = new \Predis\Client($config);
var_dump($redis1 === $redis2);// false



// 【2】 Laravel Client predis 下 服务容器的单例模式相等的对象
$redis1  = \Illuminate\Support\Facades\Redis::connection();
$redis2  = \Illuminate\Support\Facades\Redis::connection();
var_dump($redis1 === $redis2);// true

// 【3】 Laravel Client predis 下 服务容器的对象没有释放?
$redis1  = \Illuminate\Support\Facades\Redis::connection();
$redis1->disconnect(); // 释放无用?
$redis2  = \Illuminate\Support\Facades\Redis::connection();
var_dump($redis1 === $redis2);// true


// 【4】predis 下
// 'client' => 'predis',
// 'read_write_timeout' => 1,
// 'timeout' => 1,
$redis1  = \Illuminate\Support\Facades\Redis::connection();
$redis1->set('test', 1);
var_dump($redis1->getConnection()); // 可以得到
$redis1->quit();
sleep(5);
var_dump($redis1->getConnection()); // 可以得到
var_dump($redis1->isConnected()); // false

$redis2  = \Illuminate\Support\Facades\Redis::connection();
var_dump($redis2->get('test')); // 可以得到
var_dump($redis1->get('test')); // 可以得到
sleep(5);

var_dump($redis1 === $redis2); // true 

// 离大谱的是,我看网络,确实有两个 socket 连接,不知道为啥两次的获取还是同一个对象,并且无法重新获取新的 redis 对象
$ netstat -anp
tcp        0      0 10.0.0.4:49350          redis:6379       TIME_WAIT   -
tcp        0      0 10.0.0.4:49352          redis:6379       ESTABLISHED 220/php


// 【5】phpredis 下
// 'client' => 'phpredis',
// 'read_timeout' => 1,
// 'timeout' => 1,
// phpredis 下
$redis1  = \Illuminate\Support\Facades\Redis::connection();
$redis1->set('test', 1);
var_dump($redis1->get('test')); // 可以得到
sleep(10);
var_dump($redis1->get('test')); // 可以得到6】phpredis 下
ini_set('default_socket_timeout', 1);
// phpredis 下
$redis1  = \Illuminate\Support\Facades\Redis::connection();
$redis1->set('test', 1);
var_dump(spl_object_hash($redis1)); // 0000000067e5e204000000004e2d5856
var_dump($redis1->get('test')); // 可以得到 1
sleep(10);
$redis1->disconnect(); // 主动释放

// ==================================================
// !!! 复现问题   Redis server redis:6379 went away
// !!! 复现问题   read error on connection to redis:6379
// var_dump($redis1->get('test')); // NULL
// var_dump(\Illuminate\Support\Facades\Redis::get('test')); // NULL7】phpredis 下
ini_set('default_socket_timeout', 1);
// phpredis 下
$redis1  = \Illuminate\Support\Facades\Redis::connection();
$redis1->set('test', 1);
var_dump(spl_object_hash($redis1)); // 00000000034a27e4000000005142fe99
var_dump($redis1->get('test')); // 可以得到 1
sleep(10); // 没有主动断开,看看是否会超时,不会超时


// 重新连接 
$redis2  = \Illuminate\Support\Facades\Redis::connection();
var_dump($redis2->get('test')); // 1
var_dump(spl_object_hash($redis2)); // 00000000034a27e4000000005142fe99
var_dump(\Illuminate\Support\Facades\Redis::get('test')); // 18】phpredis 下
 // phpredis 下
$redis1  = \Illuminate\Support\Facades\Redis::connection();
$redis1->set('test', 1);
var_dump(spl_object_hash($redis1)); // 00000000034a27e4000000005142fe99
var_dump($redis1->get('test')); // 可以得到 1
sleep(10); // 没有主动断开,看看是否会超时


// 偶发 read error on connection
// 重新连接 
$redis2  = \Illuminate\Support\Facades\Redis::connection();
var_dump($redis2->get('test')); // 1
var_dump(spl_object_hash($redis2)); // 00000000034a27e4000000005142fe99
var_dump(\Illuminate\Support\Facades\Redis::get('test')); // 1
4个月前 评论

file

  1. set的报错,并不是expire
  2. 一直改超时时间,难道默认 60s 还不够处理逻辑?

看着像: 所有队列都是用同个 redis 链接。

但是正常情况,每个队列都是用新的 redis 链接吧,很不对劲。


你可以用最基础代码测试,别添加业务逻辑。
如果还出错,把队列驱动 换成 数据库队列。

4个月前 评论
Tacks 4个月前
AtmosphereMao (楼主) 4个月前

使用laravel的redis会发生错误,目前能跑通的解决办法:

    public function handle(): void
    {
        $redis = init_redis(); // Predis实例化
        $redis->set('test', 1); //这样后续的redis操作都不会报错
   }

// 如果在__construct里实例化Predis也会遇到这个read connection error
    public function __construct()
    {
        $this->redis = init_redis();
    }

   public function handle(): void
    {
        $this->redis->set('test', 1); //这样还是会遇到read connection error
   }
4个月前 评论
33qis 4个月前
AtmosphereMao (作者) (楼主) 4个月前
33qis 4个月前

你是不是在JOB的__construct做操作了,
我之前碰到过类似问题,我在JOB的construct里会生成一个唯一id,在本地没问题在测试环境总是不行。后来找到原因是本地是同步消费job,测试是redis消费job。异步的时候job会序列化,然后再反序列化出来。
可以直接在handle连接缓冲

4个月前 评论

后来在控制器中调用Redis也出现这种问题了,之前的解决办法总不能一直这样用,尝试了重装容器环境,从而解决了。

php:apache-buster换成php:fpm得以解决

2个月前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!