การถ่ายโอนแบ็กเอนด์ PHP ไปยังสตรีมบัส Redis และเลือกไลบรารีที่ไม่ขึ้นกับเฟรมเวิร์ก

การถ่ายโอนแบ็กเอนด์ PHP ไปยังสตรีมบัส Redis และเลือกไลบรารีที่ไม่ขึ้นกับเฟรมเวิร์ก

คำปรารภ

เว็บไซต์ของฉันซึ่งฉันทำเป็นงานอดิเรก ได้รับการออกแบบมาเพื่อโฮสต์โฮมเพจและไซต์ส่วนตัวที่น่าสนใจ หัวข้อนี้เริ่มทำให้ฉันสนใจตั้งแต่เริ่มต้นการเดินทางด้านการเขียนโปรแกรม ในขณะนั้น ฉันรู้สึกทึ่งที่ได้เจอผู้เชี่ยวชาญที่เก่งๆ ที่เขียนเกี่ยวกับตัวเอง งานอดิเรก และโปรเจ็กต์ของพวกเขา นิสัยในการค้นพบสิ่งเหล่านี้ด้วยตนเองยังคงอยู่มาจนถึงทุกวันนี้: ในเกือบทุกไซต์เชิงพาณิชย์และไม่ใช่เชิงพาณิชย์ ฉันยังคงดูในส่วนท้ายเพื่อค้นหาลิงก์ไปยังผู้เขียน

การนำแนวคิดไปใช้

เวอร์ชันแรกเป็นเพียงหน้า html บนเว็บไซต์ส่วนตัวของฉัน ซึ่งฉันใส่ลิงก์พร้อมลายเซ็นลงในรายการ ul หลังจากพิมพ์ไป 20 หน้าในช่วงระยะเวลาหนึ่ง ฉันเริ่มคิดว่าวิธีนี้ไม่ได้ผลมากนัก และตัดสินใจที่จะพยายามทำให้กระบวนการเป็นแบบอัตโนมัติ ใน stackoverflow ฉันสังเกตเห็นว่ามีคนจำนวนมากระบุไซต์ในโปรไฟล์ของพวกเขา ดังนั้นฉันจึงเขียน parser ใน php ซึ่งเพียงแค่ดูโปรไฟล์ต่างๆ โดยเริ่มจากโปรไฟล์แรก (ที่อยู่ใน SO จนถึงทุกวันนี้จะเป็นดังนี้: `/users/1` ) แยกลิงก์จากแท็กที่ต้องการและเพิ่มลงใน SQLite

สิ่งนี้สามารถเรียกได้ว่าเป็นเวอร์ชันที่สอง: คอลเลกชันของ URL นับหมื่นในตาราง SQLite ซึ่งแทนที่รายการคงที่ใน HTML ฉันค้นหาอย่างง่าย ๆ ในรายการนี้ เพราะ มีเพียง URL เท่านั้น จากนั้นการค้นหาก็ขึ้นอยู่กับสิ่งเหล่านั้น

ในขั้นตอนนี้ฉันละทิ้งโครงการและกลับมาทำใหม่อีกครั้งหลังจากผ่านไปนาน ในขั้นตอนนี้ประสบการณ์การทำงานของฉันมากกว่าสามปีแล้วและฉันรู้สึกว่าฉันสามารถทำอะไรที่จริงจังกว่านี้ได้ นอกจากนี้ยังมีความปรารถนาอย่างยิ่งที่จะเชี่ยวชาญเทคโนโลยีที่ค่อนข้างใหม่

รุ่นทันสมัย

โครงการ ใช้งานใน Docker ฐานข้อมูลถูกถ่ายโอนไปยัง mongoDb และล่าสุดมีการเพิ่มหัวไชเท้าซึ่งในตอนแรกมีไว้เพื่อการแคชเท่านั้น ไมโครเฟรมเวิร์ก PHP ตัวใดตัวหนึ่งถูกใช้เป็นพื้นฐาน

ปัญหา

