1. Temporal

Trong thực tế chúng ta sẽ gặp những tình huống mà hệ thống cần phải thực hiện các tác vụ phức tạp và tác vụ cần phải được đảm bảo thực thi một cách an toàn, đáng tin cậy. Để đảm bảo trạng thái được xử lý an toàn đáng tin cậy trong hệ thống phân tán đòi hỏi thiết kế tốt, cũng như công sức xây dựng hệ thống là tốn kém. Khi làm việc với hệ thống phân tán, các developer sẽ phải giải quyết những vấn đề sau: (Reliable execution, Code structure, State visibility)

  • Ứng dụng phân tán đáng tin cậy
  • Mô hình phát triển năng suất và cấu trúc mã nguồn cho các ứng dụng phân tán.
  • Trạng thái của ứng dụng, tác vụ có thể được kiểm soát, thể hiện một cách dễ dàng.

Để hiểu rõ hơn về Temporal, chúng ta hãy xem các vấn đề với các kiến trúc, pattern quen thuộc sau trong hệ thống phân tán nều như không sử dụng, và sử dụng Temporal.

Don’t fight dev patterns, ship code.

Event-Driven Architectures

Trong kiến trúc hướng sự kiện, chúng ta sẽ thường thấy có các message queu nhằm giúp cho các component có thể giao tiếp với nhau, cũng như tăng khả năng scale của hệ thống.

Tuy nhiên để quản lý trạng thái các service cũng như chia sẻ trạng thái giữa các service là phức tạp. Để giảm thiểu sự phức tạp này Temporal sẽ giúp chúng ta làm được điều này.

State Machines

Đây là một pattern rất mạnh mẽ, nó giúp các deverlop xây dựng logic của ứng dụng một cách trong sáng. Tuy nhiên khi hệ thống có thêm state mới, cũng như logic ngày càng phức tạp thì việc kiểm thử và bảo trì sẽ có thể là một vấn đề khó khăn trong tương lai.

Khi làm việc với Temporal thi trạng thái của flow cũng như function sẽ được Temporal tự động ghi lại, truy dấu. Do đó bạn có thể đơn giản hóa flow cũng như state machine.

Scheduled Jobs & Cron

Cron là một quy trình phần lớn thủ công, có thể không đáng tin cậy và không cung cấp khả năng kiểm soát quá trình thực thi. Với Temporal bạn có thể thay thế Cron bằng Workflow theo lịch trình để được thực thi một cách đáng tin cậy. Bạn có thể start or stop, thậm chí thiết lập các trigger để thực thi.

Có vẻ Temporal là một công cụ, cũng như giải pháp rất mạnh mẽ.

Trước khi đi vào ví dụ tôi muốn giới thiệu với các bạn về khái niệm “durable execution”, đây là khái niệm được sử dụng rất nhiều tại các công ty lớn như Stripe, Netflix, Coinbase, HashiCorp, và các công ty khác trong việc xử lý các vấn đề trong hệ thống phân tán.

Như các bạn đã biết với hệ thống monolithic thì hệ thống là một ứng dụng duy nhất được xây dựng bởi một cơ sở dữ liệu duy, chúng ta không có nhiều mối lo ngại như hệ thống phân tán. Khi làm việc với những transaction, chúng ta có thể dễ dàng duy trì trạng thái các giao dịch một cách chính xác, hoặc triển khai những phương thức phòng tránh lỗi một cách đơn giản như dưới đây:

  • Nếu Client không thể kết nối với Server, Client sẽ thực hiện retry.
  • Nếu Client truy cập được tới Server nhưng Server không thể truy cập được Database thì Server sẽ phản hồi bằng lỗi và Client sẽ thử lại.
  • Nếu Server truy cập cơ sở dữ liệu nhưng giao dịch không thành công, thì Server thông báo lỗi và sau đó Client sẽ thử lại.
  • Nếu giao dịch thành công nhưng máy chủ ngừng hoạt động trước khi phản hồi tới Client, thì Client sẽ thử lại cho đến khi Server động trở lại và giao dịch không thành công lần thứ hai (giả sử giao dịch có một số kiểm tra – như mã timeout – để biết liệu bản cập nhật có được thực hiện hay không). đã được áp dụng) và Server thông báo cho Client rằng hành động đã được thực hiện.

Tuy nhiên, khi mà trạng thái của ứng dụng được lưu trữ ở nhiều nơi, dịch vụ thì mọi chuyện sẽ trở nên phức tạp. Ví dụ chúng ta thực hiện quá trình thanh toán cho đơn hàng và sau đó update vào hệ thống, chúng ta có thể không thể viết mã đơn giản như trước đây nữa:

