無停機服務遷移

标签: 中文文章 分享 Python 0-down-time 伺服器 | 发表时间:2011-02-04 16:15 | 作者:victor lostsnow
出处:http://blog.ez2learn.com

一直以來在營運now.in都有一個令人困擾的問題,就是每當伺服器更新或是出現問題時,就得關掉重開,雖然說大部份的伺服器我在設計上都做成重開也沒有關係,但有少部份一但重啟就會造成使用者斷線,參考下面這張伺服器狀態的圖

圖中被切斷的山峰都是伺服器重啟的時候,無疑的這對使用者來說是不好的體驗,對於一般網頁HTTP這類stateless的伺服器來說重啟是無所謂,但串流伺服器就不一樣,因此最近我在思考如何解決這樣的問題,首先想到的是伺服器重開的原因,不外乎有幾種

  • 部署新版的程式
  • 為了修正BUG
  • 記憶體用量過高
  • 重新讀取執行環境,例如ulimit -n,也就是檔案數量的限制大小
  • 從主機A移到主機B

以Python的伺服器來說,如果只是單單只是為了部署新版的程式,Python有個reload函數可以重讀整個module,因此可以設個管理用的後門用來重載模組,但是這樣做有個問題,就是已經產生的instance還是一樣,其實說穿了等於是重新執行那個module,如果只是簡單的改版還好,但複雜的改版就可能牽扯到太多因素難以透過這樣來更新,而除此之外,很多重開的因素像是記憶體用量過高,或著執行的環境參數改變,這些都無可避免的一定得建新的process,至於從A主機移到B主機,基本上是難以避免的一定得重啟伺服器,除非有某個前端的伺服器保持連線,後端的伺服器進行交移工作才有可能,而主機轉移的狀況其實很少出現,因此這篇文章要探討的是如何辦到同一臺機器內的服務轉移

方法

最大的問題就在於,連線中的socket該如保持住連線,最簡單的想法就是建立新的process,將目前的連線交給新的process,新的process接手完所有的連線開始運作後舊的process就能終止了,當然所謂的連線還包函了服務的狀態,我們在者裡稱之為 CSD (Connection State Descriptor),大致想法如下面圖片所示

首先Process A是正在運行的process,而Manager是負責控制遷移的process

此時Manager啟動了要負責接手的Process B

接著Manager通知A將連線狀態傳送給Process B,B接手後繼續提供服務,並且告知接收完成

在確定B能夠接手服務並且正確執行後,這時Manager就能將A中止,走到這裡服務就算是完整地移交了,中間沒有任何間斷

技術層面問題 – Socket的轉移

這樣的做法理論上看起來確實可行沒錯,但是最主要有技術上的問題得解決,就是如何將socket,或著是file descriptor轉移到另一個process,在我找過相關的資料後知道目前有兩個方法

child process

這個方法主要是因為child process在生成後會繼承parent process的file descriptor,但是缺點就是只限於child process,如果我們想將服務移交給另一個完整的獨立process這個方法就會行不通,因此使用上會有很多限制

sendmsg

另一個方法就是unix domain的socket有提供一個函數叫sendmsg,可以將file descriptor轉移到任何process,這樣一來實用性就比child process高很多,於是我決定採用此方法

簡易的實作

在這樣的轉移架構設計,Manager主要目的是要能夠對外有個統一個process能溝通,如果不需要的話其實兩個process就能達成服務轉移,為了實作簡單因此這個例子只用了兩個process進行轉移

還有另一個問題是Python標準函式庫裡沒有sendmsg,不過幸好有第三方函式庫已經準備好,就叫做sendmsg,安裝也很簡單,只要打

easy_install sendmsg

就可以了,接下來就是兩個範例程式

a.py:

import os
import socket
import sendmsg
import struct
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', 5566))
s.listen(1)
conn, addr = s.accept()
conn.send('Hello, process %d is serving\n' % os.getpid())
print 'Accept inet connection', conn
 
us = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
us.bind(('mig.sock'))
us.listen(1)
uconn, addr = us.accept()
print 'Accept unix connection', uconn
 
payload = struct.pack('i', conn.fileno())
sendmsg.sendmsg(
    uconn.fileno(), '', 0, (socket.SOL_SOCKET, sendmsg.SCM_RIGHTS, payload))
print 'Sent socket', conn.fileno()
print 'Done.'

b.py

import os
import socket
import sendmsg
import struct
 
us = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
us.connect(('mig.sock'))
print 'Make unix connection', us
 
result = sendmsg.recvmsg(us.fileno())
identifier, flags, [(level, type, data)] = result
print identifier, flags, [(level, type, data)]
fd = struct.unpack('i', data)[0]
print 'Get fd', fd
 
conn = socket.fromfd(fd, socket.AF_INET, socket.SOCK_STREAM)
os.close(fd)
conn.send('Hello, process %d is serving\n' % os.getpid())
raw_input()

大致上的流程很簡單,首先a.py接收一個inet的連線,接著就開一個unix socket,等待b.py上線,然後把服務透過sendmsg轉移給b.py

執行結果

由執行結果我們可以看見,原本的連線是由process A提供的,然後轉交給process B,中間沒有任何中斷

有了這樣的技術基礎和想法,接著就剩下的是細節,基本上連線的狀態盡量以抽象的方式提供,如此一來任何程式只要能夠處理連線狀態就能夠接手,甚至可以由Python寫的伺服器轉移到由C/C++寫的伺服器,這些都可以辦到,中間服務都不會有間斷,而且也可以像HTTP伺服器一樣,處理到一定的量自動開新的process轉移給自己,如此一來可以保持低記憶體用量,有相當多的好處,未來我會試著將伺服器以這樣的想法改寫,應該就能做到無停機更新,有興趣的話也可以自己試試看

相关 [中文文章 分享 Python 0-down-time 伺服器 ] 推荐:

你沒遇過的編譯器鬼打牆,許功蓋問題

- mrluanma - 程式設計 遇上 小提琴
我想在前面的文章裡很多人可能都沒遇過所謂的鬼打牆,大學四年中我遇到最多鬼打牆的狀況全是Dev C++引起的,其實如果都一一記下來應該是個很長的列表,只是問題解決通常我自己也忘記了,其中一個我印相最深刻的鬼打牆問題不是由Dev C++引起的,而是Visual C++ 6.0引起的,程式碼大略像這樣. // 此函數會被呼叫以通知連線成功.

我的open source專案整理列表

- Charles - 程式設計 遇上 小提琴
HGE以前是一款商用的2D遊戲引擎,後來開源,因為我覺得他的引擎挺簡單好用的,重點是有硬體加速,不像SDL純軟體算圖很慢,所以想說把它移植到Python,於是就用Boost.Python寫了一個專案,完成度大約7~8成,裡面有些比較難處理的部份還沒弄好,但主要功能大多都已經可以使用. Glottony是一個用來分析Python函式庫在PyPi上的相依情況,主要是有一次我安裝TurboGears時遇到其中一個package的伺服器似乎掛掉了,整個進度就因為那個檔案載不了而無法進行,因此我覺得或許這種相依的情況也是選工具時重要的考量,於是就寫了這樣的工具.

那些台灣軟體產業所缺少的 – 版本控制系統

- chitsaou - 程式設計 遇上 小提琴
這幾年來,多多少少接觸了不少業界的人,雖然我自己還不算有真正待過業界太久,但是這期間看到不少業界的現象都令我挺驚訝的,例如在聊天時提到你們公司用的版本控制系統是什麼,有很多人都會回答 「那是什麼?」,一直以來這些在國外的主流開發環境都基本常識或是標準配備的東西台灣業界居然很多都連有那樣的工具存在都不知道,或著是對於某些東西有錯誤的認知,所以我想大略提一下常見的幾個問題.

