Máy chủ điều khiển sự kiện trong PHP

  • Post category:lập trình


Gần đây tôi đang nghiên cứu một loại kiến ​​trúc độc đáo cho các ứng dụng PHP. Tôi muốn nói trước với bạn rằng tôi không nghĩ nó sẽ sớm giải quyết được bất kỳ vấn đề thực tế nào; tôi vẫn muốn bạn tham gia vào quá trình suy nghĩ. Ai biết được những ý tưởng tuyệt vời nào có thể nảy sinh?

Trong bài đăng này, tôi sẽ tìm hiểu từng bước về kiến ​​trúc và giải quyết các lợi ích cũng như nhược điểm của nó – ít nhất là những điều tôi có thể nghĩ ra ngay bây giờ. Tôi có một cơ sở mã nguồn mở chứng minh khái niệm và tôi sẽ chia sẻ những hiểu biết sâu sắc về nó trong suốt bài đăng này.

Vì vậy, điều đầu tiên trước hết là kiến ​​trúc nói về điều gì. Đó là một máy chủ PHP chạy lâu dài, với toàn bộ trạng thái được tải vào bộ nhớ, được xây dựng từ các sự kiện được lưu trữ. Nói cách khác: đó là nguồn cung cấp sự kiện như chúng ta biết trong PHP, nhưng tất cả các tập hợp và phép chiếu đều được tải trong bộ nhớ và không bao giờ được lưu trữ trên đĩa.

Hãy phá vỡ nó!

# Một máy chủ PHP chạy lâu dài

Trụ cột đầu tiên của kiến ​​trúc này là một máy chủ hoạt động lâu dài. Bối cảnh PHP hiện đại cung cấp một số giải pháp đã được thử nghiệm trong thực tế để quản lý các loại quy trình này: các khung như ReactPHP, Amphp và Swoole cho phép cộng đồng PHP dấn thân vào một thế giới khác, chưa được khám phá, trong khi PHP hàng ngày thường liên quan nhiều nhất đến đặc tính của nó chu kỳ yêu cầu/phản hồi nhanh.

Tất nhiên, chu kỳ yêu cầu/phản hồi nhanh này là một trong những điều khiến PHP trở nên tuyệt vời: bạn không bao giờ phải lo lắng về việc rò rỉ trạng thái hoặc giữ mọi thứ đồng bộ: khi có yêu cầu đến, một quy trình PHP sạch sẽ được bắt đầu và ứng dụng của bạn khởi động từ đó. 0. Sau khi gửi phản hồi, ứng dụng sẽ bị hủy hoàn toàn.

Tôi không đề xuất chúng ta loại bỏ hoàn toàn kỹ thuật đã được thử nghiệm trong trận chiến này; chu trình yêu cầu/phản hồi nhanh thực sự là một phần quan trọng của kiến ​​trúc mà tôi sẽ mô tả. Mặt khác, việc luôn khởi động toàn bộ ứng dụng từ đầu cũng có nhược điểm.

Trong kiến ​​trúc tôi đang mô tả, một ứng dụng được chia thành hai phần: một phần là ứng dụng PHP thông thường, chấp nhận các yêu cầu HTTP và tạo phản hồi, trong khi phần còn lại là máy chủ phụ trợ hậu trường luôn chạy. Một máy chủ luôn tải toàn bộ trạng thái ứng dụng vào bộ nhớ, cho phép khách hàng — các ứng dụng PHP thông thường của chúng tôi — kết nối với nó, đọc dữ liệu và lưu trữ các sự kiện.

Vì toàn bộ trạng thái ứng dụng luôn được tải trong bộ nhớ nên bạn không bao giờ cần thực hiện truy vấn cơ sở dữ liệu, tốn tài nguyên để ánh xạ dữ liệu từ cơ sở dữ liệu đến đối tượng hoặc các vấn đề về hiệu suất như tham chiếu vòng giữa các thực thể ORM.

Về mặt lý thuyết, điều này nghe có vẻ hay, nhưng có lẽ chúng ta vẫn cần có khả năng thực hiện các truy vấn phức tạp – điều mà cơ sở dữ liệu được tối ưu hóa cao. Rõ ràng là kiến ​​trúc này sẽ yêu cầu chúng ta phải suy nghĩ lại một số khía cạnh nhất định mà chúng ta đã quen thuộc trong các ứng dụng PHP thông thường. Tôi sẽ quay lại vấn đề này sau.

Đầu tiên, hãy nhìn vào trụ cột thứ hai: tìm nguồn cung ứng sự kiện.

Nhận thấy một tpyo? Bạn có thể gửi PR để sửa nó. Nếu bạn muốn cập nhật về những gì đang diễn ra trên blog này, bạn có thể Theo dõi danh sách gửi thư của tôi: gửi email đến [email protected] và tôi sẽ thêm bạn vào danh sách.