ไซต์ใหม่จะถูกเพิ่มโดยคำสั่งคอนโซลที่ทำสิ่งต่อไปนี้พร้อมกัน:

  • ดาวน์โหลดเนื้อหาตาม URL
  • ตั้งค่าสถานะที่ระบุว่า HTTPS พร้อมใช้งานหรือไม่
  • รักษาสาระสำคัญของเว็บไซต์
  • HTML ต้นฉบับและส่วนหัวจะถูกบันทึกไว้ในประวัติ "การจัดทำดัชนี"
  • แยกวิเคราะห์เนื้อหา แยกชื่อและคำอธิบาย
  • บันทึกข้อมูลไปยังคอลเลกชันแยกต่างหาก

เพียงพอที่จะจัดเก็บไซต์และแสดงในรายการ:

การถ่ายโอนแบ็กเอนด์ PHP ไปยังสตรีมบัส Redis และเลือกไลบรารีที่ไม่ขึ้นกับเฟรมเวิร์ก

แต่แนวคิดในการจัดทำดัชนี จัดหมวดหมู่ และจัดอันดับทุกอย่างโดยอัตโนมัติ ทำให้ทุกอย่างทันสมัยอยู่เสมอ ไม่สอดคล้องกับกระบวนทัศน์นี้ แม้แต่เพียงแค่เพิ่มวิธีการทางเว็บเพื่อเพิ่มหน้าก็จำเป็นต้องมีการทำซ้ำโค้ดและการบล็อกเพื่อหลีกเลี่ยง DDoS ที่อาจเกิดขึ้น

โดยทั่วไปแล้ว ทุกอย่างสามารถทำได้พร้อมกัน และในวิธีการทางเว็บ คุณสามารถบันทึก URL เพื่อให้ daemon ตัวมหึมาทำงานทั้งหมดสำหรับ URL จากรายการได้ แต่ถึงกระนั้น แม้แต่ที่นี่ คำว่า "คิว" ก็บ่งบอกถึงตัวมันเอง และหากมีการนำคิวไปใช้ งานทั้งหมดก็สามารถแบ่งและดำเนินการได้แบบอะซิงโครนัสเป็นอย่างน้อย

การตัดสิน

ใช้งานคิวและสร้างระบบที่ขับเคลื่อนด้วยเหตุการณ์เพื่อประมวลผลงานทั้งหมด และฉันอยากลองใช้ Redis Streams มาเป็นเวลานานแล้ว

การใช้สตรีม Redis ใน PHP

เพราะ เนื่องจากเฟรมเวิร์กของฉันไม่ใช่หนึ่งในสามยักษ์ใหญ่ Symfony, Laravel, Yii ฉันจึงต้องการค้นหาไลบรารีอิสระ แต่เมื่อปรากฏออกมา (ในการตรวจสอบครั้งแรก) มันเป็นไปไม่ได้เลยที่จะหาห้องสมุดที่จริงจังเป็นรายบุคคล ทุกสิ่งที่เกี่ยวข้องกับคิวอาจเป็นโปรเจ็กต์จาก 3 คอมมิตเมื่อห้าปีที่แล้วหรือเชื่อมโยงกับเฟรมเวิร์ก

ฉันได้ยินมามากมายเกี่ยวกับ Symfony ในฐานะซัพพลายเออร์ส่วนประกอบที่มีประโยชน์ส่วนบุคคล และฉันก็ใช้บางส่วนไปแล้ว และบางสิ่งจาก Laravel ก็สามารถใช้ได้เช่นกัน เช่น ORM โดยไม่ต้องมีเฟรมเวิร์กเอง

ซิมโฟนี่/เมสเซนเจอร์

ผู้สมัครคนแรกดูเหมือนจะเหมาะสมในทันที และฉันก็ติดตั้งมันอย่างไม่ต้องสงสัย แต่กลับกลายเป็นว่ายากกว่าสำหรับตัวอย่างการใช้งาน Google นอก Symfony จะรวบรวมคลาสที่มีชื่อสากลและไร้ความหมายจากคลาสจำนวนมาก บัสสำหรับส่งข้อความ และแม้แต่บน Redis ได้อย่างไร