function handleRequest() {
  paymentAPI.chargeCard()
  database.insertOrder()
  return 200
}

Đoạn code trên chứa rất nhiều nguy cơ tiềm ẩn. Nếu bước đầu tiên (thanh toán) thành công nhưng bước thứ hai (thêm đơn hàng vào cơ sở dữ liệu) không thành công thì hệ thống sẽ rơi vào trạng thái không nhất quán; chúng ta đã tính tiền vào thẻ của người dùng nhưng không có hồ sơ nào về việc đó trong cơ sở dữ liệu của chúng ta. Để duy trì tính nhất quán, chúng ta có thể thử lại bước thứ hai cho đến khi có thể truy cập được cơ sở dữ liệu. Tuy nhiên, cũng có thể quá trình thực hiện sẽ không thành công, trong trường hợp đó chúng ta sẽ không biết rằng bước đầu tiên đã diễn ra. Để khắc phục điều này, chúng ta có thể phải xử lý như sau:

Step 1: Persist the order details (ghi lại chi tiết order của người dùng) Step 2: Persist which steps of the program we’ve completed (lưu trữ lại chi tiết các bước mà chương trình, workflow đã thực thi) Step 3: Run a worker process that checks the database for incomplete orders and continues with the next step (Triển khai một worker để kiểm tra trạng thái chưa hoàn thành của order để xử lý những bước tiếp theo)

Tuy nhiên để đạt được 3 steps trên đòi hỏi một nỗ lực viết code, cũng như thiết kế phức tạp hơn rất nhiều. Và đây là lúc chúng ta cần đến durable execution.

2. Durable execution

Durable execution là cách mà hệ thống chạy mã của chúng ta một cách liên tục, chúng thực hiện việc lưu trữ mỗi bước mà code thực hiện. Nếu proces hoặc container run code bị tắt, thì hệ thống này sẽ tự động tiếp tục chạy trong một quá trình khác với tất cả trạng thái được bảo toàn, bao gồm cả stack gọi và local variable. Việc thực thi bền vững đảm bảo rằng mã được thực thi đến khi hoàn thành, bất kể phần cứng có đáng tin cậy đến đâu hoặc dịch vụ dưới luồng bị ngừng hoạt động trong bao lâu. Durable execution làm cho việc triển khai các mẫu hệ thống phân tán như event-driven architecture, task queues, sagas, circuit breakers, and transactional outboxes trở nên dễ dàng hoặc không cần thiết.

3. Example

Chúng ta hãy xem xét một ví dụ sau đây

Trong ví dụ này chúng ta sẽ làm quen với khái niệm Workflow và Activity. Như các bạn đã biết thì khi người dùng thực hiện chuyển tiền từ tài khoản A -> B thì hệ thống sẽ cần thực hiện một transaction như trừ số tiền ở tài khoản A, sau đó cập nhật lại số dư ở tài khoản B. Tuy nhiên nếu có bất kỳ lỗi nào xảy ra trong giao dịch thì hệ thống cần phải đảm bảo tính toàn vẹn, cũng như giao dịch có thể được hủy bỏ hay thực thi lại một cách an toàn.

Trong ví dụ này giả sử chúng ta sẽ thực hiện tác vụ lần lượt là “withdraw” –> “deposit” functions.

Việc chạy ví dụ này rất dễ dàng, bạn hãy làm theo các bước sau, tham khảo https://github.com/temporalio/samples-typescript. Khi khởi chạy server local thành công, chúng ta có thể quan sát thấy workflow được hiển thị như sau:

Chúng ta cũng có thể xem chi tiết trạng thái của follow:

Trong trường hợp task bị faile, chúng ta cũng có thể dễ dàng quan sát

Qua ví dụ trên chúng ta có thể thấy Temporal là công cụ rất mạnh mẽ, chúng ta có thể tóm lược trong những điểm sau:

  • Temporal cung cấp cho bạn khả năng hiển thị đầy đủ về trạng thái quy trình (workflow) làm việc và thực thi mã của bạn.
  • Temporal duy trì trạng thái Quy trình làm việc của bạn, ngay cả khi máy chủ ngừng hoạt động và xảy ra lỗi.
  • Temporal cho phép bạn cấu hình thời gian timeout và thử lại task của bạn cách sử dụng các tùy chọn bên ngoài logic nghiệp vụ của bạn.
  • Temporal cho phép bạn thực hiện “live debug” logic của ứng dụng của ban, trong khi workflow vẫn đang được thực thi.

Refer: https://temporal.io/blog/building-reliable-distributed-systems-in-node