گام 18: پیش به سوی ناهمزمانی (Async)

5.0 version
Maintained

پیش به سوی ناهمزمانی (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 به‌روزرسانی نماید:

patch_file
 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 تنظیم شده است:

patch_file
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
--- a/src/Entity/Comment.php
+++ b/src/Entity/Comment.php
@@ -49,9 +49,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 را به‌روزرسانی کنید تا قادر به مشاهده‌ی وضعیت کامنت‌ها باشیم:

patch_file
 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: { attr: { readonly: true } } }
                     - 'author'
+                    - { property: 'state' }
                     - { property: 'email', type: 'email' }
                     - text

به‌روزرسانی آزمون‌ها با تنظیم state برای fixture‌ها را فراموش نکنید:

patch_file
 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');

برای آزمون‌های کنترلر، اعتبارسنجی را شبیه‌سازی کنید:

patch_file
 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,11 +24,17 @@ 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 ایجاد کنید:

src/Message/CommentMessage.php
 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 ایجاد کنید:

src/MessageHandler/CommentMessageHandler.php
 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 بر روی آرگمان این متد، به پیغام‌رسان می‌گوید که این کلاس، به چه کلاس‌هایی (چه نوع پیغام‌هایی) رسیدگی می‌کند.

کنترلر را به‌روزرسانی کنید تا از سیستم جدید استفاده کند:

patch_file
 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)
+    public function show(Request $request, Conference $conference, CommentRepository $commentRepository, string $photoDir)
     {
         $comment = new Comment();
         $form = $this->createForm(CommentFormType::class, $comment);
@@ -59,6 +62,7 @@ class ConferenceController extends AbstractController
             }

             $this->entityManager->persist($comment);
+            $this->entityManager->flush();

             $context = [
                 'user_ip' => $request->getClientIp(),
@@ -66,11 +70,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) انتقال دادیم. این یک نمونه‌ی عالی از کارکرد گذرگاه است. کد را بیازمایید، کار می‌کند. هنوز همه چیز به صورت همزمان کار می‌کند، اما احتمالاً الان کد «بهتر» است.

محدودسازی کامنت‌های نمایش‌داده‌شده

منطق نمایش را به‌روزرسانی کنید تا از ظاهرشدن کامنت‌های منتشرنشده در جلوی صحنه جلوگیری شود:

patch_file
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
--- a/src/Repository/CommentRepository.php
+++ b/src/Repository/CommentRepository.php
@@ -25,7 +25,9 @@ class CommentRepository extends ServiceEntityRepository
     {
         return $this->createQueryBuilder('c')
             ->andWhere('c.conference = :conference')
+            ->andWhere('c.state = :state')
             ->setParameter('conference', $conference)
+            ->setParameter('state', 'published')
             ->orderBy('c.createdAt', 'DESC')
             ->setMaxResults($limit)
             ->setFirstResult($offset)

پیش به سوی ناهمزمانی واقعی

به صورت پیشفرض، رسیدگی‌کننده‌ها به صورت همزمان اجرا می‌شوند. برای ناهمزمان کردن، لازم دارید که در فایل پیکربندی config/packages/messenger.yaml، به صورت صریح مشخص کنید که برای هر رسیدگی‌کننده باید از کدام صف استفاده شود:

patch_file
 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 استفاده کنیم:

patch_file
 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 خراب باشد؟ برای افراد ارسال‌کننده‌ی پیغام مشکلی ایجاد نمی‌شود، اما پیغام از دست می‌رود و هرزبودن داده بررسی نمی‌گردد.

پیغام‌رسان دارای یک مکانیسم تلاش مجدد برای زمان‌هایی است که یک استثناء هنگام رسیدگی به پیغام رخ می‌دهد. بیایید آن را پیکربندی کنیم:

patch_file
 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 به سرورهای عمل‌آوری، می‌تواند از طریق اضافه‌کردن آن به لیست سرویس‌ها انجام شود:

patch_file
 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 است فعال کنید:

patch_file
 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
         - pdo_pgsql
         - apcu
         - mbstring
@@ -22,6 +23,7 @@ variables:
 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)، قانون است:

patch_file
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
--- a/.symfony.cloud.yaml
+++ b/.symfony.cloud.yaml
@@ -46,3 +46,12 @@ hooks:
         set -x -e

         (>&2 symfony-deploy)
+
+workers:
+    messages:
+        commands:
+            start: |
+                set -x -e
+
+                (>&2 symfony-deploy)
+                php bin/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.