Thiết kế hệ thống: Chuẩn bị cho điều tồi tệ nhất

Nov 28 2022
Khi phát triển một ứng dụng mới hoặc một tính năng mới, một trong những điều quan trọng bạn cần chú ý là khả năng tự phục hồi của ứng dụng trong trường hợp lỗi. Mã của bạn có thể tuyệt vời và đáp ứng các yêu cầu kinh doanh NHƯNG, nó có đủ tốt để làm điều đó ngay cả khi gặp lỗi không mong muốn không? Ứng dụng của bạn có đủ khả năng phục hồi để tự động khắc phục những lỗi đó không? Đôi khi, bạn sẽ không được hướng dẫn ngầm để triển khai các cơ chế phục hồi và dự phòng như một phần của các yêu cầu nghiệp vụ vì các tình huống mà cơ chế này giả định xử lý có thể mang tính kỹ thuật và định hướng phát triển rất cao.
Ảnh của David Pupaza trên Bapt

Khi phát triển một ứng dụng mới hoặc một tính năng mới, một trong những điều quan trọng bạn cần chú ý là khả năng tự phục hồi của ứng dụng trong trường hợp lỗi. Mã của bạn có thể tuyệt vời và đáp ứng các yêu cầu kinh doanh NHƯNG, nó có đủ tốt để làm điều đó ngay cả khi gặp lỗi không mong muốn không? Ứng dụng của bạn có đủ khả năng phục hồi để tự động khắc phục những lỗi đó không?

Đôi khi, bạn sẽ không được hướng dẫn ngầm để triển khai các cơ chế phục hồi và dự phòng như một phần của các yêu cầu nghiệp vụ vì các tình huống mà cơ chế này giả định xử lý có thể mang tính kỹ thuật và định hướng phát triển rất cao. Những cơ chế liên quan đến họ yêu cầu phi chức năng.

Những “Thất bại bất ngờ” mà chúng ta đang nói đến là gì?

Chà, có nhiều tình huống khác nhau có thể xảy ra dẫn ứng dụng của bạn đến trạng thái lỗi/không ổn định. Ví dụ:

  • Hết thời gian / Dịch vụ không khả dụng của API bên ngoài mà ứng dụng của bạn đang sử dụng
  • Lỗi trong bên thứ ba đang được ứng dụng của bạn sử dụng
  • Thất bại của hoạt động cơ sở dữ liệu
  • Tải ứng dụng không mong muốn gây suy giảm hiệu suất và do đó từ chối các yêu cầu đến
  • Lỗi không mong muốn gây mất dữ liệu
  • các quy trình tồn tại lâu dài do ứng dụng quản lý bị kẹt ở trạng thái do mất dữ liệu

Có một ứng dụng không có cơ chế tự phục hồi và dự phòng có liên quan sẽ không tồn tại lâu dài. Những “Lỗi không mong muốn” đó cuối cùng sẽ xảy ra, nó sẽ làm hỏng SLA và trải nghiệm khách hàng của bạn. Nó có thể gây mất dữ liệu, khó khôi phục và sẽ yêu cầu BẠN can thiệp thủ công và thực hiện một số phép thuật để khắc phục sự cố. Bây giờ, hãy nghĩ về các ứng dụng xử lý hàng trăm nghìn yêu cầu mỗi phút, lỗi nhỏ có thể gây ra tác động lớn khó có thể khắc phục thủ công và sẽ tốn rất nhiều thời gian và tiền bạc cho tổ chức của bạn.
Vì vậy, bạn phải chuẩn bị cho điều tồi tệ nhất.

Hãy xem xét một số tình huống phổ biến và cung cấp các giải pháp có thể tăng mức độ phục hồi ứng dụng của bạn và khả năng dự phòng để bạn có thể ngủ yên vào ban đêm :)

Cơ chế thử lại

