tomaj / hermes
简单的 PHP 背景处理库
Requires
- php: >= 7.2.0
- ext-json: *
- psr/log: ^1 || ^2 || ^3
- ramsey/uuid: ^3 || ^4
- tracy/tracy: ^2.0
Requires (Dev)
- ext-redis: *
- aws/aws-sdk-php: 3.*
- pepakriz/phpstan-exception-rules: ^0.11.3
- php-amqplib/php-amqplib: ^2.6.3
- phpstan/phpstan: ^0.12.65
- phpunit/phpunit: ^8 || ^9
- predis/predis: ^1.1
- scrutinizer/ocular: ^1.6.0
- squizlabs/php_codesniffer: ~3.5
Suggests
- ext-redis: Allow use for redis driver with native redis php extension
- aws/aws-sdk-php: Allow use Amazon SQS driver
- monolog/monolog: Basic logger implements psr/logger
- php-amqplib/php-amqplib: Allow using rabbimq as driver
- predis/predis: Allow use for redis driver with php package Predis
- dev-master
- 4.2.0
- 4.1.0
- 4.0.1
- 4.0.0
- 3.1.0
- 3.0.1
- 3.0.0
- 2.2.3
- 2.2.2
- 2.2.1
- 2.2.0
- 2.1.1
- 2.1.0
- 2.0.1
- 2.0.0
- 1.3.1
- 1.3.0
- 1.2.0
- 1.1.0
- 1.0.0
- 0.4.0
- 0.3.0
- 0.2.0
- 0.1.0
- dev-split-redis-driver
- dev-rename-restart-to-shutdown
- dev-priorities
- dev-phpstan
- dev-giitlab-workflow
- dev-soft-restart
- dev-2.0.0-dev
- dev-sqs
- dev-redis_proxy
- dev-restart
This package is auto-updated.
Last update: 2024-08-29 13:04:25 UTC
README
后台任务处理 PHP 库
什么是 Hermes?
如果您需要在您的 web 应用程序外部处理一些任务,您可以使用 Hermes。Hermes 为从 HTTP 线程向离线处理作业发送消息提供消息代理。推荐用于发送电子邮件、调用其他 API 或其他耗时操作。
Hermes 的另一个目标是可变,可以使用各种消息代理,如 Redis、rabbit、数据库,并能够轻松为其他消息解决方案创建新驱动程序。还可以简单地创建工作者以在指定事件上执行任务。
安装
此库需要 PHP 7.2 或更高版本。
推荐的安装方法是使用 Composer
$ composer require tomaj/hermes
该库符合 PSR-1、PSR-2、PSR-3 和 PSR-4。
可选依赖
Hermes 能够使用与 psr/log
接口兼容的记录器记录活动。有关更多信息,请参阅 psr/log。
该库无需记录器即可运行,但维护者建议安装 monolog 以进行记录。
支持驱动程序
目前 Hermes 库附带 3 个驱动程序和一个独立包中的一个驱动程序
- Redis 驱动程序(两种不同的实现 phpredis 或 Predis)
- Amazon SQS 驱动程序
- RabbitMQ 驱动程序
- ZeroMQ 驱动程序(通过 php-zmq 扩展)作为 tomaj/hermes-zmq-driver 提供
注意:您必须安装所有第三方库以初始化到这些驱动程序的连接。例如,您必须将 nrk/predis
添加到您的 composer.json 并创建到您的 Redis 实例的连接。
概念 - Hermes 如何工作?
Hermes 作为事件从您的 PHP 请求在 web 服务器上到 CLI 上特定处理器的发射器和调度器工作。基本上像这样
--> HTTP request to /file.php -> emit(Message) -> Hermes Emitter
\
Queue (Redis, rabbit etc.)
/
--> running PHP CLI file waiting for new Message-s from Queue
when received a new message it calls registered handler to process it.
您必须在您的应用程序中实现以下四个步骤
- 选择您想使用的驱动程序并将其注册到调度器和发射器
- 在您需要后台处理某些内容时发出事件
- 编写一个处理程序类以处理第 2 步中的消息
- 创建一个将在您的服务器上“永远运行”的 PHP 文件并在此处运行调度器
如何使用
此简单示例演示了使用 Redis 驱动程序,并展示了如何在后台发送电子邮件的示例。
发出事件
发射消息(在应用程序的任何位置,简单快捷)。
use Redis; use Tomaj\Hermes\Message; use Tomaj\Hermes\Emitter; use Tomaj\Hermes\Driver\RedisSetDriver; $redis = new Redis(); $redis->connect('127.0.0.1', 6379); $driver = new RedisSetDriver($redis); $emitter = new Emitter($driver); $message = new Message('send-email', [ 'to' => 'test@test.com', 'subject' => 'Testing hermes email', 'message' => 'Hello from hermes!' ]); $emitter->emit($message);
处理事件
为了处理事件,我们需要创建一些将在CLI中运行的PHP文件。我们可以实现一个简单的实现并注册这个简单的处理器。
# file handler.php use Redis; use Tomaj\Hermes\Driver\RedisSetDriver; use Tomaj\Hermes\Dispatcher; use Tomaj\Hermes\Handler\HandlerInterface; class SendEmailHandler implements HandlerInterface { // here you will receive message that was emitted from web application public function handle(MessageInterface $message) { $payload = $message->getPayload(); mail($payload['to'], $payload['subject'], $payload['message']); return true; } } // create dispatcher like in the first snippet $redis = new Redis(); $redis->connect('127.0.0.1', 6379); $driver = new RedisSetDriver($redis); $dispatcher = new Dispatcher($driver); // register handler for event $dispatcher->registerHandler('send-email', new SendEmailHandler()); // at this point this script will wait for new message $dispatcher->handle();
要在服务器上运行 handler.php,您可以使用像 upstart、supervisord、monit、god 或任何其他替代工具。
日志记录
Hermes可以使用任何psr/log 日志记录器。您可以为分发器或发射器设置日志记录器,查看发送到分发器或发射器的消息类型以及处理器处理消息的时间。如果您将 Psr\Log\LoggerAwareTrait
(或实现 Psr\Log\LoggerAwareInterface
)特质添加到处理器中,您也可以在处理器中使用日志记录器(分发器和发射器会自动注入)。
使用 monolog 的基本示例
use Monolog\Logger; use Monolog\Handler\StreamHandler; // create a log channel $log = new Logger('hermes'); $log->pushHandler(new StreamHandler('hermes.log')); // $driver = .... $dispatcher = new Dispatcher($driver, $log);
如果想在处理器中记录一些信息
use Redis; use Tomaj\Hermes\Driver\RedisSetDriver; use Tomaj\Hermes\Dispatcher; use Tomaj\Hermes\Handler\HandlerInterface; use Psr\Log\LoggerAwareTrait; class SendEmailHandlerWithLogger implements HandlerInterface { // enable logger use LoggerAwareTrait; public function handle(MessageInterface $message) { $payload = $message->getPayload(); // log info message $this->logger->info("Trying to send email to {$payload['to']}"); mail($payload['to'], $payload['subject'], $payload['message']); return true; } }
重试
如果您需要重试,当它们因某些原因失败时,可以在处理器中添加 RetryTrait
。如果您想,您可以覆盖此特质中的 maxRetry()
方法来指定Hermes将尝试运行handle()多少次。注意:如果您想使用重试,必须使用支持延迟执行的驱动程序($executeAt
消息参数)
declare(strict_types=1); namespace Tomaj\Hermes\Handler; use Tomaj\Hermes\MessageInterface; class EchoHandler implements HandlerInterface { use RetryTrait; public function handle(MessageInterface $message): bool { throw new \Exception('this will always fail'); } // optional - default is 25 public function maxRetry(): int { return 10; } }
优先级
您可以使用不同的优先级声明多个队列,并确保高优先级队列中的消息首先被处理。
使用Redis驱动的示例
use Tomaj\Hermes\Driver\RedisSetDriver; use Tomaj\Hermes\Emitter; use Tomaj\Hermes\Message; use Tomaj\Hermes\Dispatcher; $redis = new Redis(); $redis->connect('127.0.0.1', 6379); $driver = new RedisSetDriver($redis); $driver->setupPriorityQueue('hermes_low', Dispatcher::DEFAULT_PRIORITY - 10); $driver->setupPriorityQueue('hermes_high', Dispatcher::DEFAULT_PRIORITY + 10); $emitter = new Emitter($driver); $emitter->emit(new Message('type1', ['a' => 'b'], Dispatcher::DEFAULT_PRIORITY - 10)); $emitter->emit(new Message('type1', ['c' => 'd'], Dispatcher::DEFAULT_PRIORITY + 10));
一些细节
- 您可以使用来自
Dispatcher
类的优先级常量,但您也可以使用任何数字 - 高优先级队列的消息将被首先处理
- 在
Dispatcher::handle()
方法中,您可以提供一个队列名称数组,创建一个只处理一个或多个选定队列的工作进程
优雅关闭
Hermes工作进程可以优雅地停止。
如果初始化 Dispatcher
时提供了 Tomaj\Hermes\Shutdoown\ShutdownInteface
的实现,Hermes将在处理每个消息后检查 ShutdwnInterface::shouldShutdown()
。如果它返回 true
,Hermes将关闭(请注意,记录了通知)。
警告:此库不提供重启功能,应由您使用的进程控制器处理以保持Hermes运行(例如,launchd、daemontools、supervisord等)。
目前实现了两种方法。
共享文件关闭
通过触摸预定义文件来启动关闭。
$shutdownFile = '/tmp/hermes_shutdown'; $shutdown = Tomaj\Hermes\Shutdown\SharedFileShutdown($shutdownFile); // $log = ... // $driver = .... $dispatcher = new Dispatcher($driver, $log, $shutdown); // ... // shutdown can be triggered be calling `ShutdownInterface::shutdown()` $shutdown->shutdown();
Redis关闭
通过将时间戳存储到Redis的预定义关闭键来启动关闭。
$redisClient = new Predis\Client(); $redisShutdownKey = 'hermes_shutdown'; // can be omitted; default value is `hermes_shutdown` $shutdown = Tomaj\Hermes\Shutdown\RedisShutdown($redisClient, $redisShutdownKey); // $log = ... // $driver = .... $dispatcher = new Dispatcher($driver, $log, $shutdown); // ... // shutdown can be triggered be calling `ShutdownInteface::shutdown()` $shutdown->shutdown();
扩展Hermes
如果您需要处理许多消息,您可以非常快速地扩展您的Hermes工作进程。您只需运行多个处理器的实例 - 将注册处理器到分发器并运行 $dispatcher->handle()
的CLI文件。您也可以将您的源代码放置在多台机器上,并将其扩展到您想要的节点数。但这将有助于您拥有支持这两件事的驱动程序
- 驱动程序需要能够在网络上工作
- 一条消息必须只能被一个工作进程接收
如果您确保这一点,Hermes将正常工作。Rabbit驱动程序或Redis驱动程序可以处理这些问题,并且这些产品也是为处理大量负载而设计的。
扩展Hermes
Hermes是用相互依赖的接口编写的独立类。您可以轻松更改类的实现。例如,您可以创建一个新的驱动程序,使用另一个日志记录器。或者,如果您真的想,您还可以创建自己的消息格式,该格式将通过您的自定义序列化器序列化并发送到驱动程序。
如何编写您的驱动程序
每个驱动程序都必须实现具有2个方法(send和wait)的Tomaj\Hermes\Driver\DriverInterface
。一个简单的驱动程序将使用Gearman作为驱动程序。
namespace My\Custom\Driver; use Tomaj\Hermes\Driver\DriverInterface; use Tomaj\Hermes\Message; use Closure; class GearmanDriver implements DriverInterface { private $client; private $worker; private $channel; private $serializer; public function __construct(GearmanClient $client, GearmanWorker $worker, $channel = 'hermes') { $this->client = $client; $this->worker = $worker; $this->channel = $channel; $this->serializer = $serialier; } public function send(Message $message) { $this->client->do($this->channel, $this->serializer->serialize($message)); } public function wait(Closure $callback) { $worker->addFunction($this->channel, function ($gearmanMessage) use ($callback) { $message = $this->serializer->unserialize($gearmanMessage); $callback($message); }); while ($this->worker->work()); } }
如何编写自己的序列化器
如果你想在驱动程序中使用自己的序列化器,你必须创建一个新的类来实现Tomaj\Hermes\MessageSerializer
,并且需要一个支持它的驱动程序。你可以在你的驱动程序中添加Tomaj\Hermes\Driver\SerializerAwareTrait
特质,这将向你的驱动程序添加setSerializer
方法。
简单的序列化器,它将使用库jms/serializer
namespace My\Custom\Serializer; use Tomaj\Hermes\SerializerInterface; use Tomaj\Hermes\MessageInterface; class JmsSerializer implements SerializerInterface { public function serialize(MessageInterface $message) { $serializer = JMS\Serializer\SerializerBuilder::create()->build(); return $serializer->serialize($message, 'json'); } public function unserialize($string) { $serializer = JMS\Serializer\SerializerBuilder::create()->build(); return $serializer->deserialize($message, 'json'); } }
计划执行
从版本2.0开始,你可以将一个未来的时间戳作为第四个参数添加到消息中。这个消息将在该时间之后被处理。目前这个功能在RedisSetDriver和PredisSetDriver中得到支持。
升级
从v3到v4
- 将“重启”重命名为“关闭”
- 命名变更以反映Hermes的功能。它可以优雅地停止自己的进程,但Hermes的重启(重新启动)必须由外部进程/库来处理。因此这被称为关闭而不是重启。
- 将RestartInterface重命名为ShutdownInterface
- 以及所有实现都更改了命名空间名称和类名称
变更日志
请参阅CHANGELOG以获取有关最近更改的更多信息。
测试
$ composer test
贡献
请参阅CONTRIBUTING和CONDUCT以获取详细信息。
安全
如果你发现任何与安全相关的问题,请通过电子邮件tomasmajer@gmail.com联系,而不是使用问题跟踪器。
许可协议
MIT许可(MIT)。有关更多信息,请参阅许可文件。