hyperf Error 处理 和 队列异常重试处理的问题

in LinuxPHP with 0 comment

消息队列配置

config/autoload/async_queue.php

这里可以直接参考 default 拷贝多个队列配置即可

每个新的配置都为一个新的队列,与其他队列互不干扰

注意点: 如果多个项目 并且使用同个队列名称 且同个 redis 库,会造成读取互相的数据,造成意料之外的错误
建议不同的项目使用不同的 DB

列出常用的代码

use Hyperf\AsyncQueue\Driver\DriverFactory;
use Hyperf\Utils\ApplicationContext;

...

/**
 * 获取一个实例
 * DateTime: 2021/11/30 9:34 上午
 */
public static function getDriver($driver = 'default')
{
    return ApplicationContext::getContainer()->get(DriverFactory::class)->get($driver);
}

----
use Hyperf\Logger\LoggerFactory;
use Hyperf\Utils\ApplicationContext;
use Psr\Log\LoggerInterface;

/**
 * 获取 Logger 实例
 * DateTime: 2021/12/3 9:47 上午
 * @param string $name
 * @return LoggerInterface
 */
public static function get(string $name = 'default'): LoggerInterface
{
    return ApplicationContext::getContainer()->get(LoggerFactory::class)->get($name, $name);
}

---
use Hyperf\Redis\RedisFactory;
use Hyperf\Utils\ApplicationContext;

/**
 * 获取 Logger 实例
 * DateTime: 2021/12/3 9:47 上午
 * @param string $name
 * @return LoggerInterface
 */
public static function get(string $name = 'default'): LoggerInterface
{
    return ApplicationContext::getContainer()->get(LoggerFactory::class)->get($name, $name);
}

以上为获取常用的几个实例的方式

设置异常接管

# AppExceptionHandler.php

/**
 * @var StdoutLoggerInterface
 */
protected $logger;

public function __construct(StdoutLoggerInterface $logger)
{
    $this->logger = $logger;
}

public function handle(Throwable $throwable, ResponseInterface $response)
{
    $this->logger->error(sprintf('%s[%s] in %s', $throwable->getMessage(), $throwable->getLine(), $throwable->getFile()));
    $this->logger->error($throwable->getTraceAsString());
    $this->errorToFile($throwable);
    return $response->withHeader('Server', 'Hyperf')->withStatus(500)->withBody(new SwooleStream('Internal Server Error.'));
}

public function isValid(Throwable $throwable): bool
{
    dump("---------------------");
    return true;
}

public function errorToFile(Throwable $throwable)
{
    $logger = Logger::get("error");
    $logger->error(sprintf('%s[%s] in %s', $throwable->getMessage(), $throwable->getLine(), $throwable->getFile()));
    $logger->error($throwable->getTraceAsString());
    ErrorHandle::handle($throwable);
}

投递

这里演示的结果包含 错误投递的处理 及 重试

任务

namespace App\Job;

use App\Container\Logger;
use Hyperf\AsyncQueue\Job;

class TestJob extends Job
{
protected $maxAttempts = 2;
protected $params = [];

    public function __construct(array $params)
    {
        // 这里最好是普通数据,不要使用携带 IO 的对象,比如 PDO 对象
        // 即为 不要传递 类似如 new Job() | New User() 这种实例化对象进来,这个地方如 队列的时候 会进行 serialize 序列化,不然后续读取需要解析其命名空间和实例对象 无法进行解析的
        $this->params = $params;
    }

    public function handle()
    {
        // ... 执行我们的业务逻辑
        Logger::get('error')->info('手动触发 Error' . format_now(), $this->params);
        throw new \Exception("手动触发 Error");
    }
}

结果

图片描述...

测试后发现,我们无法无法在 AppExceptionHandler 监听到我们的异常 【这个其实是正常的,他针对的是 http 的异常接管】

我们需要手动创建事件监听器,这里就不再细说,直接贴代码吧

抛出异常 和 监听 队列异常

想要监听到队列的异常,我们只需要在我们队列的业务逻辑处理中 抛出我们的 error 即可

public function handle()
{
    // ... 执行我们的业务逻辑
    Logger::get('error')->info('手动触发 Error' . format_now(), $this->params);
    // 抛出 error
    throw new \Exception("手动触发 Error");
}


# App\Listener

namespace App\Listener;

use App\Container\Logger;
use App\Services\Notify\ErrorHandle;
use Hyperf\AsyncQueue\Event\FailedHandle;
use Hyperf\Crontab\Event\FailToExecute;
use Hyperf\Event\Annotation\Listener;
use Hyperf\Event\Contract\ListenerInterface;

#[Listener]
class QueueCrontabListener implements ListenerInterface
{
public function listen(): array
{
return [
    FailedHandle::class,
];
}

    /**
     * @param FailedHandle $event
     */
    public function process(object $event)
    {
        $throwable = $event->getThrowable();
        $message = sprintf("[%s] [Queue Error] [%s]", format_now(), $throwable->getMessage());
        ErrorHandle::handle($throwable, [], $message);
    }
}

ErrorHandle::handle 为我写的全局 error 处理器

class ErrorHandle
...
public static function handle(\Throwable $throwable, array $params = [], $message = null)
{
    $exportHtml = ExceptionReport($throwable);
    Logger::get('error')->error("error handle", compact('exportHtml','params', 'message'));
    $message && dingtoo('error')->text($message);

    if(env('APP_ENV') == 'dev') { return; }
    try {
        $html = ExceptionReportHtml($exportHtml, $throwable->getTrace());
        $html = $html . json_encode($params);
        $emails = explode( ',', env('DEVELOP_EMAIL'));
        $service = new MailerService();
        $emails && array_map(function ($email) use ($service, $html){
            $service->smtpMail($email, 'Error', $html);
        }, $emails);

    }catch (\Throwable $e) {
        Logger::get('error')->error("error handle", ExceptionReport($e));
    }
}

参考地址

Responses