Đặc biệt là khi xử lý các hoạt động I/O như gửi yêu cầu http, đọc/ghi từ cơ sở dữ liệu hoặc tệp, v.v. khả năng bạn gặp lỗi là phổ biến. Ví dụ: Dịch vụ không khả dụng, Lỗi máy chủ nội bộ, Thời gian chờ hoạt động, v.v.
Rất nhiều lỗi trong số này có thể thoáng qua và dừng lại sau vài giây, nghĩa là có khả năng cao là lần thử tiếp theo sau vài giây sẽ hoạt động.
Vậy… tại sao công việc kinh doanh của bạn lại thất bại chỉ vì một vấn đề nhỏ tạm thời? Thử lại! Sử dụng cơ chế thử lại thao tác để đảm bảo rằng nếu sự cố chỉ là tạm thời, ứng dụng của bạn sẽ tự động khắc phục sự cố đó.
Hãy chú ý những điều sau:

  • Có thể định cấu hình và giới hạn số lần thử lại mà ứng dụng của bạn cố gắng thực thi. Số lần thử lại vô hạn/cao có thể khiến ứng dụng của bạn bị kẹt và từ chối các yêu cầu khác vì ứng dụng sẽ bận trong các lần thử lại vô tận
  • thực hiện bakeoff theo cấp số nhân. Tăng theo cấp số nhân khoảng thời gian giữa các lần thử lại.
    Nó sẽ tăng khả năng vấn đề tạm thời (trên máy chủ/cơ sở dữ liệu/bên thứ 3) đã được giải quyết và lần thử tiếp theo của bạn sẽ thành công. Ngoài ra, việc sử dụng khoảng thời gian theo cấp số nhân cũng tạo điều kiện thuận lợi cho bên thứ 3 có thể đang tải và do đó không thể phục vụ khách hàng của bên đó. Việc thực hiện các lần thử lại ngay lập tức mà không có độ trễ có thể khiến vấn đề trở nên tồi tệ hơn.
  • Đảm bảo rằng bạn hiểu lỗi nào nên thử lại và lỗi nào không. Không phải mọi lỗi đều có thể được giải quyết bằng một lần thử lại. Ví dụ: giả sử rằng yêu cầu http của bạn không thành công với mã trạng thái là 'Yêu cầu không hợp lệ' (400). Có nghĩa là một cái gì đó với dữ liệu của yêu cầu là sai và đã bị máy chủ từ chối. Cho dù ứng dụng của bạn có thử lại bao nhiêu lần đi chăng nữa, nó sẽ kết thúc với cùng một kết quả - thất bại.
    Việc thử lại các loại lỗi đó là không cần thiết và sẽ ảnh hưởng đến hiệu suất ứng dụng của bạn. Bạn phải phân biệt giữa các lỗi khác nhau và quyết định xem có thể thử lại lần nào.

Có nhiều thư viện nguồn mở đã cung cấp cho bạn tất cả các tính năng liên quan của cơ chế thử lại, do đó bạn không phải viết lại từ đầu. Ví dụ: trong .NET, bạn có thư viện tên là Polly.

Đừng quên đặt câu hỏi, tôi nên làm gì nếu mọi lần thử lại đều thất bại? Đây là một câu hỏi kinh doanh và câu trả lời có thể được thay đổi giữa các kịch bản.

Xác định thời gian chờ cho các quy trình của bạn

Một số ứng dụng quản lý các quy trình tồn tại lâu dài. Quá trình có trạng thái cho biết tiến độ. Tiến trình của quy trình đôi khi phụ thuộc vào các thao tác kéo dài hoặc phản hồi không đồng bộ từ bên thứ ba. Vì mọi thứ có thể đi sai hướng (và chúng luôn đi sai hướng), quy trình của bạn có thể bị mắc kẹt mãi mãi ở trạng thái không kết thúc và luôn “mở”. Ví dụ: bên thứ 3 gặp lỗi khi gửi lại phản hồi.

Ok, vì vậy họ sẽ tiếp tục mở. Khuyến mại lớn là gì?
Nhiều khả năng là các hệ thống khác phụ thuộc vào các quy trình của bạn và chúng đang chờ kết quả của chúng. Ứng dụng của bạn sẽ làm kẹt toàn bộ quy trình kinh doanh, ảnh hưởng đến SLA của các dịch vụ khác và trải nghiệm người dùng.
Hơn nữa, quy trình mở đó sẽ gây khó khăn cho việc điều tra và phân tích dữ liệu vì chúng hoạt động khác và có thể gây hại cho số liệu thống kê của bạn.

