題目與範圍
liburing 的 multishot accept 能讓一次提交產生多個完成佇列事件(CQE)。請求可能在錯誤後停止,或在完成事件沒有 multishot 標記時終止。核心能力是非同步系統程式設計,因此分類為 coding。
面試官考察點
應解釋 IORINGCQEF_MORE、CQE 所有權、提交與完成佇列背壓、accept 錯誤、取消和重新佈置。先探測核心支援,不能過早重用緩衝區或使用者資料,並與傳統非阻塞 accept 迴圈比較。
先釐清的問題
- 部署的核心和 liburing 版本是什麼?
- 監聽 socket 由一個 ring 擁有,還是多個工作器共享?
- 連線速率、突發量和檔案描述元預算是多少?
- 接受的 socket 如何交給協議工作器?
- multishot 請求終止或 CQ 滿時怎麼辦?
- 是否需要不依賴 io_uring 的回退?
30 秒答題框架
「先探測支援,提交一個 multishot accept,把每個 CQE 當成獨立接受的 socket 處理。檢查 IORINGCQEF_MORE;標誌缺失時請求已不再佈置,處理終止結果後必須重新提交。迴圈需要有界 CQ、明確處理 EMFILE 與瞬時錯誤、socket 交接所有權和普通 accept 回退。基準比較 CPU、每秒接受數、尾延遲、丟棄、CQ 溢出和重新佈置間隙。」
分步作答
步驟 1:探測並配置
使用 ring 探針或文件化能力檢查,確認 multishot accept 和所需標誌。依突發量與交接速率配置佇列大小,設定 close-on-exec 和非阻塞行為,並權衡直接描述元的管理成本。
步驟 2:提交長生命週期請求
準備穩定的使用者資料,標識監聽 socket 和代次。不能假設一個 SQE 只產生一個 CQE。請求生命週期屬於事件迴圈狀態,關聯狀態必須等到終止完成事件消費後才能釋放。
步驟 3:消費每個完成事件
逐個檢查 CQE:結果可能是接受的描述元,也可能是負錯誤。成功 socket 只向協議工作器轉移一次所有權。每次檢查 IORINGCQEF_MORE;即使當前 CQE 成功而標誌缺失,也要把請求標為非活動。
步驟 4:重新佈置並施加背壓
清空可用 CQE 後,若請求非活動且系統仍能接收工作,就重新提交。限制交接佇列,檔案描述元壓力升高時暫停或拒絕新工作,並處理 EMFILE、ENFILE 和瞬時網路錯誤,避免忙等。
步驟 5:關閉與基準
關閉時取消或關閉請求,排空 CQE,並關閉未交接的接受 socket。用相同 CPU、backlog、連線組合和工作器容量比較 multishot 與傳統 accept。除了系統呼叫數,還要測量重新佈置間隙以及遺失或拒絕的連線。
參考答案
「Multishot accept 能降低提交成本,但它是有生命週期的 CQE 流。先探測支援,提交穩定使用者資料,每個接受描述元只處理一次,並在每個完成事件檢查 IORINGCQEF_MORE。標誌消失後,把請求標為非活動,處理終止結果再重新佈置。佇列深度、交接背壓、EMFILE、關閉排空和普通 accept 回退都是正確性的一部分。基準必須包括重新佈置間隙、丟棄、尾延遲和 CPU。」
常見錯誤
- 認為請求永久有效 → 錯誤或缺少 MORE 後完成會停止 → 顯式重新佈置。
- 把一個 CQE 當成全部結果 → 後續連線被漏掉 → 排空所有 CQE。
- 過早釋放使用者資料 → 後續完成事件存取無效狀態 → 到終止完成後再釋放。
- 忽略負結果 → 迴圈忙等或隱藏資源耗盡 → 分類錯誤並退避。
- 交接佇列無界 → 接受描述元耗盡程序 → 設定描述元和佇列預算。
- 只測系統呼叫 → 重新佈置間隙和遺失連線不可見 → 測量端到端連線結果。
追問
追問 1:IORINGCQEF_MORE 表示什麼?
它表示 multishot 請求預計還會產生更多 CQE。標誌缺失時請求已結束,應用不能假設會有下一次完成事件。
追問 2:成功 CQE 可以是終止事件嗎?
可以。CQE 可能帶有效接受描述元卻沒有 MORE。先處理 socket,再重新佈置請求。
追問 3:如何避免描述元耗盡?
限制協議交接,監控 RLIMIT_NOFILE,處理 EMFILE 與 ENFILE,容量恢復前暫停接受或削峰。
追問 4:為什麼保留回退?
核心、liburing、容器或策略限制可能阻止 io_uring。非阻塞 accept 保持可用性,也提供效能比較的正確性基線。