Đôi khi nhiều hơn là ít hơn. Khi giảm tải dẫn đến độ trễ tăng lên

Như trong hầu hết các bài viết, có vấn đề với dịch vụ phân tán, hãy gọi dịch vụ này là Alvin. Lần này tôi không tự mình phát hiện ra vấn đề, bên phía khách hàng đã thông báo cho tôi.

Một ngày nọ, tôi thức dậy và nhận được một email bất mãn do sự chậm trễ kéo dài với Alvin mà chúng tôi dự định sẽ tung ra trong thời gian tới. Cụ thể, khách hàng đã gặp phải độ trễ ở phân vị thứ 99 trong khoảng 50 mili giây, cao hơn nhiều so với ngân sách độ trễ của chúng tôi. Điều này thật đáng ngạc nhiên khi tôi đã thử nghiệm dịch vụ này một cách rộng rãi, đặc biệt là về độ trễ, đây là lời phàn nàn thường gặp.

Trước khi đưa Alvin vào thử nghiệm, tôi đã chạy rất nhiều thử nghiệm với 40k truy vấn mỗi giây (QPS), tất cả đều hiển thị độ trễ dưới 10ms. Tôi sẵn sàng tuyên bố rằng tôi không đồng ý với kết quả của họ. Nhưng khi xem lại bức thư, tôi nhận thấy một điều mới: Tôi chưa kiểm tra chính xác các điều kiện mà họ đề cập, QPS của họ thấp hơn nhiều so với của tôi. Tôi đã thử nghiệm ở mức 40k QPS, nhưng chúng chỉ ở mức 1k. Tôi đã thực hiện một thử nghiệm khác, lần này với QPS thấp hơn, chỉ để xoa dịu họ.

Vì tôi đang viết blog về vấn đề này nên có lẽ bạn đã nhận ra rằng những con số của họ là đúng. Tôi đã thử đi thử lại máy khách ảo của mình và nhận được cùng một kết quả: số lượng yêu cầu thấp không chỉ làm tăng độ trễ mà còn làm tăng số lượng yêu cầu có độ trễ hơn 10 mili giây. Nói cách khác, nếu ở 40k QPS, khoảng 50 yêu cầu mỗi giây vượt quá 50 ms thì ở 1k QPS sẽ có 100 yêu cầu trên 50 ms mỗi giây. Nghịch lý!

Đôi khi nhiều hơn là ít hơn. Khi giảm tải dẫn đến độ trễ tăng lên

Thu hẹp tìm kiếm

Khi gặp vấn đề về độ trễ trong hệ thống phân tán có nhiều thành phần, bước đầu tiên là tạo một danh sách ngắn các nghi phạm. Hãy tìm hiểu sâu hơn một chút về kiến ​​trúc của Alvin:

Đôi khi nhiều hơn là ít hơn. Khi giảm tải dẫn đến độ trễ tăng lên

Điểm khởi đầu tốt là danh sách các chuyển tiếp I/O đã hoàn thành (cuộc gọi mạng/tra cứu đĩa, v.v.). Chúng ta hãy thử tìm ra độ trễ ở đâu. Bên cạnh việc I/O rõ ràng với khách hàng, Alvin còn thực hiện thêm một bước nữa: anh ấy truy cập vào kho dữ liệu. Tuy nhiên, bộ lưu trữ này hoạt động trong cùng cụm với Alvin, do đó độ trễ sẽ ít hơn so với máy khách. Vì vậy, danh sách nghi phạm:

  1. Cuộc gọi mạng từ khách hàng đến Alvin.
  2. Cuộc gọi mạng từ Alvin đến kho dữ liệu.
  3. Tìm kiếm trên đĩa trong kho dữ liệu.
  4. Cuộc gọi mạng từ kho dữ liệu tới Alvin.
  5. Cuộc gọi mạng từ Alvin tới khách hàng.

Hãy thử gạch bỏ một số điểm.

Lưu trữ dữ liệu không có gì để làm với nó

Điều đầu tiên tôi làm là chuyển Alvin thành máy chủ ping-ping không xử lý yêu cầu. Khi nhận được yêu cầu, nó sẽ trả về một phản hồi trống. Nếu độ trễ giảm thì lỗi trong quá trình triển khai Alvin hoặc kho dữ liệu không có gì là chưa từng xảy ra. Trong thử nghiệm đầu tiên, chúng ta có được biểu đồ sau:

Đôi khi nhiều hơn là ít hơn. Khi giảm tải dẫn đến độ trễ tăng lên

