SOFAMosn 無損重啟/升級
說明,本文件基於SOFAMosn 0.4.0 版本編寫
前言
SOFAMosn 是一款採用 GoLang 開發的 Service Mesh 資料平面代理,由螞蟻金服系統部網路團隊、螞蟻金服中介軟體團隊、UC 大文娛團隊共同開發,功能和定位類似 Envoy,旨在提供分散式,模組化,可觀察,智慧化的代理能力;她通過模組化,分層解耦的設計,提供了可程式設計,事件機制,擴充套件性,高吞吐量的能力。
——摘自《 SOFAMosn 的誕生和特性》
概述
總體上看,連線遷移的流程如下圖:
- MOSN 通過 forkexec 生成 New MOSN
- MOSN 通過 domain socket 把 TCP fd 和請求資料傳送給 New MOSN
- New MOSN 轉發請求到後端(PUB2)
- 後端 回覆響應到 New MOSN
- New MOSN 通過 MOSN 傳遞來的 TCP fd,回覆響應到 client
此後:
- mosn 退出 readloop,不再接受該 TCP 連線上的資料
- New mosn 開始 readloop,接受該 TCP 連線上的資料
——摘自《SOFAMosn Introduction 》
具體實現
觸發
在 MOSN 啟動的時候,會載入包
github.com/alipay/sofa-mosn/pkg/server
在這個包載入的時候,該裡面的 serverkeeper.go 這個檔案中的 init() 函式被執行。這個函式會起一個協程在捕獲 HUP 訊號。
當 Mosn 接收到來自系統的 HUP 訊號時,MOSN 首先會呼叫 stopStoppable 函式先讓 Admin Server 中的所有 Listener 都關閉 。然後呼叫 reconfigure 函式來進行配置重新載入。
遷移過程
舊程序的退出
觸發了 reconfigure 函式後,首先 MOSN 會設定兩個環境變數
_MOSN_GRACEFUL_RESTART=true _MOSN_INHERITFD_FD=<number>
- _MOSN_GRACEFUL_RESTART 對應的包 github.com/alipay/sofa-mosn/pkg/types 中的 GracefulRestart 常量,用於告訴新啟動的 MOSN(下簡稱 New MOSN) 這個是一個優雅重啟。
- _MOSN_INHERITFD_FD 對應包 github.com/alipay/sofa-mosn/pkg/types 中的 InheritFd 常量,裡面儲存的是 ListenerFD 的數量(ListenerFD 就是每個 Listener 呼叫 listen() 返回的 fd)。
準備好環境變數後,就呼叫 syscall 包的 ForkExec 按照當前 MOSN 的啟動引數進行啟動,並將環境變數和標準輸入輸出錯誤和 ListenerFD 都和 New MOSN 共享。然後,MOSN 會等 3 秒,讓 New MOSN 啟動起來。認為 New MOSN 啟動完成後,它就會呼叫 StopAccept() 讓所有的 Listener 停止 Accept 新的請求(已經 Accept 的請求不會結束,socket 的監聽也不會斷),然後呼叫 WaitConnectionsDone 函式根據 GracefulTimeout(預設是 30秒) 設定的優雅重啟的超時時間讓所有的連線都完畢。接著 MOSN 就進行 Metrics 的遷移,完成後就會退出程序。
在 WaitConnectionsDone 中,MOSN 設定了一個時間長度為 2 個 GracefulTimeout + 10秒 的時間的定時器。然後首先會 sleep 一個 GracefulTimeout 的時間,等待所有的連線主動關閉。然後關閉所有 server 中 connHandler 的 listeners 成員的 stopChan. 然後再 sleep 一個 GracefulTimeout + 10秒的時間,等待所有連線的遷移。時間過了之後,函式就會返回。此後,上層會呼叫 TransferMetrics 進行 Metrics 的呼叫 Exit 進行程序退出。
新程序的啟動
繼承 Listener 的獲取
在 New MOSN 啟動的過程中,首先會呼叫 getInheritListeners。這個函式會從讀取 Old MOSN 設定的環境變數 _MOSN_GRACEFUL_RESTART,如果為 true, 說明這是一個優雅重啟,就會讀取環境變數 _MOSN_INHERITFD_FD。由於 Listener 是最先使用 fd 的,所以 fd 總是從3 開始,那麼所有 Listener fd 就是: 3, 4, ... , 3 + _MOSN_INHERITFD_FD。然後利用這些 fd 將 Old MOSN 的 Listener 恢復出來。從而獲取到繼承過來的 Listener。獲取完之後,會對獲取的 Listener 和配置檔案進行比對,判斷其合法性。如果不合法的,或者不能新的配置裡面沒有以致繼承過來的 Listener 不需要複用,就會將其關閉。
完成了所有的初始化之後,就會啟動兩個 Unix Sock 的Server, 分別用與進行連線的遷移和 metrics 的遷移。用於連線遷移的 Unix Sock Server 會在 2 個 GracefulTimeout + 10 秒後自動關閉。
遷移過程中,New MOSN 對每一個 Unix Sock 請求都會分配一個協程去處理。
連線的遷移
當一個請求進來的時候,如果請求使用的協議不是 HTTP1 且不使用系統提供的事件迴圈的時候,MOSN 會啟動自己的 ConnIo, 呼叫 startReadLoop 和 startWriteLoop 來開啟針對這個請求的的讀寫迴圈。
讀寫資料遷移的協議
在傳送請求的過程中,首先會發送一個位元組的資料, 這個位元組代表了傳輸的是讀資料遷移還是寫資料遷移。0 代表是使用讀資料遷移協議。1 代表是使用寫資料協議。如果是 0, 還會將該連線的 fd 以 out-of-band 的方式也傳送出去。
讀資料遷移協議
首先是頭部分:包括 8 個位元組,前 4 個位元組是 data 部分的長度,後 4 個位元組是 TLS 部分的長度。body 部分:接下來 data length 個位元組儲存的是 readBuffer 資料。最後 TLS length 個位元組儲存的是 TLS 的資料。
寫資料遷移協議
頭部分也是 8 個位元組, 前 4 個位元組儲存了 data 部分長度,後 4 個位元組儲存的是 connection ID。body 部分:接下來的 data length 自己儲存的是 writeBuffer 資料。
讀資料的遷移
Old MOSN 傳送
在 startReadLoop 中,MOSN 會捕獲之前提到的 stopChan 被關閉的事件。捕獲到這個事件之後,MOSN 會讓這個連結等待一個隨機的時間,然後開啟連線遷移的過程。
首先 MOSN 會往連線中的 transferChan 發一個 transferNotify(值為1) 訊息,告訴這個連線對應的寫迴圈:要開始遷移連線了。然後呼叫 transferRead 開始遷移讀連線,並返回一個connection ID,最後將這個 ID 再次傳送給 transferChan。
在函式 transferRead 中:
- 和 New MOSN 先前提到的負責連線遷移的 transferServer 建立 unix socket 連線。
- 獲取該連線對應的 fd
- 呼叫 transferSendType 將傳輸資料使用的協議型別(讀資料遷移協議還是寫資料遷移協議)和連線的 fd 傳送給 New MOSN。
- 呼叫 transferReadSendData 將 header 部分和 body 部分傳輸給 New MOSN。如果 TLS 握手還沒結束,則 TLS length 為 0。
- 接收 New MOSN 處理完這些資料之後返回的 connection ID, 並返回
New MOSN 的接收
當 New MOSN 接受到來自 Old MOSN 的資料時:
- New MOSN 會呼叫 transferRecvType, 首先接受協議的型別(一個位元組), 如果是讀資料遷移的協議,還會去接受 oob(out-of-band)中的 fd,並利用個這個 fd 重建出一個連線,恢復監聽。
- 呼叫 transferReadRecvData 獲得本次請求中的 data 部分和 tls 部分的資料。
- 呼叫 transferNewConn, 首先根據重建出的連線,找到 NewMOSN 中對應的 Listener。如果這是一個 TLS 連線,還會利用 Old MOSN 傳過來的 TLS 資訊將連線重建成一個 TLS 連線。
- 然後該 Listener 從觸發 Listener 的 OnAccept 事件開始,處理這個連線的請求。當 MOSN 用於封裝連線的 connection 結構體建立完畢後,就標誌著這個連線遷移完成,並將這個 connection 結構體儲存在一個叫 transferMap 的資料結構中。
- 利用重建的 connection 的 id 生成一個本次遷移連線的 ID,回傳給 Old MOSN。
寫資料的遷移
Old MOSN 的傳送
當寫迴圈收到讀迴圈從 transferChan 發過來的 transferNotify 訊息時,會再讀一次 transferChan, 獲取到這一次連線傳輸的 ID,如果 ID 合法,則會開始監聽兩個 channel:
- internalStopChan: 從這個 channel 收到訊號(這個 channel 被 close 了),認為這寫資料遷移完成了,會直接退出。
- writeBufferChan: 這個 channel 傳送過來的是需要寫的資料,也就是需要傳送的資料。收到後就會呼叫 transferWrite 開始遷移。當 writeLoop 要結束的時候,會 close 掉 internalStopChan和 writeBufferChan。觸發條件1。
在 transferWrite 中:
- 和 New MOSN 先前提到的負責連線遷移的 transferServer 建立 unix socket 連線。
- 呼叫 transferSendType 將傳輸資料使用的協議型別(讀資料遷移協議還是寫資料遷移協議,此處是寫資料遷移協議)
- 呼叫 tranferWriteSendData 將 writeBuffer 裡面的內容連同從 New MOSN 返回的連線傳輸 ID 一起傳送給 New MOSN
New MOSN 的接收
當 New MOSN 接受到來自 Old MOSN 的資料時:
- New MOSN 會呼叫 transferRecvType, 接受協議的型別(一個位元組),判斷是寫資料遷移協議,進入接受寫資料遷移的資料流程。
- 從 unix sock 中讀出要寫的 buffer, 和連線傳輸的 ID
- 根據 ID 從 transferMap 中 取出對應的 connection 結構體。並讓將傳輸過來的資料扔到 connection 結構體中的 writeBufferChan 中,進入新的 writeLoop。
至此,連線遷移的過成就完成了。
Metrics 的遷移
Old MOSN 退出前的最後一件事,就是把 Metrics 資料託付給 New MOSN。
協議
Metrics 的傳輸協議很簡單,包括兩部分 header 和 body
- Header 長度為 4 個位元組 存放的是 body length
- Body 的長度為 body length 個位元組。存放著要傳輸的資料,即 Metrics 資料。
Old MOSN 的傳送
- 首先他會調 makesTransferData, 將所有的 Metrics 資料都統一收集起來。
- 和 New MOSN 先前提到的負責 Metrics 遷移的 transferServer 建立 unix socket 連線。
- 先後把 header 和 body 傳送給 New MOSN
- 如果設定了等待 New MOSN 的響應,會在一個超市時間內等待 New MOSN 1 個位元組的返回。
New MOSN 的接收
當 New MOSN 接受到來自 Old MOSN 的資料時,會調 serveConn 函式去處理每一個遷移請求:
- 讀取資料中的協議頭,並根據協議頭讀取出報文體。
- 將報文體的資料恢復成成 go-metrics 的資料,供 New MOSN 使用
至此,所有關於平滑重啟的操作就完成了。