گام 18: پیش به سوی ناهمزمانی (Async)
پیش به سوی ناهمزمانی (Async)¶
بررسی هرز بودن محتوا در هنگام ارسال فرم ممکن است موجب مشکلاتی شود. اگر API مربوط به Akismet کند شود، وبسایت ما نیز برای کاربران کند میشود. اما بدتر از آن، اگر مهلت (timeout) پایان یابد یا API مربوط به Akismet در دسترس نباشد، ممکن است ما کامنتها را از دست بدهیم.
به صورت ایدهآل ما باید دادههای ارسالی را بدون انتشار آن ذخیره کنیم و به سرعت پاسخ را برگردانیم. سپس بررسی هرز بودن محتوا میتواند به صورت مجزا (out of band) صورت پذیرد.
پرچمگذاری کامنتها¶
ما نیاز داریم که یک state
برای کامنتها معرفی کنیم: submitted
، spam
و published
.
ویژگی state
را به کلاس Comment
اضافه کنید:
1 | $ symfony console make:entity Comment
|
migration پایگاهداده را ایجاد کنید:
1 | $ symfony console make:migration
|
migration را اصلاح کنید تا تمام کامنتهای موجود را با مقدار پیشفرض published
بهروزرسانی نماید:
1 2 3 4 5 6 7 8 9 10 11 12 13 | --- a/migrations/Version00000000000000.php
+++ b/migrations/Version00000000000000.php
@@ -20,7 +20,9 @@ final class Version20200714155905 extends AbstractMigration
public function up(Schema $schema) : void
{
// this up() migration is auto-generated, please modify it to your needs
- $this->addSql('ALTER TABLE comment ADD state VARCHAR(255) NOT NULL');
+ $this->addSql('ALTER TABLE comment ADD state VARCHAR(255)');
+ $this->addSql("UPDATE comment SET state='published'");
+ $this->addSql('ALTER TABLE comment ALTER COLUMN state SET NOT NULL');
}
public function down(Schema $schema) : void
|
پایگاهداده را Migrate کنید:
1 | $ symfony console doctrine:migrations:migrate
|
همچنین باید اطمینان یابیم که به صورت پیشفرض، state
با مقدار submitted
تنظیم شده است:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | --- a/src/Entity/Comment.php
+++ b/src/Entity/Comment.php
@@ -55,9 +55,9 @@ class Comment
private $photoFilename;
/**
- * @ORM\Column(type="string", length=255)
+ * @ORM\Column(type="string", length=255, options={"default": "submitted"})
*/
- private $state;
+ private $state = 'submitted';
public function __toString(): string
{
|
پیکربندی EasyAdmin را بهروزرسانی کنید تا قادر به مشاهدهی وضعیت کامنتها باشیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | --- a/config/packages/easy_admin.yaml
+++ b/config/packages/easy_admin.yaml
@@ -18,6 +18,7 @@ easy_admin:
- author
- { property: 'email', type: 'email' }
- { property: 'photoFilename', type: 'image', 'base_path': "/uploads/photos", label: 'Photo' }
+ - state
- { property: 'createdAt', type: 'datetime' }
sort: ['createdAt', 'ASC']
filters: ['conference']
@@ -26,5 +27,6 @@ easy_admin:
- { property: 'conference' }
- { property: 'createdAt', type: datetime, type_options: { disabled: true } }
- 'author'
+ - { property: 'state' }
- { property: 'email', type: 'email' }
- text
|
بهروزرسانی آزمونها با تنظیم state
برای fixtureها را فراموش نکنید:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | --- a/src/DataFixtures/AppFixtures.php
+++ b/src/DataFixtures/AppFixtures.php
@@ -37,8 +37,16 @@ class AppFixtures extends Fixture
$comment1->setAuthor('Fabien');
$comment1->setEmail('[email protected]');
$comment1->setText('This was a great conference.');
+ $comment1->setState('published');
$manager->persist($comment1);
+ $comment2 = new Comment();
+ $comment2->setConference($amsterdam);
+ $comment2->setAuthor('Lucas');
+ $comment2->setEmail('[email protected]');
+ $comment2->setText('I think this one is going to be moderated.');
+ $manager->persist($comment2);
+
$admin = new Admin();
$admin->setRoles(['ROLE_ADMIN']);
$admin->setUsername('admin');
|
برای آزمونهای کنترلر، اعتبارسنجی را شبیهسازی کنید:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | --- a/tests/Controller/ConferenceControllerTest.php
+++ b/tests/Controller/ConferenceControllerTest.php
@@ -2,6 +2,8 @@
namespace App\Tests\Controller;
+use App\Repository\CommentRepository;
+use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class ConferenceControllerTest extends WebTestCase
@@ -22,10 +24,16 @@ class ConferenceControllerTest extends WebTestCase
$client->submitForm('Submit', [
'comment_form[author]' => 'Fabien',
'comment_form[text]' => 'Some feedback from an automated functional test',
- 'comment_form[email]' => '[email protected]',
+ 'comment_form[email]' => $email = '[email protected]',
'comment_form[photo]' => dirname(__DIR__, 2).'/public/images/under-construction.gif',
]);
$this->assertResponseRedirects();
+
+ // simulate comment validation
+ $comment = self::$container->get(CommentRepository::class)->findOneByEmail($email);
+ $comment->setState('published');
+ self::$container->get(EntityManagerInterface::class)->flush();
+
$client->followRedirect();
$this->assertSelectorExists('div:contains("There are 2 comments")');
}
|
از درون یک آزمون PHPUnit، میتوانید هر سرویسی را از کانتینر و از طریق self::$container->get()
دریافت کنید؛ همچنین این روش دسترسی به سرویسهای غیر عمومی را نیز ممکن میکند.
درک کردن پیغامرسان (Messenger)¶
مدیریت کدهای غیرهمزمان در سیمفونی بر عهدهی کامپوننت پیغامرسان (Messenger) است:
1 | $ symfony composer req messenger
|
زمانی که لازم باشد یک منطق به صورت غیرهمزمان اجرا گردد، یک پیغام (message) برای گذرگاه پیغامرسان (messenger bus) ارسال کنید. گذرگاه، پیغام را در یک صف(queue) ذخیره کرده و به صورت آنی باز میگرداند تا جریان عملیات بتوان به سریعترین وجه ممکن ادامه یابد.
یک مصرفکننده (consumer) به صورت مستمر در پسزمینه پیغامهای جدید درون صف را میخواند و منطق مربوطه را اجرا میکند. مصرفکننده میتواند بر روی همان سرور اپلیکیشن وب یا بر روی یک سرور مجزا اجرا گردد.
این بسیار شبیه به شیوهی رسیدگی به درخواستهای HTTP است، با این تفاوت که دیگر واکنشی نداریم.
کدنویسی یک رسیدگیکننده به پیغام (Message Handler)¶
پیغام یک شیء دادهای است که نباید حاوی هیچ منطقی باشد. پیغام برای ذخیرهشدن در صف، serialize میشود، بنابراین تنها دادههای «سادهی» قابل serializeکردن را ذخیره کنید.
یک کلاس CommentMessage
ایجاد کنید:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | namespace App\Message;
class CommentMessage
{
private $id;
private $context;
public function __construct(int $id, array $context = [])
{
$this->id = $id;
$this->context = $context;
}
public function getId(): int
{
return $this->id;
}
public function getContext(): array
{
return $this->context;
}
}
|
در دنیای پیغامرسان، ما کنترلر نداریم، بلکه رسیدگیکنندگان به پیغام (message handlers) را داریم.
یک کلاس CommentMessageHandler
که میداند چگونه به پیغامهای CommentMessage
رسیدگی کند را در فضاینام (namespace) جدید App\MessageHandler
ایجاد کنید:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | namespace App\MessageHandler;
use App\Message\CommentMessage;
use App\Repository\CommentRepository;
use App\SpamChecker;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
class CommentMessageHandler implements MessageHandlerInterface
{
private $spamChecker;
private $entityManager;
private $commentRepository;
public function __construct(EntityManagerInterface $entityManager, SpamChecker $spamChecker, CommentRepository $commentRepository)
{
$this->entityManager = $entityManager;
$this->spamChecker = $spamChecker;
$this->commentRepository = $commentRepository;
}
public function __invoke(CommentMessage $message)
{
$comment = $this->commentRepository->find($message->getId());
if (!$comment) {
return;
}
if (2 === $this->spamChecker->getSpamScore($comment, $message->getContext())) {
$comment->setState('spam');
} else {
$comment->setState('published');
}
$this->entityManager->flush();
}
}
|
MessageHandlerInterface
یک رابط * نشانهگذار (marker)* است. این رابط تنها به سیمفونی کمک میکند تا به صورت خودکار کلاس را به عنوان یک پیغامرسان ثبت و پیکربندی کند. بر اساس قرارداد، منطق یک رسیدگیکننده در درون متدی که __invoke()
نامیده میشود، قرار میگیرد. راهنمای نوعِ (type hint) CommentMessage
بر روی آرگمان این متد، به پیغامرسان میگوید که این کلاس، به چه کلاسهایی (چه نوع پیغامهایی) رسیدگی میکند.
کنترلر را بهروزرسانی کنید تا از سیستم جدید استفاده کند:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | --- a/src/Controller/ConferenceController.php
+++ b/src/Controller/ConferenceController.php
@@ -5,14 +5,15 @@ namespace App\Controller;
use App\Entity\Comment;
use App\Entity\Conference;
use App\Form\CommentFormType;
+use App\Message\CommentMessage;
use App\Repository\CommentRepository;
use App\Repository\ConferenceRepository;
-use App\SpamChecker;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Routing\Annotation\Route;
use Twig\Environment;
@@ -20,11 +21,13 @@ class ConferenceController extends AbstractController
{
private $twig;
private $entityManager;
+ private $bus;
- public function __construct(Environment $twig, EntityManagerInterface $entityManager)
+ public function __construct(Environment $twig, EntityManagerInterface $entityManager, MessageBusInterface $bus)
{
$this->twig = $twig;
$this->entityManager = $entityManager;
+ $this->bus = $bus;
}
/**
@@ -40,7 +43,7 @@ class ConferenceController extends AbstractController
/**
* @Route("/conference/{slug}", name="conference")
*/
- public function show(Request $request, Conference $conference, CommentRepository $commentRepository, SpamChecker $spamChecker, string $photoDir): Response
+ public function show(Request $request, Conference $conference, CommentRepository $commentRepository, string $photoDir): Response
{
$comment = new Comment();
$form = $this->createForm(CommentFormType::class, $comment);
@@ -58,6 +61,7 @@ class ConferenceController extends AbstractController
}
$this->entityManager->persist($comment);
+ $this->entityManager->flush();
$context = [
'user_ip' => $request->getClientIp(),
@@ -65,11 +69,8 @@ class ConferenceController extends AbstractController
'referrer' => $request->headers->get('referer'),
'permalink' => $request->getUri(),
];
- if (2 === $spamChecker->getSpamScore($comment, $context)) {
- throw new \RuntimeException('Blatant spam, go away!');
- }
- $this->entityManager->flush();
+ $this->bus->dispatch(new CommentMessage($comment->getId(), $context));
return $this->redirectToRoute('conference', ['slug' => $conference->getSlug()]);
}
|
ما حالا به جای تکیه به بررسیکنندهی دادههای هرز، یک پیغام را به گذرگاه اعزام میکنیم. سپس رسیدگیکننده تصمیم میگیرد که با آن چه کار کند.
ما به چیزی غیرمنتظره دست یافتیم. ما کنترلرمان را از بررسیکنندهی دادهی هرز (Spam Checker) مجزا کردیم و منطق آن را به یک کلاس جدید یعنی رسیدگیکننده (handler) انتقال دادیم. این یک نمونهی عالی از کارکرد گذرگاه است. کد را بیازمایید، کار میکند. هنوز همه چیز به صورت همزمان کار میکند، اما احتمالاً الان کد «بهتر» است.
محدودسازی کامنتهای نمایشدادهشده¶
منطق نمایش را بهروزرسانی کنید تا از ظاهرشدن کامنتهای منتشرنشده در جلوی صحنه جلوگیری شود:
1 2 3 4 5 6 7 8 9 10 11 12 | --- a/src/Repository/CommentRepository.php
+++ b/src/Repository/CommentRepository.php
@@ -27,7 +27,9 @@ class CommentRepository extends ServiceEntityRepository
{
$query = $this->createQueryBuilder('c')
->andWhere('c.conference = :conference')
+ ->andWhere('c.state = :state')
->setParameter('conference', $conference)
+ ->setParameter('state', 'published')
->orderBy('c.createdAt', 'DESC')
->setMaxResults(self::PAGINATOR_PER_PAGE)
->setFirstResult($offset)
|
پیش به سوی ناهمزمانی واقعی¶
به صورت پیشفرض، رسیدگیکنندهها به صورت همزمان اجرا میشوند. برای ناهمزمان کردن، لازم دارید که در فایل پیکربندی config/packages/messenger.yaml
، به صورت صریح مشخص کنید که برای هر رسیدگیکننده باید از کدام صف استفاده شود:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | --- a/config/packages/messenger.yaml
+++ b/config/packages/messenger.yaml
@@ -5,10 +5,10 @@ framework:
transports:
# https://symfony.com/doc/current/messenger.html#transport-configuration
- # async: '%env(MESSENGER_TRANSPORT_DSN)%'
+ async: '%env(RABBITMQ_DSN)%'
# failed: 'doctrine://default?queue_name=failed'
# sync: 'sync://'
routing:
# Route your messages to the transports
- # 'App\Message\YourMessage': async
+ App\Message\CommentMessage: async
|
پیکربندی به گذرگاه میگوید که نمونههای App\Message\CommentMessage
را به صف async
ارسال کند که توسط یک DSN تعریف شده و در متغیر محیط RABBITMQ_DSN
ذخیره شده است.
افزودن RabbitMQ به پشتهی Docker¶
همانطور که احتمالاً حدس زدهاید، ما میخواهیم از RabbitMQ استفاده کنیم:
1 2 3 4 5 6 7 8 9 10 | --- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -12,3 +12,7 @@ services:
redis:
image: redis:5-alpine
ports: [6379]
+
+ rabbitmq:
+ image: rabbitmq:3.7-management
+ ports: [5672, 15672]
|
راهاندازی مجدد سرویسهای Docker¶
برای مجبور کردن Docker Compose به در نظر گرفتن کانتینر RabbitMQ، کانتینرها را متوقف کرده و آنها را مجدداً راهاندازی کنید:
1 2 | $ docker-compose stop
$ docker-compose up -d
|
1 | $ sleep 10
|
مصرفکردن پیغامها¶
اگر سعی کنید که یک کامنت جدید ارسال کنید، دیگر بررسیکنندهی محتوای هرز اجرا نمیشود. برای تأیید این امر، یک فراخوانی error_log()
به متد getSpamScore()
بیافزایید. به جای آن، یک پیغام در RabbitMQ در حال انتظار است و آمادگی دارد تا توسط یک پردازشگر مصرف شود.
همانطور که احتمالاً تصور کردهاید، سیمفونی یک فرمان مصرفکننده به همراه دارد. حالا آن را اجرا کنید:
1 | $ symfony console messenger:consume async -vv
|
این فرمان باید بیدرنگ پیغامی را برای کامنت ارسالشده اعزام گردیده است، مصرف کند:
1 2 3 4 5 6 7 8 9 10 11 | [OK] Consuming messages from transports "async".
// The worker will automatically exit once it has received a stop signal via the messenger:stop-workers command.
// Quit the worker with CONTROL-C.
11:30:20 INFO [messenger] Received message App\Message\CommentMessage ["message" => App\Message\CommentMessage^ { …},"class" => "App\Message\CommentMessage"]
11:30:20 INFO [http_client] Request: "POST https://80cea32be1f6.rest.akismet.com/1.1/comment-check"
11:30:20 INFO [http_client] Response: "200 https://80cea32be1f6.rest.akismet.com/1.1/comment-check"
11:30:20 INFO [messenger] Message App\Message\CommentMessage handled by App\MessageHandler\CommentMessageHandler::__invoke ["message" => App\Message\CommentMessage^ { …},"class" => "App\Message\CommentMessage","handler" => "App\MessageHandler\CommentMessageHandler::__invoke"]
11:30:20 INFO [messenger] App\Message\CommentMessage was handled successfully (acknowledging to transport). ["message" => App\Message\CommentMessage^ { …},"class" => "App\Message\CommentMessage"]
|
فعالیت مصرفکنندهی پیغام، log شده است. اما شما با دادن پرچم -vv
، به صورت آنی در کنسول بازخورد میگیرد. حتی شما باید قادر به تشخیص فراخوانی API مربوط به Akismet نیز باشد.
برای متوقف کردن مصرفکننده، Ctrl+C
را فشار دهید.
کشف رابط مدیریتی تحت وب RabbitMQ¶
اگر میخواید صفها و پیغامهای در جریان درون RabbitMQ را ببینید، رابط مدیریتی تحت وب آن را باز کنید:
1 | $ symfony open:local:rabbitmq
|
یا از طریق نوارابزار اشکالزدایی وب:

از guest
/guest
برای ورود به رابط کاربری مدیریتی RabbitMQ استفاده کنید:

اجرای کارگرها (Workers) در پسزمینه¶
به جای اینکه هر بار پس از ارسال کامنت، مصرفکننده را اجرا و به محض پایان اجرا، آن را متوقف کنیم، ما میخواهیم که آن را به صورت پیوسته و بدون آنکه لازم به بازکردن تعداد زیادی پنجره یا تب ترمینال باشد، اجرا کنیم.
رابط خط فرمان سیمفونی میتواند با پرچم شبه (-d
) بر روی فرمان run
، چنین فرمانها یا کارگرهایِ پسزمینهای را اجرا کند.
مجدداً مصرفکنندهی پیغام را اجرا کنید، اما آن را به پسزمینه بفرستید:
1 | $ symfony run -d --watch=config,src,templates,vendor symfony console messenger:consume async
|
گزینهی --watch
به سیمفونی میگوید که هرگاه تغییری از نوع فایلسیستم در پوشههای config/
، src/
، templates/
یا vendor/
بوجود آید، باید این فرمان را مجدداً راهاندازی نماید.
توجه
از پرچم -vv
استفاده نکنید چرا که موجب پیغامهای تکراری در server:log
میشود (پیغامهای لاگشده و پیغامهای کنسول).
اگر مصرفکننده به دلیلی (محدودیت حافظه، باگ و ...) از کار بیافتد، به صورت خودکار راهاندازی مجدد خواهد شد. و اگر مصرفکننده مجدداً خیلی سریع شکست بخورد، رابط خط فرمان سیمفونی، بازراهاندازی آن را رها خواهد کرد.
لاگها از طریق symfony server:log
به همراه سایر لاگهایی که از PHP، وب سرور و اپلیکیشن میآیند، جریان مییابند:
1 | $ symfony server:log
|
از فرمان server:status
استفاده کنید تا تمام کارگرهای پسزمینهی بهکاررفته برای پروژهی فعلی لیست شوند:
1 2 3 4 | $ symfony server:status
Web server listening on https://127.0.0.1:8000
Command symfony console messenger:consume async running with PID 15774 (watching config/, src/, templates/)
|
برای متوقفکردن یک کارگر، وب سرور را متوقف کنید یا PID دادهشده توسط فرمان server:status
را بکشید:
1 | $ kill 15774
|
تلاش مجدد برای پیغامهای شکستخورده¶
چه پیش میآید اگر هنگان مصرف پیغام، Akismet خراب باشد؟ برای افراد ارسالکنندهی پیغام مشکلی ایجاد نمیشود، اما پیغام از دست میرود و هرزبودن داده بررسی نمیگردد.
پیغامرسان دارای یک مکانیسم تلاش مجدد برای زمانهایی است که یک استثناء هنگام رسیدگی به پیغام رخ میدهد. بیایید آن را پیکربندی کنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | --- a/config/packages/messenger.yaml
+++ b/config/packages/messenger.yaml
@@ -5,10 +5,17 @@ framework:
transports:
# https://symfony.com/doc/current/messenger.html#transport-configuration
- async: '%env(RABBITMQ_DSN)%'
- # failed: 'doctrine://default?queue_name=failed'
+ async:
+ dsn: '%env(RABBITMQ_DSN)%'
+ retry_strategy:
+ max_retries: 3
+ multiplier: 2
+
+ failed: 'doctrine://default?queue_name=failed'
# sync: 'sync://'
+ failure_transport: failed
+
routing:
# Route your messages to the transports
App\Message\CommentMessage: async
|
اگر هنگام رسیدگی به پیغام، مشکلی رخ دهد، مصرفکننده ۳ بار تلاش مجدد میکند و پس از آن از تلاش بیشتر دست میکشد. اما به جای دورانداختن پیغام، آن را در یک انبار دائمیتر ذخیره میکند. یعنی در صف failed
که از پایگاهدادهی Doctrine استفاده میکند.
با کمک فرمانهای زیر، پیغامهای شکستخورده را بررسی کرده و مجدداً برای توفیق آنها تلاش کنید:
1 2 3 | $ symfony console messenger:failed:show
$ symfony console messenger:failed:retry
|
مستقرکردن RabbitMQ¶
افزودن RabbitMQ به سرورهای عملآوری، میتواند از طریق اضافهکردن آن به لیست سرویسها انجام شود:
1 2 3 4 5 6 7 8 9 10 11 | --- a/.symfony/services.yaml
+++ b/.symfony/services.yaml
@@ -5,3 +5,8 @@ db:
rediscache:
type: redis:5.0
+
+queue:
+ type: rabbitmq:3.7
+ disk: 1024
+ size: S
|
همچنین در پیکربندی کانتینر وب به آن ارجاع دهید و amqp
را که یک افزونهی PHP است فعال کنید:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | --- a/.symfony.cloud.yaml
+++ b/.symfony.cloud.yaml
@@ -4,6 +4,7 @@ type: php:7.4
runtime:
extensions:
+ - amqp
- redis
- pdo_pgsql
- apcu
@@ -26,6 +27,7 @@ disk: 512
relationships:
database: "db:postgresql"
redis: "rediscache:redis"
+ rabbitmq: "queue:rabbitmq"
web:
locations:
|
وقتی RabbitMQ بر روی یک پروژه نصب شده است، میتوانید از طریق باز کردن یک تونل، به رابط مدیریتی تحت وب آن دست پیدا کنید:
1 2 3 4 5 | $ symfony tunnel:open
$ symfony open:remote:rabbitmq
# when done
$ symfony tunnel:close
|
اجرای کارگرها در SymfonyCloud¶
برای مصرف پیغامها از RabbitMQ، ما نیاز داریم تا فرمان messenger:consume
را به صورت مستمر اجرا کنیم. در SymfonyCloud، این برای یک کارگر (worker)، قانون است:
1 2 3 4 5 6 7 8 9 10 11 | --- a/.symfony.cloud.yaml
+++ b/.symfony.cloud.yaml
@@ -54,3 +54,8 @@ hooks:
set -x -e
(>&2 symfony-deploy)
+
+workers:
+ messages:
+ commands:
+ start: symfony console messenger:consume async -vv --time-limit=3600 --memory-limit=128M
|
همچون رابط خط فرمان سیمفونی، اینجا نیز SymfonyCloud بازراهاندازیها و لاگها را مدیریت میکند.
برای گرفتن لاگهای یک کارگر، از این استفاده کنید:
1 | $ symfony logs --worker=messages all
|
- « Previous گام 17: آزمودن (Testing)
- Next » گام 19: تصمیمگیری با یک جریانکار
This work, including the code samples, is licensed under a Creative Commons BY-NC-SA 4.0 license.