Như bạn có thể thấy, không có sự cải thiện nào khi sử dụng máy chủ ping-ping. Điều này có nghĩa là kho dữ liệu không tăng độ trễ và danh sách nghi phạm bị cắt giảm một nửa:

  1. Cuộc gọi mạng từ khách hàng đến Alvin.
  2. Cuộc gọi mạng từ Alvin tới khách hàng.

Tuyệt vời! Danh sách đang bị thu hẹp nhanh chóng. Tôi nghĩ tôi gần như đã tìm ra lý do.

gRPC

Bây giờ là lúc giới thiệu bạn với một người chơi mới: gRPC. Đây là thư viện nguồn mở của Google để liên lạc trong quá trình RPC. Tuy nhiên gRPC được tối ưu hóa tốt và được sử dụng rộng rãi, đây là lần đầu tiên tôi sử dụng nó trên một hệ thống có quy mô như thế này và tôi dự kiến ​​việc triển khai của mình sẽ ở mức dưới mức tối ưu - ít nhất phải nói là như vậy.

tính sẵn sàng gRPC trong ngăn xếp đã nảy sinh một câu hỏi mới: có thể đó là do tôi thực hiện hoặc do chính tôi gRPC gây ra vấn đề về độ trễ? Thêm một nghi phạm mới vào danh sách:

  1. Khách hàng gọi tới thư viện gRPC
  2. Thư viện gRPC thực hiện cuộc gọi mạng tới thư viện trên máy khách gRPC trên máy chủ
  3. Thư viện gRPC liên hệ với Alvin (không hoạt động trong trường hợp máy chủ bóng bàn)

Để cho bạn biết mã trông như thế nào, việc triển khai máy khách/Alvin của tôi không khác nhiều so với cách triển khai máy khách-máy chủ ví dụ không đồng bộ.

Lưu ý: Danh sách trên được đơn giản hóa một chút vì gRPC cho phép sử dụng mô hình luồng (mẫu?) của riêng bạn, trong đó ngăn xếp thực thi được đan xen gRPC và việc triển khai của người dùng. Để đơn giản, chúng ta sẽ bám sát mô hình này.

Hồ sơ sẽ khắc phục mọi thứ

Sau khi gạch bỏ các kho dữ liệu, tôi nghĩ mình đã gần xong: “Bây giờ thật dễ dàng! Hãy áp dụng hồ sơ và tìm hiểu xem độ trễ xảy ra ở đâu ”. TÔI fan hâm mộ lớn của hồ sơ chính xác, vì CPU rất nhanh và thường không phải là nút cổ chai. Hầu hết sự chậm trễ xảy ra khi bộ xử lý phải ngừng xử lý để làm việc khác. Cấu hình CPU chính xác thực hiện được điều đó: nó ghi lại chính xác mọi thứ công tắc ngữ cảnh và làm rõ nơi xảy ra sự chậm trễ.

Tôi đã lấy bốn cấu hình: với QPS cao (độ trễ thấp) và với máy chủ bóng bàn có QPS thấp (độ trễ cao), cả ở phía máy khách và phía máy chủ. Và để đề phòng, tôi cũng lấy một hồ sơ mẫu của bộ xử lý. Khi so sánh các hồ sơ, tôi thường tìm kiếm một ngăn xếp cuộc gọi bất thường. Ví dụ, về mặt xấu, độ trễ cao lại có nhiều chuyển đổi ngữ cảnh hơn (10 lần trở lên). Nhưng trong trường hợp của tôi, số lần chuyển ngữ cảnh gần như giống nhau. Điều kinh hoàng của tôi là không có gì đáng kể ở đó cả.

Gỡ lỗi bổ sung

Tôi đã tuyệt vọng. Tôi không biết mình có thể sử dụng những công cụ nào khác và kế hoạch tiếp theo của tôi về cơ bản là lặp lại các thử nghiệm với các biến thể khác nhau thay vì chẩn đoán rõ ràng vấn đề.

Chuyện gì xảy ra nếu

Ngay từ đầu, tôi đã lo ngại về độ trễ cụ thể là 50ms. Đây là một thời gian rất lớn. Tôi quyết định sẽ cắt bớt một số đoạn mã cho đến khi tôi có thể tìm ra chính xác phần nào gây ra lỗi này. Sau đó đã có một thử nghiệm thành công.

Như thường lệ, nhìn lại thì có vẻ như mọi thứ đều rõ ràng. Tôi đã đặt ứng dụng khách trên cùng một máy với Alvin - và gửi yêu cầu tới localhost. Và sự gia tăng độ trễ không còn nữa!

Đôi khi nhiều hơn là ít hơn. Khi giảm tải dẫn đến độ trễ tăng lên

Đã xảy ra lỗi với mạng.

Học kỹ năng kỹ sư mạng

