Steven
Steven7 phút đọc

Ngày ứng dụng của chúng tôi tự DDoS chính mình

Một đống bản tải lên còn tồn đọng, được xả ra cùng một lúc lúc khởi động, đã biến mỗi client GeekBye thành một đợt tấn công từ chối dịch vụ nhỏ nhắm vào chính máy chủ của chúng tôi. Cách sửa — và cái thang connection-liveness mà nó buộc chúng tôi phải dựng — là một trong những thứ hữu ích nhất mà v2 đã dạy chúng tôi.

Độ tin cậy
Mạng
Kỹ thuật
GeekBye Releases
Ngày ứng dụng của chúng tôi tự DDoS chính mình

Kỹ sư hệ phân tán nào rồi cũng gặp đợt đổ xô (thundering herd): một khối client cùng làm một việc vào cùng một khoảnh khắc, và tài nguyên dùng chung phía sau chúng oằn xuống. Thường thì đó là client của người khác. Trong GeekBye v2.0.1 thì đó là của chúng tôi, và kẻ tấn công chính là app của chúng tôi.

Một app ghi chú tấn công chính mình như thế nào

GeekBye có thể tải các bản ghi lên Google Drive của bạn. Nếu một bản ghi không tải lên ngay được — bạn đang offline, app đã đóng, Drive trục trặc — nó vào một đống tồn đọng để thử lại sau. Hợp lý.

Thất bại nằm ở chỗ "sau" đó khi nào xảy ra. Vào lần khởi động kế tiếp, app sẽ cố rút cạn toàn bộ đống tồn đọng cùng một lúc. Một người dùng với cả tuần bản ghi đang chờ trở thành một cú bùng nổ các yêu cầu tải lên đồng thời ngay khoảnh khắc họ mở app. Nhân lên với mọi người dùng khởi động vào buổi sáng, và backend của chúng tôi bị yêu cầu xác thực, kiểm tra rate, và xử lý một bức tường lưu lượng trong cùng vài giây đó — từ những client mà, về mặt kỹ thuật, tất cả đều đang cư xử đàng hoàng.

Backend của chúng tôi làm đúng việc và rate-limit trận lũ. Và đó là chỗ thiệt hại bậc hai bắt đầu. Một phản hồi rate-limit là một HTTP 429, và 429 trông rất giống các thất bại khác nếu bạn không cẩn thận:

  • Lượt lấy hồ sơ lúc khởi động nhận một 429 và app coi nó là xác thực thất bại — đăng xuất người dùng khỏi một phiên hoàn toàn hợp lệ trong chốc lát.
  • Kết nối phiên âm nhận một 429 chung chung và hiện một lời nhắc nâng cấp báo đã chạm giới hạn âm thanh — bảo những người dùng trả tiền rằng họ đã chạm một hạn ngạch mà họ chưa hề chạm.

Thế là một nguyên nhân gốc — một đống tồn đọng không được điều nhịp — sinh ra ba triệu chứng nhìn thấy được: máy chủ căng thẳng, đăng xuất giả, và mời chào nâng cấp giả. Đúng cái hình dạng kinh điển của self-DoS: tải trọng thì tệ, nhưng cái mà người dùng thực sự cảm nhận lại là sự hiểu sai các triệu chứng của tải trọng đó.

Cách sửa, thành hai lớp

Chặn đợt giẫm đạp. Đống tải lên tồn đọng giờ đã được điều nhịp — các yêu cầu trải ra với backoff, và một cooldown sau bất kỳ 429 nào để client lùi ra khỏi một máy chủ đang căng thẳng thay vì tì vào nó mạnh hơn. Một đợt đổ xô (thundering herd) trở thành một hàng đợi có trật tự.

Chặn sự hiểu sai. Một 429 hay 5xx thoáng qua trong lúc khởi động không còn đăng xuất bạn nữa — client phân biệt "máy chủ đang bận chốc lát" với "phiên của bạn không hợp lệ". Và một 429 rate-limit chung chung trên đường phiên âm không còn giả dạng thành một lỗi hạn ngạch âm thanh nữa; chỉ một phản hồi hạn ngạch thật sự mới hiện lời nhắc nâng cấp. Bài học đọng lại: một mã lỗi không phải là một ý nghĩa lỗi. 429 nghĩa là "chậm lại", không phải "bạn chưa được xác thực" và cũng không phải "bạn đã hết hạn ngạch" — và client phải biết sự khác biệt đó.

Cái thang liveness theo sau

Điều nhịp cho đàn đông phơi ra một vấn đề lặng lẽ hơn: khi một kết nối thực sự hỏng dưới tải, chúng tôi nhận ra nhanh cỡ nào? Chậm hơn chúng tôi muốn. Vậy nên v2.0.4 đã dựng ra một cái thang connection-liveness cho phiên âm thời gian thực:

  • Heartbeat — socket phiên âm được ping theo một chu kỳ cố định, để một kết nối chết lặng lẽ được phát hiện trong vài giây thay vì chờ khối âm thanh kế tiếp thất bại.
  • Một cú kích kết nối lại khi mạng trở lại — khi hệ điều hành báo mạng đã về, app chủ động thiết lập lại kết nối thay vì chờ vấp phải phát hiện đó.
  • Liveness ping/pong ở tầng điều khiển — một vòng đi-về ở mức ứng dụng xác nhận không chỉ "socket đang mở" mà còn "đầu bên kia thực sự đang trả lời".
  • Một cổng kết nối lại do service sở hữu — một chỗ duy nhất quyết định có kết nối lại hay không, thay vì nhiều phần của app đua nhau làm và giẫm lên nhau.