การถ่ายโอนแบ็กเอนด์ PHP ไปยังสตรีมบัส Redis และเลือกไลบรารีที่ไม่ขึ้นกับเฟรมเวิร์ก

เอกสารในเว็บไซต์อย่างเป็นทางการมีรายละเอียดค่อนข้างมาก แต่การเริ่มต้นนั้นอธิบายไว้สำหรับ Symfony เท่านั้นโดยใช้ YML ที่พวกเขาชื่นชอบและวิธีการมหัศจรรย์อื่น ๆ สำหรับผู้ที่ไม่ใช่ซิมโฟนี ฉันไม่สนใจกระบวนการติดตั้งเลย โดยเฉพาะในช่วงวันหยุดปีใหม่ แต่ฉันต้องทำสิ่งนี้เป็นเวลานานโดยไม่คาดคิด

การพยายามหาวิธีสร้างอินสแตนซ์ของระบบโดยใช้แหล่งที่มาของ Symfony ก็ไม่ใช่งานที่ไม่สำคัญที่สุดสำหรับกำหนดเวลาที่จำกัด:

การถ่ายโอนแบ็กเอนด์ PHP ไปยังสตรีมบัส Redis และเลือกไลบรารีที่ไม่ขึ้นกับเฟรมเวิร์ก

หลังจากเจาะลึกทั้งหมดนี้และพยายามทำอะไรบางอย่างด้วยมือของฉัน ฉันก็สรุปได้ว่าฉันกำลังทำไม้ค้ำอยู่และตัดสินใจลองทำอย่างอื่น

สว่าง/เข้าคิว

ปรากฎว่าไลบรารีนี้เชื่อมโยงอย่างแน่นหนากับโครงสร้างพื้นฐาน Laravel และการขึ้นต่อกันอื่น ๆ มากมาย ดังนั้นฉันจึงไม่ได้ใช้เวลามากนักกับมัน: ฉันติดตั้งแล้ว ดูมัน เห็นการขึ้นต่อกัน และลบมันออก

yiisoft/yii2-คิว

ที่นี่มันถูกสันนิษฐานจากชื่อทันทีอีกครั้งซึ่งมีความเชื่อมโยงอย่างเข้มงวดกับ Yii2 ฉันต้องใช้ไลบรารีนี้และมันก็ไม่ได้แย่ แต่ฉันไม่ได้คิดถึงความจริงที่ว่ามันขึ้นอยู่กับ Yii2 โดยสิ้นเชิง

ที่เหลือ

ทุกสิ่งทุกอย่างที่ฉันพบใน GitHub เป็นโปรเจ็กต์ที่ไม่น่าเชื่อถือ ล้าสมัย และละทิ้งไปโดยไม่มีดาว ส้อม และคอมมิตจำนวนมาก

กลับไปที่รายละเอียดด้านเทคนิคของ Symfony/Messenger

ฉันต้องหาห้องสมุดนี้ให้เจอ และหลังจากใช้เวลาสักพักฉันก็ทำสำเร็จ ปรากฎว่าทุกอย่างค่อนข้างกระชับและเรียบง่าย เพื่อยกตัวอย่างรถบัส ฉันสร้างโรงงานขนาดเล็กขึ้นมา เพราะ... ฉันควรจะมียางหลายเส้นและมีแฮนด์ที่แตกต่างกัน

การถ่ายโอนแบ็กเอนด์ PHP ไปยังสตรีมบัส Redis และเลือกไลบรารีที่ไม่ขึ้นกับเฟรมเวิร์ก