Tôi phải thừa nhận: kiến ​​thức của tôi về công nghệ mạng rất tệ, đặc biệt là khi tôi làm việc với chúng hàng ngày. Nhưng mạng là nghi phạm chính và tôi cần học cách gỡ lỗi nó.

May mắn thay, Internet yêu thích những người muốn học hỏi. Sự kết hợp giữa ping và tracert dường như là một khởi đầu đủ tốt để gỡ lỗi các vấn đề truyền tải mạng.

Đầu tiên tôi phát động psping tới cổng TCP của Alvin. Tôi đã sử dụng cài đặt mặc định - không có gì đặc biệt. Trong số hơn một nghìn ping, không có cái nào vượt quá 10 mili giây, ngoại trừ cái đầu tiên để khởi động. Điều này trái ngược với mức tăng độ trễ được quan sát là 50 mili giây ở phân vị thứ 99: ở đó, cứ 100 yêu cầu, chúng ta sẽ thấy khoảng một yêu cầu có độ trễ là 50 mili giây.

Sau đó tôi đã cố gắng tracert: Có thể có sự cố tại một trong các nút dọc tuyến đường giữa Alvin và khách hàng. Nhưng người đánh dấu cũng trở về tay không.

Vì vậy, nguyên nhân gây ra sự chậm trễ không phải là mã của tôi, việc triển khai gRPC hay mạng. Tôi bắt đầu lo lắng rằng tôi sẽ không bao giờ hiểu được điều này.

Bây giờ chúng ta đang sử dụng hệ điều hành nào

gRPC được sử dụng rộng rãi trên Linux, nhưng lạ trên Windows. Tôi quyết định thử một thử nghiệm và thấy hiệu quả: Tôi tạo một máy ảo Linux, biên dịch Alvin cho Linux và triển khai nó.

Đôi khi nhiều hơn là ít hơn. Khi giảm tải dẫn đến độ trễ tăng lên

Và đây là những gì đã xảy ra: máy chủ bóng bàn Linux không có độ trễ giống như máy chủ Windows tương tự, mặc dù nguồn dữ liệu không khác nhau. Hóa ra vấn đề nằm ở việc triển khai gRPC cho Windows.

Thuật toán của Nagle

Suốt thời gian qua tôi tưởng mình đã thiếu một lá cờ gRPC. Bây giờ tôi đã hiểu nó thực sự là gì gRPC Cờ Windows bị thiếu. Tôi đã tìm thấy một thư viện RPC nội bộ mà tôi tin rằng sẽ hoạt động tốt cho tất cả các cờ được đặt Winsock. Sau đó, tôi đã thêm tất cả các cờ này vào gRPC và triển khai Alvin trên Windows, trong một máy chủ bóng bàn Windows đã được vá lỗi!

Đôi khi nhiều hơn là ít hơn. Khi giảm tải dẫn đến độ trễ tăng lên

Hầu như Xong: Tôi bắt đầu xóa từng cờ đã thêm cho đến khi quá trình hồi quy quay trở lại để tôi có thể xác định nguyên nhân. Nó thật khét tiếng TCP_NODELAY, chuyển đổi thuật toán của Nagle.

Thuật toán của Nagle cố gắng giảm số lượng gói được gửi qua mạng bằng cách trì hoãn việc truyền tin nhắn cho đến khi kích thước gói vượt quá một số byte nhất định. Mặc dù điều này có thể tốt cho người dùng bình thường nhưng nó lại có hại cho các máy chủ thời gian thực vì hệ điều hành sẽ trì hoãn một số tin nhắn, gây ra độ trễ trên QPS thấp. bạn gRPC cờ này được đặt trong quá trình triển khai Linux cho các ổ cắm TCP, nhưng không có trong Windows. tôi là cái này sửa lại.

Kết luận

Độ trễ cao hơn ở QPS thấp là do tối ưu hóa hệ điều hành. Nhìn lại, việc lập hồ sơ không phát hiện được độ trễ vì nó được thực hiện ở chế độ kernel chứ không phải ở chế độ kernel. chế độ người dùng. Tôi không biết liệu thuật toán của Nagle có thể được quan sát thông qua các lần chụp ETW hay không, nhưng nó sẽ rất thú vị.

Đối với thử nghiệm localhost, có thể nó không chạm vào mã mạng thực tế và thuật toán của Nagle không chạy, do đó vấn đề về độ trễ đã biến mất khi khách hàng tiếp cận Alvin thông qua localhost.

Lần tới khi bạn thấy độ trễ tăng lên khi số lượng yêu cầu mỗi giây giảm đi, thuật toán của Nagle sẽ nằm trong danh sách nghi ngờ của bạn!

Nguồn: www.habr.com

Thêm một lời nhận xét