Chẳng cái nào hào nhoáng. Tất cả đều là lý do vì sao một phiên v2 cảm giác như nó chỉ… cứ ở nguyên kết nối.

Nó lại xảy ra tuần này — thấp hơn một bậc

Đây là phần khiến chuyện này hơn một câu chuyện chiến trận. Cùng lớp thất bại này nổi lên lại vài ngày trước, thấp hơn chúng tôi một mức. Một client đơn lẻ bắn ra 21 lượt khởi động phiên phiên âm trong chưa đầy 400 mili-giây — và vấp phải giới hạn cấp-toàn-tài-khoản của nhà cung cấp giọng nói của chúng tôi là 15 yêu cầu đồng thời. Một cú giẫm đạp lên hạ tầng dùng chung, y hệt đống tải lên tồn đọng, chỉ là nhắm vào một dependency thay vì backend của chính chúng tôi.

Hình dạng giống hệt, và cách sửa cũng vậy: client đang giẫm đạp cần một single-flight guard để nó không thể bắn hai mươi lượt khởi động cho một ý định, và tài nguyên dùng chung cần một trần theo từng người dùng để một client không thể ngốn hết cả pool. Chúng tôi đã dựng phiên bản phía client của việc này trước đây rồi — bộ điều nhịp tải lên chính là mẫu hình này. Giờ chúng tôi áp nó vào việc khởi động phiên. Đợt đổ xô (thundering herd) không phải là một con bug bạn sửa một lần; nó là một hình dạng bạn học cách nhận ra ở mọi nơi client gặp một giới hạn dùng chung.

Ba điều để mang theo

  1. Chính client của bạn là một bài kiểm thử tải mà bạn không hề lên lịch. Bất cứ thứ gì gộp-lúc-khởi-động, thử-lại-lúc-mở, hay kết-nối-lại-khi-thức đều là một đợt đổ xô (thundering herd) đang chờ đủ người dùng. Hãy điều nhịp nó trước khi bạn có họ.
  2. Phân biệt cái mã với cái ý nghĩa. 429 là trạng thái bị đọc sai nhiều nhất trên đời. "Chậm lại" không phải "đăng xuất" và không phải "trả tiền cho chúng tôi". Định tuyến mỗi thất bại về đúng cái nó thực sự có nghĩa.
  3. Liveness là một cái thang, không phải một cái cờ. "Kết nối còn sống không?" có vài câu trả lời trung thực ở các tầng khác nhau — socket mở, byte đang chảy, đầu bên kia đang trả lời, mạng đang hiện diện. Một app vững vàng kiểm tra hơn một điều.

Đây là bộ máy không hào nhoáng nằm dưới sự điềm tĩnh của GeekBye v2. Về các tính năng độ tin cậy mà nó mở đường, xem vì sao AI notetaker của bạn dừng khi Wi-Fi tệphiên âm trực tiếp khi tường lửa chặn WebSocket (v2.0.8). Về các bản phát hành lân cận trong series này, xem vì sao AI notetaker của bạn dừng ghi âm giữa cuộc họp (v2.0.9).

Bài Viết Liên Quan

Khi tường lửa chặn WebSocket, phiên âm thời gian thực sẽ ra sao
Steven
Steven6 phút đọc

Khi tường lửa chặn WebSocket, phiên âm thời gian thực sẽ ra sao

Mạng doanh nghiệp thích cho HTTPS đi qua nhưng âm thầm giết chết bước nâng cấp WebSocket. Điều đó lặng lẽ làm hỏng phiên âm thời gian thực. GeekBye v2.0.8 tự động fall back sang một transport thuần HTTPS — và trong lúc làm ra nó, chúng tôi đào được một con bug có thể khiến cả tính năng trở nên vô dụng.

Phiên âm
Mạng
Kỹ thuật
Vì sao AI notetaker của bạn dừng ghi âm giữa cuộc họp
Steven
Steven7 phút đọc

Vì sao AI notetaker của bạn dừng ghi âm giữa cuộc họp

Chính ứng dụng của chúng tôi đã kết thúc hai cuộc họp của mình khi phía bên kia đang nói dở câu. Dấu vết điều tra dẫn đến một bộ đếm idle đầy thiện chí nhưng không thể nghe thấy ai ngoài bạn — và một con bug thứ hai có thể khóa cứng toàn bộ desktop của bạn. Cả hai đã được sửa trong GeekBye v2.0.9.

Độ tin cậy
Cuộc họp
Kỹ thuật
Vì sao AI phiên âm nghe nhầm thuật ngữ kỹ thuật (và chúng tôi đã sửa như thế nào)
Steven
Steven9 phút đọc

Vì sao AI phiên âm nghe nhầm thuật ngữ kỹ thuật (và chúng tôi đã sửa như thế nào)

Một phiên ghi âm trực tiếp đã nghe "what is the pointer in C++" thành "what is the point in life". Đây là hành trình điều tra từ bản phiên âm đó đến GeekBye v2.0.11 — keyterm biasing, một race condition làm rớt kết nối, và cái ngày mà chính bản sửa lỗi của chúng tôi phản tác dụng.

Phiên âm
Độ tin cậy
Kỹ thuật