# Tìm nguồn cung ứng sự kiện

Tại sao tôi lại đề xuất biến việc tìm nguồn cung ứng sự kiện thành một phần cốt lõi của kiến ​​trúc này? Bạn rất có thể có một máy chủ chạy lâu dài với tất cả dữ liệu được tải vào bộ nhớ từ cơ sở dữ liệu thông thường.

Chúng ta hãy đi theo con đường đó một lát: giả sử một khách hàng thực hiện một bản cập nhật và gửi nó đến máy chủ phụ trợ. Máy chủ sẽ cần lưu trữ dữ liệu trong cơ sở dữ liệu cũng như làm mới trạng thái trong bộ nhớ của nó. Những hệ thống như vậy sẽ cần phải quan tâm đến việc cập nhật trạng thái ứng dụng đúng cách để mọi thứ đều chính xác sau khi cập nhật.

Cách tiếp cận đơn giản nhất là thực hiện cập nhật trong cơ sở dữ liệu và tải lại toàn bộ trạng thái ứng dụng, điều này trên thực tế là không thể thực hiện được do vấn đề hiệu suất. Một cách tiếp cận khác có thể là theo dõi mọi thứ cần xảy ra khi nhận được bản cập nhật và cách linh hoạt nhất để thực hiện điều đó là sử dụng các sự kiện.

Nếu chúng ta đương nhiên hướng tới một hệ thống hướng sự kiện để đồng bộ hóa trạng thái trong bộ nhớ, thì tại sao lại thêm chi phí lưu trữ mọi thứ trong cơ sở dữ liệu và yêu cầu ORM để ánh xạ dữ liệu trở lại các đối tượng? Đó là lý do tại sao tìm nguồn cung ứng sự kiện là cách tiếp cận tốt hơn: nó tự động giải quyết tất cả các vấn đề đồng bộ hóa trạng thái và mang lại hiệu suất tăng do bạn không phải giao tiếp với cơ sở dữ liệu và làm việc với ORM.

Còn những truy vấn phức tạp thì sao? Ví dụ: bạn sẽ tìm kiếm như thế nào trong một cửa hàng sản phẩm chứa hàng triệu mặt hàng khi mọi thứ đều được tải vào bộ nhớ. PHP không đặc biệt xuất sắc ở những loại nhiệm vụ này. Nhưng một lần nữa, tìm nguồn cung ứng sự kiện lại đưa ra một giải pháp: dự đoán. Bạn hoàn toàn có thể tạo một phép chiếu được tối ưu hóa cho một tác vụ nhất định và thậm chí lưu trữ nó trong cơ sở dữ liệu! Đây có thể là cơ sở dữ liệu SQLite nhẹ trong bộ nhớ hoặc máy chủ MySQL hoặc PostgreSQL toàn diện.

Quan trọng nhất, những cơ sở dữ liệu này không còn là một phần của lõi ứng dụng nữa. Chúng không còn là nguồn gốc của sự thật nữa mà là các công cụ hữu ích nằm ở rìa lõi của ứng dụng và rất có thể so sánh được với việc xây dựng các chỉ mục tìm kiếm được tối ưu hóa như ElasticSearch hoặc Algolia. Bạn có thể hủy các nguồn dữ liệu này bất kỳ lúc nào và xây dựng lại chúng từ các sự kiện được lưu trữ.

Điều đó đưa chúng ta đến lý do cuối cùng tại sao việc tìm nguồn cung ứng sự kiện lại phù hợp tuyệt vời với kiến ​​trúc này. Khi máy chủ yêu cầu khởi động lại — do sự cố máy chủ hoặc sau khi triển khai — tìm nguồn cung ứng sự kiện sẽ cung cấp cho bạn một cách để xây dựng lại trạng thái của ứng dụng nhanh hơn nhiều: ảnh chụp nhanh.

Trong kiến ​​trúc này, ảnh chụp nhanh toàn bộ trạng thái ứng dụng sẽ được lưu trữ một hoặc hai lần một ngày. Đó là điểm mà máy chủ có thể được xây dựng lại từ đó mà không cần phải phát lại tất cả các sự kiện.

Như bạn có thể thấy, có một số lợi ích khi xây dựng một hệ thống có nguồn gốc sự kiện trong kiến ​​trúc này. Bây giờ chúng ta đang chuyển sang trụ cột cuối cùng: khách hàng.

# Khách hàng

Tôi đã đề cập đến điều này trước đây: với “máy khách”, ý tôi là các ứng dụng PHP phía máy chủ giao tiếp với máy chủ phụ trợ tập trung. Chúng là các ứng dụng PHP bình thường, chỉ tồn tại trong một thời gian ngắn trong chu kỳ yêu cầu/phản hồi thông thường.

