Làm thế nào để bạn tái tạo các điều kiện lỗi và xem những gì đang xảy ra khi ứng dụng thực thi?
Làm thế nào để bạn hình dung các tương tác giữa các phần đồng thời khác nhau của ứng dụng?
Dựa trên kinh nghiệm của tôi, câu trả lời cho hai khía cạnh này như sau:
Truy tìm phân tán
Truy tìm phân tán là công nghệ thu thập dữ liệu thời gian cho từng thành phần đồng thời riêng lẻ trong hệ thống của bạn và trình bày cho bạn ở định dạng đồ họa. Các đại diện của các thực thi đồng thời luôn được xen kẽ, cho phép bạn xem những gì đang chạy song song và những gì không.
Truy tìm phân tán nợ nguồn gốc của nó trong (tất nhiên) các hệ thống phân tán, theo định nghĩa là không đồng bộ và đồng thời cao. Một hệ thống phân tán với theo dõi phân tán cho phép mọi người:
a) xác định các tắc nghẽn quan trọng, b) có được biểu thị trực quan về 'chạy' lý tưởng của ứng dụng của bạn và c) cung cấp khả năng hiển thị về hành vi đồng thời đang được thực thi, d) có được dữ liệu thời gian có thể được sử dụng để đánh giá sự khác biệt giữa các thay đổi trong hệ thống (cực kỳ quan trọng nếu bạn có SLA mạnh).
Tuy nhiên, hậu quả của truy tìm phân tán là:
Nó thêm chi phí cho tất cả các quy trình đồng thời của bạn, vì nó chuyển thành nhiều mã hơn để thực thi và gửi tiềm năng qua mạng. Trong một số trường hợp, chi phí này rất có ý nghĩa - thậm chí Google chỉ sử dụng hệ thống theo dõi của họ Dapper trên một tập hợp nhỏ của tất cả các yêu cầu để không làm hỏng trải nghiệm người dùng.
Nhiều công cụ khác nhau tồn tại, không phải tất cả đều có thể tương tác với nhau. Điều này có phần được cải thiện bởi các tiêu chuẩn như OpenTracing, nhưng không được giải quyết hoàn toàn.
Nó không cho bạn biết gì về tài nguyên được chia sẻ và tình trạng hiện tại của chúng. Bạn có thể đoán, dựa trên mã ứng dụng và biểu đồ bạn thấy đang hiển thị cho bạn, nhưng nó không phải là một công cụ hữu ích trong vấn đề này.
Các công cụ hiện tại giả định rằng bạn có bộ nhớ và lưu trữ dự phòng. Lưu trữ một máy chủ thời gian có thể không rẻ, tùy thuộc vào các ràng buộc của bạn.
Phần mềm theo dõi lỗi
Tôi liên kết với Sentry ở trên chủ yếu vì đây là công cụ được sử dụng rộng rãi nhất hiện nay và vì lý do chính đáng - phần mềm theo dõi lỗi như Sentry chiếm quyền điều khiển thời gian chạy để đồng thời chuyển tiếp dấu vết lỗi của máy chủ trung tâm.
Lợi ích ròng của phần mềm chuyên dụng như vậy trong mã đồng thời:
- Lỗi trùng lặp không được nhân đôi . Nói cách khác, nếu một hoặc nhiều hệ thống đồng thời gặp phải ngoại lệ tương tự, Sentry sẽ tăng báo cáo sự cố, nhưng không gửi hai bản sao của sự cố.
Điều này có nghĩa là bạn có thể tìm ra hệ thống đồng thời đang gặp loại lỗi nào mà không phải trải qua vô số báo cáo lỗi đồng thời. Nếu bạn đã từng bị spam email từ một hệ thống phân tán, bạn sẽ biết cảm giác như thế nào.
Bạn thậm chí có thể 'gắn thẻ' các khía cạnh khác nhau của hệ thống đồng thời của mình (mặc dù điều này giả định rằng bạn không làm việc xen kẽ với chính xác một luồng, dù về mặt kỹ thuật không phải là đồng thời vì luồng chỉ đơn giản là nhảy giữa các tác vụ một cách hiệu quả nhưng vẫn phải xử lý các trình xử lý sự kiện để hoàn thành) và xem bảng phân tích các lỗi theo thẻ.
- Bạn có thể sửa đổi phần mềm xử lý lỗi này để cung cấp thêm chi tiết với các ngoại lệ thời gian chạy của bạn. Những tài nguyên mở đã làm quá trình có? Có một tài nguyên được chia sẻ mà quá trình này đang nắm giữ? Người dùng nào gặp vấn đề này?
Điều này, ngoài các dấu vết ngăn xếp tỉ mỉ (và bản đồ nguồn, nếu bạn phải cung cấp một phiên bản rút gọn của các tệp của mình), giúp bạn dễ dàng xác định điều gì sẽ xảy ra trong một phần lớn thời gian.
- (Cụ thể về Sentry) Bạn có thể có bảng điều khiển báo cáo Sentry riêng để chạy thử hệ thống, cho phép bạn bắt lỗi trong quá trình kiểm tra.
Những nhược điểm của phần mềm này bao gồm:
Giống như mọi thứ, họ thêm số lượng lớn. Bạn có thể không muốn một hệ thống như vậy trên phần cứng nhúng, ví dụ. Tôi đặc biệt khuyên bạn nên chạy thử phần mềm như vậy, so sánh một thực thi đơn giản với và không có phần mềm được lấy mẫu trong vài trăm lần chạy trên một máy nhàn rỗi.
Không phải tất cả các ngôn ngữ đều được hỗ trợ như nhau, vì nhiều hệ thống trong số này dựa vào việc bắt một ngoại lệ và không phải tất cả các ngôn ngữ đều có ngoại lệ mạnh mẽ. Điều đó đang được nói, có khách hàng cho rất nhiều hệ thống.
Chúng có thể được coi là một rủi ro bảo mật, vì nhiều trong số các hệ thống này về cơ bản là nguồn đóng. Trong những trường hợp như vậy, hãy chăm chỉ nghiên cứu chúng, hoặc, nếu được ưa thích, hãy tự mình lăn lộn.
Họ có thể không luôn luôn cung cấp cho bạn thông tin bạn cần. Đây là một rủi ro với tất cả các nỗ lực để thêm khả năng hiển thị.
Hầu hết các dịch vụ này được thiết kế cho các ứng dụng web có tính đồng thời cao, vì vậy không phải mọi công cụ đều có thể hoàn hảo cho trường hợp sử dụng của bạn.
Tóm lại : có khả năng hiển thị là phần quan trọng nhất của bất kỳ hệ thống đồng thời nào. Hai phương pháp tôi mô tả ở trên, kết hợp với bảng điều khiển chuyên dụng về phần cứng và dữ liệu để có được một bức tranh toàn diện về hệ thống tại bất kỳ mốc thời gian cụ thể nào, được sử dụng rộng rãi trong toàn ngành để giải quyết chính xác khía cạnh đó.
Một số gợi ý bổ sung
Tôi đã dành nhiều thời gian hơn tôi quan tâm đến việc sửa mã bởi những người đã cố gắng giải quyết các vấn đề đồng thời theo những cách khủng khiếp. Mỗi lần, tôi đã tìm thấy những trường hợp trong đó những điều sau đây có thể cải thiện đáng kể trải nghiệm của nhà phát triển (điều này cũng quan trọng như trải nghiệm người dùng):
Dựa vào các loại . Nhập vào tồn tại để xác thực mã của bạn và có thể được sử dụng trong thời gian chạy như một bảo vệ bổ sung. Trường hợp gõ không tồn tại, hãy dựa vào các xác nhận và trình xử lý lỗi phù hợp để bắt lỗi. Mã đồng thời yêu cầu mã phòng thủ và các loại đóng vai trò là loại xác nhận tốt nhất hiện có.
- Kiểm tra liên kết giữa các thành phần mã , không chỉ chính thành phần đó. Đừng nhầm lẫn điều này với một bài kiểm tra tích hợp toàn diện - kiểm tra mọi liên kết giữa mọi thành phần, và thậm chí sau đó nó chỉ tìm kiếm một xác nhận toàn cầu về trạng thái cuối cùng. Đây là một cách khủng khiếp để bắt lỗi.
Một tốt kiểm tra liên kết sẽ kiểm tra xem, khi một cuộc đàm phán thành phần để một thành phần trong sự cô lập , các tin nhắn đã nhận và gửi tin nhắn là aa cùng bạn mong đợi. Nếu bạn có hai hoặc nhiều thành phần dựa vào dịch vụ chia sẻ để liên lạc, hãy quay vòng tất cả, yêu cầu họ trao đổi tin nhắn qua dịch vụ trung tâm và xem cuối cùng họ có nhận được những gì bạn mong đợi không.
Việc chia nhỏ các bài kiểm tra liên quan đến rất nhiều thành phần để tự kiểm tra các thành phần và kiểm tra xem mỗi thành phần giao tiếp với nhau như thế nào cũng giúp bạn tăng sự tin cậy về tính hợp lệ của mã. Việc có các bài kiểm tra nghiêm ngặt như vậy cho phép bạn thực thi các hợp đồng giữa các dịch vụ cũng như nắm bắt các lỗi không mong muốn xảy ra khi chúng chạy cùng một lúc.
- Sử dụng các thuật toán phù hợp để xác nhận trạng thái ứng dụng của bạn. Tôi đang nói về những điều đơn giản, chẳng hạn như khi bạn có một quy trình tổng thể chờ tất cả nhân viên của mình hoàn thành một nhiệm vụ và chỉ muốn chuyển sang bước tiếp theo nếu tất cả các công nhân đã hoàn thành - đây là một ví dụ về phát hiện toàn cầu chấm dứt, tồn tại các phương pháp đã biết như thuật toán của Safra.
Một số trong các công cụ này đi kèm với các ngôn ngữ - chẳng hạn, Rust, đảm bảo mã của bạn sẽ không có điều kiện chạy đua trong thời gian biên dịch, trong khi Go có trình phát hiện khóa chết sẵn có cũng chạy trong thời gian biên dịch. Nếu bạn có thể nắm bắt các vấn đề trước khi chúng được sản xuất, nó luôn luôn là một chiến thắng.
Một nguyên tắc chung: thiết kế cho sự thất bại trong các hệ thống đồng thời . Dự đoán rằng các dịch vụ phổ biến sẽ sụp đổ hoặc phá vỡ. Điều này diễn ra ngay cả đối với mã không được phân phối trên các máy - mã đồng thời trên một máy có thể phụ thuộc vào các phụ thuộc bên ngoài (như tệp nhật ký dùng chung, máy chủ Redis, máy chủ MySQL chết tiệt) có thể biến mất hoặc bị xóa bất cứ lúc nào .
Cách tốt nhất để làm điều này là thỉnh thoảng xác nhận trạng thái ứng dụng - kiểm tra sức khỏe cho từng dịch vụ và đảm bảo người tiêu dùng của dịch vụ đó được thông báo về tình trạng sức khỏe xấu. Các công cụ container hiện đại như Docker làm điều này khá tốt, và nên được sử dụng để làm các thứ trong hộp cát.
Làm thế nào để bạn tìm ra những gì có thể được thực hiện đồng thời và những gì có thể được thực hiện tuần tự?
Một trong những bài học lớn nhất tôi học được khi làm việc trên một hệ thống đồng thời cao là: bạn không bao giờ có thể có đủ số liệu . Số liệu sẽ điều khiển hoàn toàn mọi thứ trong ứng dụng của bạn - bạn không phải là kỹ sư nếu bạn không đo lường mọi thứ.
Không có số liệu, bạn không thể làm một vài điều rất quan trọng:
Đánh giá sự khác biệt được thực hiện bởi những thay đổi cho hệ thống. Nếu bạn không biết nếu nút điều chỉnh A làm cho số liệu B tăng lên và số liệu C giảm xuống, bạn không biết cách khắc phục hệ thống của mình khi mọi người đẩy mã ác tính bất ngờ trên hệ thống của bạn (và họ sẽ đẩy mã vào hệ thống của bạn) .
Hiểu những gì bạn cần làm tiếp theo để cải thiện mọi thứ. Cho đến khi bạn biết các ứng dụng sắp hết bộ nhớ, bạn không thể biết liệu bạn nên lấy thêm bộ nhớ hay mua thêm đĩa cho máy chủ của mình.
Các số liệu rất quan trọng và thiết yếu đến nỗi tôi đã biến nó thành một nỗ lực có ý thức để lên kế hoạch cho những gì tôi muốn đo trước khi tôi nghĩ về những gì một hệ thống sẽ yêu cầu. Trong thực tế, các số liệu rất quan trọng đến nỗi tôi tin rằng chúng là câu trả lời đúng cho câu hỏi này: bạn chỉ biết những gì có thể được thực hiện tuần tự hoặc đồng thời khi bạn đo lường các bit trong chương trình của bạn đang làm gì. Thiết kế phù hợp sử dụng số, không phỏng đoán.
Điều đó đang được nói, chắc chắn có một vài quy tắc của ngón tay cái:
Tuần tự ngụ ý sự phụ thuộc. Hai quá trình nên được tuần tự nếu một phụ thuộc vào nhau trong một số thời trang. Các quy trình không có phụ thuộc nên được đồng thời. Tuy nhiên, lập kế hoạch một cách để xử lý luồng không thành công mà không ngăn chặn các quá trình xuôi dòng chờ đợi vô thời hạn.
Không bao giờ trộn lẫn một nhiệm vụ ràng buộc I / O với một nhiệm vụ gắn với CPU trên cùng một lõi. Đừng (ví dụ) viết trình thu thập dữ liệu web khởi chạy mười yêu cầu đồng thời trong cùng một chuỗi, loại bỏ chúng ngay khi chúng đến và dự kiến sẽ mở rộng đến năm trăm - yêu cầu I / O đi đến hàng đợi song song, nhưng CPU vẫn sẽ đi qua chúng một cách an toàn. (Mô hình hướng sự kiện đơn luồng này là một mô hình phổ biến, nhưng nó bị hạn chế do khía cạnh này - thay vì hiểu điều này, mọi người chỉ cần vặn tay và nói Node không mở rộng quy mô, để cho bạn một ví dụ).
Một chủ đề duy nhất có thể làm rất nhiều công việc I / O. Nhưng để sử dụng hoàn toàn đồng thời phần cứng của bạn, hãy sử dụng các chuỗi kết hợp với nhau chiếm tất cả các lõi. Trong ví dụ trên, khởi chạy năm quy trình Python (mỗi quy trình có thể sử dụng lõi trên máy sáu lõi) chỉ dành cho công việc CPU và luồng Python thứ sáu chỉ dành cho công việc I / O sẽ mở rộng nhanh hơn nhiều so với bạn nghĩ.
Cách duy nhất để tận dụng lợi thế đồng thời của CPU là thông qua một luồng chuyên dụng. Một chủ đề thường đủ tốt cho nhiều công việc ràng buộc I / O. Đây là lý do tại sao các máy chủ web điều khiển sự kiện như Nginx có quy mô tốt hơn (chúng hoàn toàn làm việc liên kết I / O) so với Apache (kết hợp công việc ràng buộc I / O với thứ gì đó yêu cầu CPU và khởi chạy quy trình theo yêu cầu), nhưng tại sao sử dụng Node để thực hiện Hàng chục ngàn phép tính GPU nhận được song song là một ý tưởng tồi tệ .