Tôi không muốn điều này! Làm thế nào để tôi giải quyết nó?
Đầu tiên, hãy xác định đâu là thời điểm phù hợp và có thể chấp nhận được mà quy trình của bạn có thể không ở trạng thái cuối cùng. Đây là một câu hỏi kinh doanh và phải được xác định theo SLA mà bạn có nghĩa vụ phải tuân theo và bằng cách đánh giá tổng thời gian hợp lý cần dành cho các hoạt động bên trong quy trình. Ví dụ: nếu quy trình của tôi phụ thuộc vào bên thứ 3 có thời gian phản hồi trung bình là 10 phút, thì bạn có thể xác định thời gian chờ là 20–30 phút.
Bây giờ bạn có thể bắt đầu triển khai. Việc triển khai cơ bản có thể là tạo một hoạt động đã lên lịch chạy X phút một lần và tìm kiếm các quy trình đang mở hơn Y phút (giá trị thời gian chờ). Nếu tìm thấy, chỉ cần chuyển quá trình sang trạng thái cuối cùng, có thể là trạng thái thất bại/trạng thái hủy bỏ.
Bằng cách này, bạn có khả năng thất bại/hủy bỏ quy trình một cách duyên dáng. Nó đang được thực hiện theo cách có thể quản lý được và cung cấp cho ứng dụng của bạn khả năng thông báo kết quả có liên quan cho các ứng dụng khách đang chờ xử lý của nó.

Để ý! mẫu không tự giải quyết được vấn đề NHƯNG nó cung cấp cho bạn khả năng tự động quản lý và thực thi quy trình dự phòng. Có thể thông báo rằng có vấn đề và tuân thủ SLA đã hứa.
Hãy xem xét một tình huống thực tế sử dụng mẫu này:
Nhóm của tôi trong Payoneer chịu trách nhiệm về quy trình phê duyệt tự động các tài liệu của khách hàng. Quy trình phê duyệt tài liệu là một quy trình ngoại tuyến được xây dựng gồm nhiều bước và liên quan đến việc tích hợp với các nhà cung cấp bên ngoài cung cấp kết quả phân tích thông qua giao tiếp không đồng bộ.
Hệ thống của chúng tôi triển khai mẫu hết thời gian chờ và khi hết thời gian chờ, hệ thống sẽ chuyển tài liệu của khách hàng để người đại diện xem xét thủ công thay vì chờ kết quả tự động.
Bằng cách này, chúng tôi không ảnh hưởng đến SLA mà chúng tôi có trước mặt khách hàng về thời gian cần thiết để xem xét tài liệu của họ.

Không chuyển tiếp khi chỉ gọi lại/webhook- Triển khai cơ chế bỏ phiếu

Khi xử lý giao tiếp không đồng bộ, phản hồi sẽ được trả về máy khách dưới dạng gọi lại. Đó có thể là máy chủ kích hoạt webhook có dữ liệu hoặc máy chủ xuất bản một sự kiện thông qua trình trung gian thông báo/xe buýt sự kiện.
Có những tình huống khi khách hàng không nhận được tin nhắn hoặc đã nhận được nhưng không được xử lý đúng cách và do đó bị hủy dẫn đến mất dữ liệu. Trong những trường hợp đó, máy khách vẫn đang chờ phản hồi nhưng máy chủ đã gửi nó và sẽ không gửi lại.

trong phần trước, chúng tôi đã đề cập đến mẫu thời gian chờ không giải quyết được sự cố nhưng cho phép bạn xử lý sự cố một cách dễ dàng và kích hoạt luồng dự phòng có thể quản lý được. Bằng cách triển khai cơ chế thăm dò ý kiến, bạn sẽ có thể tự động giải quyết một số vấn đề và duy trì “con đường hạnh phúc” của quy trình kinh doanh.

Thay vì chỉ đợi máy chủ cung cấp cho bạn phản hồi không đồng bộ bằng cách đẩy, hãy triển khai một quy trình đã lên lịch sẽ được thực hiện sau X phút một lần và sẽ tìm kiếm yêu cầu đang chờ xử lý hơn Y phút. Đối với những yêu cầu đó, ứng dụng của bạn sẽ gửi yêu cầu đồng bộ đến máy chủ để nhận dữ liệu phản hồi. Nếu yêu cầu ban đầu đã hoàn tất để được xử lý bởi máy chủ, nó sẽ có thể gửi cho bạn kết quả. Trong trường hợp này, ứng dụng của bạn sẽ tiếp tục với quy trình công việc giống như trước đây nếu webhook nhận được dữ liệu. Mặt khác, yêu cầu ban đầu vẫn đang được máy chủ xử lý và không có thông báo gửi lại. Bạn chỉ cần tiếp tục chờ đợi.

Bằng cách triển khai cơ chế này, bạn có thể tự động giải quyết các sự cố không mong muốn liên quan đến giao tiếp không đồng bộ và nhờ đó tránh làm hỏng luồng hoặc can thiệp thủ công để khôi phục dữ liệu từ máy chủ.