Bạn có thể sử dụng bất kỳ khung hiện có nào mà bạn muốn cho những máy khách này, miễn là có cách sử dụng máy chủ sự kiện thay vì liên lạc trực tiếp với ví dụ. Một cơ sở dữ liệu. Thay vì sử dụng ORM như Doctrine trong Symfony hoặc Eloquent trong Laravel, bạn sẽ sử dụng một lớp giao tiếp nhỏ để giao tiếp qua socket với máy chủ phụ trợ.

Ngoài ra, hãy nhớ rằng máy chủ phụ trợ và máy khách có thể chia sẻ cùng một cơ sở mã, điều đó có nghĩa là theo quan điểm của nhà phát triển, bạn không cần phải lo lắng về giao tiếp giữa máy khách và máy chủ, việc này được thực hiện một cách minh bạch.

Lấy ví dụ về tài khoản ngân hàng có số dư. Với kiến ​​trúc này, bạn sẽ viết mã như thế này:

final class AccountsController
{
    public function index(): View
    {
        $accounts = Account::all();

        return new View('accounts.index', (
            'accounts' => $accounts,
        ));
    }
}

Hãy nhớ rằng tôi chủ yếu làm việc trong bối cảnh Laravel và tôi đã quen với ORM Eloquent. Nếu bạn thích sử dụng mẫu kho lưu trữ thì cũng không sao.

Đằng sau hậu trường, Account::all() hoặc $accountRepository->all() sẽ không thực hiện các truy vấn cơ sở dữ liệu, thay vào đó chúng sẽ gửi một tin nhắn nhỏ đến máy chủ phụ trợ, máy chủ này sẽ gửi các tài khoản, từ bộ nhớ, quay lại máy khách.

Nếu chúng tôi thực hiện thay đổi đối với số dư tài khoản, việc đó sẽ được thực hiện như sau:

final class BalanceController
{
    public function increase(Account $account, int $amount): Redirect
    {
        $aggregateRoot = AccountAggregateRoot::find($account);
   
        $aggregateRoot->increaseBalance($amount);

        return new Redirect(
            (AccountsController::class, 'index'), 
            ($account)
        );
    }
}

Đằng sau hậu trường, AccountAggregateRoot::increaseBalance() sẽ gửi một sự kiện đến máy chủ, máy chủ sẽ lưu trữ nó và thông báo cho tất cả những người đăng ký có liên quan.

Nếu bạn đang thắc mắc việc triển khai như vậy là gì AccountAggregateRoot có thể trông giống như đây là phiên bản đơn giản hóa:

final class AccountAggregateRootRoot extends AggregateRoot
{
    public function increaseBalance(int $amount): self
    {
        $this->event(new BalanceIncreased($amount));

        return $this;
    }
}

Và cuối cùng đây là điều Account thực thể trông như thế nào. Lưu ý thiếu cấu hình kiểu ORM; đây là những đối tượng PHP trong bộ nhớ đơn giản!

final class Account extends Entity
{
    public string $uuid;

    public string $name;

    public int $balance = 0;
}

Một lưu ý cuối cùng cần thực hiện: hãy nhớ rằng tôi đã đề cập đến chu trình yêu cầu/phản hồi nhanh của PHP thực sự rất quan trọng? Đây là lý do: nếu chúng tôi gửi các bản cập nhật đến máy chủ, chúng tôi không cần phải lo lắng về việc phát lại các bản cập nhật đó cho khách hàng. Mọi khách hàng thường chỉ tồn tại trong một hoặc hai giây, vì vậy không cần phải lo lắng về việc giữ chúng đồng bộ.

#Những nhược điểm

Tất cả những điều này nghe có vẻ thú vị về mặt lý thuyết, nhưng trong thực tế thì sao? Còn hiệu suất thì sao? Bạn sẽ cần bao nhiêu RAM để lưu trữ mọi thứ trong bộ nhớ? Liệu chúng ta có thể tối ưu hóa việc đọc trạng thái bằng cách thực hiện các truy vấn phức tạp không? Ảnh chụp nhanh sẽ được lưu trữ như thế nào? Còn việc lập phiên bản thì sao?

Rất nhiều câu hỏi vẫn chưa được trả lời. Mục tiêu của bài đăng này không phải là cung cấp tất cả các câu trả lời mà là chia sẻ một số suy nghĩ và câu hỏi với bạn, cộng đồng. Ai biết được bạn có thể nghĩ ra điều gì?

Tôi đã đề cập rằng mã cho mã này là mã nguồn mở, bạn có thể xem qua tại đây. Tôi rất mong nhận được phản hồi của bạn trên Reddit thông qua Twitter hoặc e-mail.



Trả lời