เพียงไม่กี่ขั้นตอน:

  • เราสร้างตัวจัดการข้อความที่ควรเรียกง่ายๆ
  • เราล้อมพวกมันไว้ใน HandlerDescriptor (คลาสจากไลบรารี)
  • เรารวม "Descriptors" เหล่านี้ไว้ในอินสแตนซ์ HandlersLocator
  • การเพิ่ม HandlersLocator ไปยังอินสแตนซ์ MessageBus
  • เราส่งชุด `SenderInterface` ไปยัง SendersLocator ในกรณีของฉันคือคลาส `RedisTransport` ซึ่งได้รับการกำหนดค่าในลักษณะที่ชัดเจน
  • การเพิ่ม SendersLocator ไปยังอินสแตนซ์ MessageBus

MessageBus имеет метод `->dispatch()`, который ищет соответствующие обработчики в HandlersLocator и передает сообщение им, пользуясь соответствующими `SenderInterface` для отправки через шину (Redis streams).

ในการกำหนดค่าคอนเทนเนอร์ (ในกรณีนี้คือ php-di) บันเดิลทั้งหมดนี้สามารถกำหนดค่าได้ดังนี้:

        CONTAINER_REDIS_TRANSPORT_SECRET => function (ContainerInterface $c) {
            return new RedisTransport(
                $c->get(CONTAINER_REDIS_STREAM_CONNECTION_SECRET),
                $c->get(CONTAINER_SERIALIZER))
            ;
        },
        CONTAINER_REDIS_TRANSPORT_LOG => function (ContainerInterface $c) {
            return new RedisTransport(
                $c->get(CONTAINER_REDIS_STREAM_CONNECTION_LOG),
                $c->get(CONTAINER_SERIALIZER))
            ;
        },
        CONTAINER_REDIS_STREAM_RECEIVER_SECRET => function (ContainerInterface $c) {
            return new RedisReceiver(
                $c->get(CONTAINER_REDIS_STREAM_CONNECTION_SECRET),
                $c->get(CONTAINER_SERIALIZER)
            );
        },
        CONTAINER_REDIS_STREAM_RECEIVER_LOG => function (ContainerInterface $c) {
            return new RedisReceiver(
                $c->get(CONTAINER_REDIS_STREAM_CONNECTION_LOG),
                $c->get(CONTAINER_SERIALIZER)
            );
        },
        CONTAINER_REDIS_STREAM_BUS => function (ContainerInterface $c) {
            $sendersLocator = new SendersLocator([
                AppMessagesSecretJsonMessages::class => [CONTAINER_REDIS_TRANSPORT_SECRET],
                AppMessagesDaemonLogMessage::class => [CONTAINER_REDIS_TRANSPORT_LOG],
            ], $c);
            $middleware[] = new SendMessageMiddleware($sendersLocator);

            return new MessageBus($middleware);
        },
        CONTAINER_REDIS_STREAM_CONNECTION_SECRET => function (ContainerInterface $c) {
            $host = 'bu-02-redis';
            $port = 6379;
            $dsn = "redis://$host:$port";
            $options = [
                'stream' => 'secret',
                'group' => 'default',
                'consumer' => 'default',
            ];

            return Connection::fromDsn($dsn, $options);
        },
        CONTAINER_REDIS_STREAM_CONNECTION_LOG => function (ContainerInterface $c) {
            $host = 'bu-02-redis';
            $port = 6379;
            $dsn = "redis://$host:$port";
            $options = [
                'stream' => 'log',
                'group' => 'default',
                'consumer' => 'default',
            ];

            return Connection::fromDsn($dsn, $options);
        },

ที่นี่คุณจะเห็นว่าใน SendersLocator เราได้กำหนด "การขนส่ง" ที่แตกต่างกันสำหรับข้อความสองข้อความที่แตกต่างกัน ซึ่งแต่ละข้อความมีการเชื่อมต่อกับสตรีมที่สอดคล้องกัน

ฉันสร้างโปรเจ็กต์สาธิตแยกต่างหากเพื่อสาธิตแอปพลิเคชันของ daemons สามตัวที่สื่อสารกันโดยใช้บัสต่อไปนี้: https://github.com/backend-university/products/tree/master/products/02-redis-streams-bus.

แต่ฉันจะแสดงให้คุณเห็นว่าผู้บริโภคสามารถมีโครงสร้างได้อย่างไร:

use AppMessagesDaemonLogMessage;
use SymfonyComponentMessengerHandlerHandlerDescriptor;
use SymfonyComponentMessengerHandlerHandlersLocator;
use SymfonyComponentMessengerMessageBus;
use SymfonyComponentMessengerMiddlewareHandleMessageMiddleware;
use SymfonyComponentMessengerMiddlewareSendMessageMiddleware;
use SymfonyComponentMessengerTransportSenderSendersLocator;

require_once __DIR__ . '/../vendor/autoload.php';
/** @var PsrContainerContainerInterface $container */
$container = require_once('config/container.php');

$handlers = [
    DaemonLogMessage::class => [
        new HandlerDescriptor(
            function (DaemonLogMessage $m) {
                error_log('DaemonLogHandler: message handled: / ' . $m->getMessage());
            },
            ['from_transport' => CONTAINER_REDIS_TRANSPORT_LOG]
        )
    ],
];
$middleware = [];
$middleware[] = new HandleMessageMiddleware(new HandlersLocator($handlers));
$sendersLocator = new SendersLocator(['*' => [CONTAINER_REDIS_TRANSPORT_LOG]], $container);
$middleware[] = new SendMessageMiddleware($sendersLocator);

$bus = new MessageBus($middleware);
$receivers = [
    CONTAINER_REDIS_TRANSPORT_LOG => $container->get(CONTAINER_REDIS_STREAM_RECEIVER_LOG),
];
$w = new SymfonyComponentMessengerWorker($receivers, $bus, $container->get(CONTAINER_EVENT_DISPATCHER));
$w->run();

การใช้โครงสร้างพื้นฐานนี้ในแอปพลิเคชัน

หลังจากติดตั้งบัสในแบ็กเอนด์แล้ว ฉันได้แยกแต่ละขั้นตอนออกจากคำสั่งซิงโครนัสแบบเก่า และสร้างตัวจัดการแยกกัน ซึ่งแต่ละคนทำหน้าที่ของตัวเอง

ไปป์ไลน์สำหรับการเพิ่มไซต์ใหม่ลงในฐานข้อมูลมีลักษณะดังนี้:

การถ่ายโอนแบ็กเอนด์ PHP ไปยังสตรีมบัส Redis และเลือกไลบรารีที่ไม่ขึ้นกับเฟรมเวิร์ก

และหลังจากนั้นทันที มันกลายเป็นเรื่องง่ายสำหรับฉันที่จะเพิ่มฟังก์ชันการทำงานใหม่ เช่น การแยกและแยกวิเคราะห์ Rss เพราะ กระบวนการนี้ยังต้องใช้เนื้อหาต้นฉบับ จากนั้นตัวจัดการตัวแยกลิงก์ RSS เช่น WebsiteIndexHistoryPersistor จะสมัครรับข้อความ "เนื้อหา/HtmlContent" จากนั้นจะประมวลผลและส่งข้อความที่ต้องการไปตามขั้นตอนต่อไป

การถ่ายโอนแบ็กเอนด์ PHP ไปยังสตรีมบัส Redis และเลือกไลบรารีที่ไม่ขึ้นกับเฟรมเวิร์ก

ในท้ายที่สุด เราก็ได้ daemons หลายตัว ซึ่งแต่ละ daemons จะรักษาการเชื่อมต่อกับทรัพยากรที่จำเป็นเท่านั้น ยกตัวอย่างปีศาจ รวบรวมข้อมูล ประกอบด้วยตัวจัดการทั้งหมดที่จำเป็นต้องเชื่อมต่ออินเทอร์เน็ตเพื่อดูเนื้อหา และ daemon ยังคงมีอยู่ เชื่อมต่อกับฐานข้อมูล

ตอนนี้ แทนที่จะเลือกจากฐานข้อมูล รหัสที่ต้องการหลังจากการแทรกโดยผู้คงอยู่จะถูกส่งผ่านบัสไปยังตัวจัดการที่สนใจทั้งหมด

ที่มา: will.com

เพิ่มความคิดเห็น