Để ý! Không phải tất cả các dịch vụ mà bạn sẽ tích hợp sẽ có khả năng cung cấp cho bạn các điểm cuối để bỏ phiếu ngoài giao tiếp không đồng bộ mà chúng cung cấp. Nếu họ không có thì đáng để hỏi liệu điều đó có thể được thực hiện trong tương lai hay không.

Đúng là có chi phí hoạt động với giải pháp này NHƯNG với việc chọn đúng khoảng thời gian, giải pháp sẽ liền mạch và lợi ích mà giải pháp này mang lại khi gặp sự cố là vô giá.

Tránh khóa nhà cung cấp

Thông thường, ứng dụng sử dụng các nhà cung cấp bên ngoài để đạt được các mục tiêu kinh doanh của nó. Không thể nào mọi tổ chức sẽ phát triển mọi phần logic trong nội bộ, đặc biệt nếu logic đó phức tạp và khác xa với hoạt động kinh doanh chính của tổ chức. Do đó, bạn có thể sẽ muốn tích hợp với một nhà cung cấp có thể cung cấp cho bạn một phần logic mà bạn cần để hoàn thành quy trình kinh doanh của mình.

Vì vậy, sau khi nghiên cứu và POC, bạn đã tìm thấy một nhà cung cấp phù hợp cho nhu cầu của mình.
Quá trình tích hợp diễn ra suôn sẻ và… VOILA! bạn đã thiết lập và vận hành công việc kinh doanh của mình, phục vụ hàng trăm nghìn khách hàng mỗi ngày.

Giờ để tôi hỏi bạn một câu hỏi. Điều gì sẽ xảy ra nếu một ngày nào đó nhà cung cấp bên ngoài này ngừng hoạt động? điều gì sẽ xảy ra nếu nó phá sản? Nếu nó bị hack và dịch vụ sẽ không khả dụng trong một thời gian dài? Hoặc có thể bạn sẽ không hài lòng với hiệu suất của nhà cung cấp hoặc có thể từ cách nó được quản lý vì hàng tháng đều có những thay đổi đột phá mới khiến nhóm phát triển của bạn phải làm việc suốt ngày đêm để điều chỉnh tích hợp?

Đặc biệt là khi xử lý các luồng kinh doanh chính và nhạy cảm, tình huống này rất nghiêm trọng và có thể làm tê liệt một phần tổ chức của bạn và có tác động lớn đến khách hàng của bạn.
Quay lại quy trình phê duyệt tự động các tài liệu của khách hàng do nhóm của tôi quản lý trong Payoneer, như một phần của quy trình, chúng tôi đang sử dụng các nhà cung cấp bên ngoài để trích xuất văn bản có liên quan từ tài liệu và xử lý nó. Chúng tôi đã chọn tích hợp với 2 nhà cung cấp cung cấp cho chúng tôi kết quả này và có mức độ ưu tiên giữa họ, vì vậy nếu một nhà cung cấp không thành công hoặc có nhiều thông tin sai lệch, chúng tôi có thể chuyển sang nhà cung cấp khác mà không mất thời gian ngừng hoạt động của quy trình kinh doanh. Tất nhiên, điều này cũng mang lại cho chúng tôi niềm tin rằng nếu có điều gì đó xảy ra với một trong số họ, chúng tôi luôn có một người khác để tiếp tục.

Tất nhiên không phải lúc nào bạn cũng phải tạo bản sao lưu này. Đó là câu hỏi về giá trị đồng tiền và mức độ quan trọng của luồng kinh doanh mà bạn đang giải quyết.

Mô hình ngắt mạch

Không giống như mẫu thử lại giả sử giải quyết các lỗi tạm thời, mẫu ngắt mạch ở đây để ngăn ứng dụng gọi hoạt động có khả năng bị lỗi cao.
Hãy nghĩ rằng bạn đang sử dụng bên thứ 3 thông qua API REST hiện có lỗi khiến tất cả yêu cầu không thành công. Giả sử thời gian ETA để khắc phục là 4 giờ.
Với mẫu thử lại, ứng dụng sẽ cố gắng gọi yêu cầu nhiều lần và cuối cùng sẽ không thành công. Những lần thử lại vô ích đó sẽ ảnh hưởng đến hiệu suất của ứng dụng và của bên thứ ba.
Thay vào đó, mô hình ngắt mạch sẽ cho phép ứng dụng bị lỗi nhanh và cố gắng khôi phục dần dần khi sự cố được giải quyết. Bằng cách đó tránh tăng lỗi và suy giảm hiệu suất.

