Lỗi trong hệ thống phần mềm là điều không thể tránh khỏi. Cách xử lý những lỗi này có thể ảnh hưởng đáng kể đến hiệu suất, độ tin cậy của hệ thống và kết quả kinh doanh của doanh nghiệp. Trong bài đăng này, tôi muốn thảo luận về mặt trái của sự thất bại. Tại sao bạn nên tìm kiếm thất bại, tại sao thất bại là tốt và tại sao việc tránh thất bại có thể làm giảm độ tin cậy của ứng dụng của bạn. Chúng ta sẽ bắt đầu với cuộc thảo luận về thất bại nhanh và an toàn; điều này sẽ đưa chúng ta đến cuộc thảo luận thứ hai về những thất bại nói chung.
Một lưu ý phụ, nếu bạn thích nội dung của bài đăng này và các bài đăng khác trong loạt bài này, hãy xem bài viết của tôi
Hệ thống không nhanh được thiết kế để ngừng hoạt động ngay lập tức khi gặp phải tình trạng không mong muốn. Lỗi ngay lập tức này giúp phát hiện lỗi sớm, giúp việc gỡ lỗi trở nên đơn giản hơn.
Cách tiếp cận không nhanh đảm bảo rằng lỗi được phát hiện ngay lập tức. Ví dụ: trong thế giới ngôn ngữ lập trình, Java thể hiện cách tiếp cận này bằng cách tạo ra NullPointerException
ngay lập tức khi gặp giá trị null
, dừng hệ thống và làm rõ lỗi. Phản hồi ngay lập tức này giúp các nhà phát triển xác định và giải quyết vấn đề một cách nhanh chóng, ngăn chặn chúng trở nên nghiêm trọng hơn.
Bằng cách phát hiện và ngăn chặn lỗi sớm, các hệ thống xử lý lỗi nhanh sẽ giảm nguy cơ xảy ra lỗi xếp tầng, trong đó lỗi này dẫn đến lỗi khác. Điều này giúp ngăn chặn và giải quyết các vấn đề dễ dàng hơn trước khi chúng lan rộng ra toàn hệ thống, duy trì sự ổn định chung.
Thật dễ dàng để viết các bài kiểm tra đơn vị và tích hợp cho các hệ thống gặp lỗi nhanh. Ưu điểm này càng rõ ràng hơn khi chúng ta cần hiểu rõ về lỗi thử nghiệm. Các hệ thống không nhanh thường chỉ thẳng vào vấn đề trong dấu vết ngăn xếp lỗi.
Tuy nhiên, các hệ thống không nhanh có rủi ro riêng, đặc biệt là trong môi trường sản xuất:
Các hệ thống an toàn có cách tiếp cận khác, nhằm mục đích phục hồi và tiếp tục hoạt động ngay cả khi đối mặt với các điều kiện không mong muốn. Điều này làm cho chúng đặc biệt phù hợp với môi trường không chắc chắn hoặc dễ biến động.
Dịch vụ vi mô là một ví dụ điển hình về các hệ thống không an toàn, có khả năng phục hồi thông qua kiến trúc của chúng. Bộ ngắt mạch, cả dựa trên vật lý và phần mềm, ngắt kết nối chức năng bị lỗi để ngăn ngừa lỗi xếp tầng, giúp hệ thống tiếp tục hoạt động.
Hệ thống an toàn đảm bảo rằng hệ thống có thể tồn tại ngay cả trong môi trường sản xuất khắc nghiệt, giảm nguy cơ xảy ra lỗi nghiêm trọng. Điều này khiến chúng đặc biệt phù hợp với các ứng dụng quan trọng, chẳng hạn như trong thiết bị phần cứng hoặc hệ thống hàng không vũ trụ, nơi việc khôi phục trơn tru sau lỗi là rất quan trọng.
Tuy nhiên, hệ thống không an toàn có nhược điểm:
Thật khó để xác định cách tiếp cận nào tốt hơn vì cả hai đều có giá trị riêng. Các hệ thống không nhanh có khả năng gỡ lỗi ngay lập tức, giảm nguy cơ xảy ra lỗi xếp tầng cũng như phát hiện và giải quyết lỗi nhanh hơn. Điều này giúp phát hiện và khắc phục sự cố sớm, ngăn chúng lây lan.
Các hệ thống an toàn xử lý lỗi một cách khéo léo, khiến chúng phù hợp hơn với các hệ thống quan trọng và môi trường không ổn định, nơi những lỗi nghiêm trọng có thể tàn phá.
Để tận dụng điểm mạnh của từng phương pháp, một chiến lược cân bằng có thể có hiệu quả:
Một cách tiếp cận cân bằng cũng yêu cầu triển khai rõ ràng và nhất quán trong suốt quá trình mã hóa, đánh giá, tạo công cụ và thử nghiệm, đảm bảo nó được tích hợp liền mạch. Fail-fast có thể tích hợp tốt với khả năng điều phối và quan sát. Thực tế, điều này chuyển khía cạnh không an toàn sang một lớp OPS khác thay vì vào lớp nhà phát triển.
Đây là nơi mà mọi thứ trở nên thú vị. Đây không phải là việc lựa chọn giữa thất bại an toàn và thất bại nhanh chóng. Đó là về việc chọn lớp phù hợp cho họ. Ví dụ: nếu một lỗi được xử lý ở lớp sâu bằng cách sử dụng phương pháp không an toàn thì lỗi đó sẽ không được chú ý. Điều này có thể ổn, nhưng nếu lỗi đó có tác động bất lợi (hiệu suất, dữ liệu rác, hỏng hóc, bảo mật, v.v.), thì sau này chúng ta sẽ gặp sự cố và không có manh mối.
Giải pháp phù hợp là xử lý tất cả các lỗi trong một lớp duy nhất, trong các hệ thống hiện đại, lớp trên cùng là lớp OPS và nó có ý nghĩa nhất. Nó có thể báo cáo lỗi cho các kỹ sư có trình độ cao nhất để xử lý lỗi. Nhưng chúng cũng có thể cung cấp biện pháp giảm thiểu ngay lập tức, chẳng hạn như khởi động lại dịch vụ, phân bổ tài nguyên bổ sung hoặc hoàn nguyên phiên bản.
Gần đây, tôi đang tham dự một bài giảng nơi các diễn giả liệt kê kiến trúc đám mây cập nhật của họ. Họ đã chọn đi đường tắt đến microservices bằng cách sử dụng một framework cho phép họ thử lại trong trường hợp thất bại. Thật không may, thất bại không diễn ra theo cách chúng ta mong muốn. Bạn không thể loại bỏ nó hoàn toàn chỉ bằng cách thử nghiệm. Thử lại không an toàn. Trong thực tế, nó có thể có nghĩa là thảm họa.
Họ đã thử nghiệm hệ thống của mình và "nó hoạt động", ngay cả trong quá trình sản xuất. Nhưng giả sử rằng một tình huống thảm khốc xảy ra, cơ chế thử lại của họ có thể hoạt động như một cuộc tấn công từ chối dịch vụ đối với máy chủ của chính họ. Số lượng các cách mà các kiến trúc đặc biệt như thế này có thể thất bại là điều đáng kinh ngạc.
Điều này đặc biệt quan trọng khi chúng ta xác định lại thất bại.
Lỗi trong hệ thống phần mềm không chỉ là do sự cố. Sự cố có thể được coi là một sự cố đơn giản và ngay lập tức, nhưng có nhiều vấn đề phức tạp hơn cần xem xét. Trên thực tế, các vụ tai nạn trong thời đại container có lẽ là thất bại lớn nhất. Hệ thống khởi động lại liền mạch mà hầu như không bị gián đoạn.
Tham nhũng dữ liệu nghiêm trọng và nguy hiểm hơn nhiều so với sự cố. Nó mang theo những hậu quả lâu dài. Dữ liệu bị hỏng có thể dẫn đến các vấn đề về bảo mật và độ tin cậy khó khắc phục, đòi hỏi phải làm lại trên diện rộng và dữ liệu có khả năng không thể phục hồi được.
Điện toán đám mây đã dẫn đến các kỹ thuật lập trình phòng thủ, như ngắt mạch và thử lại, nhấn mạnh vào việc kiểm tra và ghi nhật ký toàn diện để phát hiện và xử lý lỗi một cách khéo léo. Theo một cách nào đó, môi trường này đã đưa chúng tôi trở lại về mặt chất lượng.
Một hệ thống không hoạt động nhanh ở cấp độ dữ liệu có thể ngăn điều này xảy ra. Việc giải quyết một lỗi không chỉ là một cách sửa lỗi đơn giản. Nó đòi hỏi phải hiểu nguyên nhân cốt lõi của nó và ngăn ngừa tái diễn, mở rộng sang ghi nhật ký, kiểm tra và cải tiến quy trình toàn diện. Điều này đảm bảo rằng lỗi được xử lý triệt để, giảm khả năng lỗi tái diễn.
Nếu đó là lỗi trong quá trình sản xuất, bạn có thể nên hoàn nguyên nếu không thể hoàn nguyên sản xuất ngay lập tức. Điều này luôn có thể thực hiện được và nếu không, đây là điều bạn nên tiếp tục.
Các lỗi phải được hiểu đầy đủ trước khi tiến hành sửa chữa. Ở công ty của mình, tôi thường bỏ qua bước đó do áp lực, ở một công ty khởi nghiệp nhỏ thì điều đó có thể tha thứ được. Ở các công ty lớn hơn, chúng ta cần hiểu nguyên nhân gốc rễ. Văn hóa giải đáp các lỗi và các vấn đề trong sản xuất là điều cần thiết. Bản sửa lỗi cũng phải bao gồm việc giảm thiểu quy trình nhằm ngăn chặn các sự cố tương tự tiếp cận sản xuất.
Hệ thống không nhanh dễ gỡ lỗi hơn nhiều. Chúng vốn có kiến trúc đơn giản hơn và việc xác định vấn đề trong một khu vực cụ thể sẽ dễ dàng hơn. Điều quan trọng là phải đưa ra các ngoại lệ ngay cả đối với những vi phạm nhỏ (ví dụ: xác nhận). Điều này ngăn chặn các loại lỗi xảy ra liên tục trong các hệ thống lỏng lẻo.
Điều này cần được thực thi thêm bằng các bài kiểm tra đơn vị nhằm xác minh các giới hạn mà chúng tôi xác định và xác minh các trường hợp ngoại lệ thích hợp được đưa ra. Nên tránh thử lại trong mã vì chúng khiến việc gỡ lỗi trở nên cực kỳ khó khăn và vị trí thích hợp của chúng là trong lớp OPS. Để tạo điều kiện thuận lợi hơn nữa, thời gian chờ phải ngắn theo mặc định.
Thất bại không phải là thứ chúng ta có thể tránh, dự đoán hoặc kiểm tra đầy đủ. Điều duy nhất chúng ta có thể làm là giảm nhẹ đòn khi thất bại xảy ra. Thông thường, việc "làm mềm" này đạt được bằng cách sử dụng các thử nghiệm kéo dài nhằm tái tạo các điều kiện khắc nghiệt nhiều nhất có thể với mục tiêu tìm ra điểm yếu trong ứng dụng của chúng tôi. Điều này hiếm khi đủ. Các hệ thống mạnh mẽ thường cần phải sửa lại các thử nghiệm này dựa trên các lỗi sản xuất thực tế.
Một ví dụ tuyệt vời về tính năng an toàn dự phòng sẽ là bộ đệm chứa các phản hồi REST cho phép chúng tôi tiếp tục hoạt động ngay cả khi dịch vụ không hoạt động. Thật không may, điều này có thể dẫn đến các vấn đề phức tạp trong niche như ngộ độc bộ đệm hoặc tình huống trong đó người dùng bị cấm vẫn có quyền truy cập do bộ đệm.
Fail-safe chỉ được áp dụng tốt nhất trong sản xuất/dàn dựng và trong lớp OPS. Điều này làm giảm số lượng thay đổi giữa sản xuất và nhà phát triển, chúng tôi muốn chúng giống nhau nhất có thể, tuy nhiên đó vẫn là một thay đổi có thể tác động tiêu cực đến sản xuất. Tuy nhiên, lợi ích là rất lớn vì khả năng quan sát có thể mang lại bức tranh rõ ràng về các lỗi hệ thống.
Cuộc thảo luận ở đây có chút khác biệt với kinh nghiệm gần đây của tôi về việc xây dựng kiến trúc đám mây có thể quan sát được. Tuy nhiên, nguyên tắc tương tự cũng áp dụng cho bất kỳ loại phần mềm nào, dù được nhúng hay trên đám mây. Trong những trường hợp như vậy, chúng tôi thường chọn triển khai tính năng không an toàn trong mã, trong trường hợp này, tôi khuyên bạn nên triển khai nó một cách nhất quán và có ý thức trong một lớp cụ thể.
Ngoài ra còn có một trường hợp đặc biệt về các thư viện/khuôn khổ thường cung cấp các hành vi không nhất quán và được ghi chép sai trong những tình huống này. Bản thân tôi cũng mắc phải lỗi thiếu nhất quán như vậy trong một số công việc của mình. Đó là một sai lầm dễ mắc phải.
Đây là bài đăng cuối cùng của tôi về lý thuyết gỡ lỗi, một phần trong cuốn sách/khóa học về gỡ lỗi của tôi. Chúng ta thường nghĩ việc gỡ lỗi là hành động chúng ta thực hiện khi có lỗi xảy ra. Không phải vậy. Việc gỡ lỗi bắt đầu ngay khi chúng ta viết dòng mã đầu tiên. Chúng ta đưa ra những quyết định sẽ ảnh hưởng đến quá trình gỡ lỗi khi viết mã, thường thì chúng ta không biết về những quyết định này cho đến khi gặp lỗi.
Tôi hy vọng bài đăng và loạt bài này sẽ giúp bạn viết mã được chuẩn bị cho những điều chưa biết. Về bản chất, việc gỡ lỗi là giải quyết những điều không mong đợi. Các bài kiểm tra không thể giúp đỡ. Nhưng như tôi đã minh họa trong các bài viết trước của mình, có nhiều phương pháp thực hành đơn giản mà chúng ta có thể thực hiện để giúp việc chuẩn bị dễ dàng hơn. Đây không phải là quá trình diễn ra một lần mà là một quá trình lặp đi lặp lại đòi hỏi phải đánh giá lại các quyết định được đưa ra khi chúng ta gặp phải thất bại.