無用 – 更改Linux SSH登入歡迎畫面為新世紀福音戰士裡的Nerv的logo

- CMGS - 程式設計 遇上 小提琴
不要問我這有什麼用,只是純好玩,如果你的ssh沒有用utf8編碼的話可能會看到亂碼,以下是看起來的效果. 做法很簡單,修改/etc/motd檔即可,如果你想要這個Nerv logo,可以下載這個motd.txt然後解開後覆蓋原本的motd即可. Logo作者為ptt的cfi697,而原始版本因為字型寬度的問題我修正了一下.

無停機服務遷移

- lostsnow - 程式設計 遇上 小提琴
一直以來在營運now.in都有一個令人困擾的問題,就是每當伺服器更新或是出現問題時,就得關掉重開,雖然說大部份的伺服器我在設計上都做成重開也沒有關係,但有少部份一但重啟就會造成使用者斷線,參考下面這張伺服器狀態的圖. 圖中被切斷的山峰都是伺服器重啟的時候,無疑的這對使用者來說是不好的體驗,對於一般網頁HTTP這類stateless的伺服器來說重啟是無所謂,但串流伺服器就不一樣,因此最近我在思考如何解決這樣的問題,首先想到的是伺服器重開的原因,不外乎有幾種.

淺談區域性 (locality)

- chuang - 程式設計 遇上 小提琴
在設計不同的網路服務系統時,為了能夠有擴展性,通常都會設計成分散式的架構,然而除了架構上的設計,如何部署也是很重要的事,其中有個很重要的議題叫做區域性,因為沒有統一或明確的翻譯慣例,所以以英文來說明較為精確,在這裡指的區域性英文為locality,對於這個議題最近有一點心得. 所以,回到主題,到底什麼是區域性.

那些台灣軟體產業所缺少的 – 開放源始碼

- votis - 程式設計 遇上 小提琴
前面幾篇談到了台灣軟體產業界常見的毛病,除了工具以外,還有一項令我感到相當意外的,就是我發現台灣業界對於開放源始碼的認知真的很有問題,例如我曾有和別人討論過,跟他們你們可以使用open source的現成資源來減低成本,但是得到的回應很常是. 那不是讓你用但之後就要付錢嗎?. 從這類的回應就可以大略知道,其實有很多人對於開放源始碼都有一些錯誤的認知,到底什麼時候該付錢、什麼情況可以使用都搞不清楚,因此這回我大算介紹一下一些常見的開源授權的常識.

分享一套 python 试题

- eastxing - 赖勇浩的编程私伙局
赖勇浩(http://laiyonghao.com). 今天在 simple-is-better.com 看到一篇《Python 面试题集合》(http://simple-is-better.com/news/596),里面有一些很好的试题,如“Python是如何进行类型转换的. ”,也有一些让人吐血的试题,如“Python如何实现单例模式.

tagBeep 免費網站伺服器監控服務

- 幻幽 or A書 - 香腸炒魷魚
網路上非常多的網站監控服務,有中文也有英文,但為什麼要用網站監控服務. 用網站監控服務的目的在於你可以即時的掌握網站主機的狀態. 如果哪天主機突然掛點,但你卻剛好一整天剛好沒碰電腦,網站掛了,除了喪失訪客之外,也會讓訪客有不好的體驗,租用的主機如果你不知道網站掛了,其實很虧的. ...詳全文(共1934字).

Twitter如何在數千台伺服器上快速部署程式碼?

- Isis - 牛博山寨 编辑推荐
答案是:用BT,也就是你我應該都很熟悉的BitTorrent. 對於網站經營者、創業者來說,延展性的問題是在網站流量成長過程中勢必會面對的問題,如何建立一個具有延展性的架構(scalable architecture)便是在規劃網站事業過程中不可或缺的專業知識. 如果服務本身的功能性合乎使用者需求,卻因為架構、程式效能、資料庫效能的問題導致服務成長出現瓶頸,如何評估、分析網站效能瓶頸.