Vì vậy, làm thế nào nó hoạt động?
Cơ chế hoạt động như một đại diện cho hoạt động mà bạn muốn thực hiện.
Nó có 3 trạng thái:

  • Đã đóng- Có nghĩa là hoạt động được phép thực hiện thường xuyên.
    Số lần thất bại đang được tính trong trạng thái này. Nếu số lượng lỗi vượt qua ngưỡng xác định trong một khoảng thời gian xác định, cơ chế sẽ chuyển sang trạng thái mở.
  • Mở - Có nghĩa là cơ chế sẽ ngăn hoạt động được thực thi vì nó đã trải qua nhiều lần thất bại và do đó, nó sẽ thích thất bại nhanh hơn mà không cố gắng thực thi nó. Ở trạng thái này, chúng tôi có thời gian chờ. Khi đạt đến, cơ chế sẽ chuyển sang trạng thái Mở một nửa.
  • Mở một nửa- Có nghĩa là một yêu cầu giới hạn để thực hiện thao tác được cho phép và được thông qua. Trạng thái này là để kiểm tra xem sự cố đã được giải quyết chưa. Nếu số lượng hoạt động hạn chế đó đang được thực hiện thành công, cơ chế sẽ giả định rằng sự cố đã được giải quyết và chuyển trạng thái của cơ chế thành đóng. Nếu không, nó sẽ quay trở lại Mở vì sự cố vẫn tồn tại.

Điểm cuối chuyên dụng để can thiệp thủ công dễ dàng và hiệu quả

Đã nói tất cả những điều đó, đôi khi chúng tôi không chuẩn bị sẵn giải pháp tự động hóa cho bất kỳ trường hợp nào có thể xảy ra. Đôi khi, giải pháp tự động của chúng tôi sẽ chỉ xuất hiện sau khi chúng tôi gặp tình huống lần đầu tiên và hiểu cách giải quyết nó bằng tự động hóa. Do đó, trong những trường hợp đó, can thiệp thủ công là không thể tránh khỏi. Hầu hết thời gian, các can thiệp thủ công này yêu cầu bạn phải trải qua “phần chính” của dịch vụ như: thay đổi trực tiếp các giá trị thông qua cơ sở dữ liệu hoặc, cấu trúc lại một tin nhắn theo cách thủ công và gửi trực tiếp qua trình môi giới tin nhắn.

Cũng tại đây, chúng tôi có thể chuẩn bị trước để việc can thiệp thủ công này có thể quản lý được, an toàn và hiệu quả. Chỉ cần tạo một API nội bộ trong ứng dụng của bạn để kích hoạt các thao tác thủ công phổ biến đó.
Ví dụ: tạo một điểm cuối cho phép bạn hủy một quy trình mà ứng dụng của bạn đang quản lý. Hoặc tạo một điểm cuối cho phép xuất bản lại kết quả của một quy trình cũ để hệ thống khác có thể sử dụng lại nếu nó bị mất hoặc không bao giờ được kích hoạt.

Bằng cách tạo API nội bộ này, mặc dù nó yêu cầu ai đó kích hoạt nó, nhưng nó có rất nhiều lợi ích:

  • Các hoạt động có thể quản lý được bởi ứng dụng. Ứng dụng này là chủ sở hữu của sự thay đổi và nó nhận thức được điều đó.
  • Hoạt động đã được kiểm tra bởi QA vì nó là một phần của quá trình phát triển. Vì vậy, bạn giảm cơ hội cho những sai lầm
  • đôi khi thao tác không đơn giản như chỉ cập nhật giá trị trong DB. Đôi khi cũng có những hành động phụ cần được thực hiện. Tất cả những hành động phụ đó có thể được quản lý theo API nội bộ để dễ sử dụng.
  • Tiết kiệm thời gian

Để kết luận

Ứng dụng của bạn PHẢI luôn được chuẩn bị cho điều tồi tệ nhất!
Ứng dụng của bạn càng biết cách tự phục hồi và có các giải pháp dự phòng, hiệu suất của ứng dụng sẽ càng tăng, tác động đến trải nghiệm của khách hàng sẽ thấp, bạn càng có thể khắc phục lỗi nhanh hơn và tiết kiệm thời gian và tiền bạc quý giá cho tổ chức của mình.

Trong quá trình thiết kế ứng dụng/tính năng của bạn, bạn phải chú ý đến điều này. Bạn phải đặt câu hỏi đúng và xác định những điểm yếu để chuẩn bị cách xử lý phù hợp và tránh những bất ngờ khó chịu trong sản xuất.