原帖地址: http://www.kuqin.com/system-analysis/20110910/264592.html
我們在設計一個服務器的軟件架構的時候,通常會考慮幾種架構:多進程,多線程,非阻塞/異步IO(callback) 以及Coroutine模型。
多進程
這種模型在linux下面的服務程序廣泛採用,比如大名鼎鼎的apache。主進程負責監聽和管理連接,而具體的業務處理都會交給子進程來處理。這裏有一篇我以前寫的文章具體的解釋這種架構的實現。
這種架構的最大的好處是隔離性,子進程萬一crash並不會影響到父進程。缺點就是對系統的負擔過重,想像一下如果有上萬的連接,會需要多少進程來處理。所以這種模型比較合適那種不需要太多併發量的服務器程序。另外,進程間的通訊效率也是一個瓶頸之一,大部分會採用share memory等技術來減低通訊開銷。
多線程
這種模型在windows下面比較常見。它使用一個線程來處理一個client。他的好處是編程簡單,最重要的是你會有一個清晰連續順序的work flow。簡單意味着不容易出錯。
這種模型的問題就是太多的線程會減低軟件的運行效率。
多進程和多線程都有資源耗費比較大的問題,所以在高併發量的服務器端使用並不多。這裏我們重點來研究一下兩種架構,基於callback和coroutine的架構。
Callback- 非阻塞/異步IO
這種架構的特點是使用非阻塞的IO,這樣服務器就可以持續運轉,而不需要等待,可以使用很少的線程,即使只有一個也可以。需要定期的任務可以採取定時器來觸發。把這種架構發揮到極致的就是node.js,一個用javascript來寫服務器端程序的框架。在node.js中,所有的io都是non-block的,可以設置回調。
舉個例子來說明一下。
var file = open(‘my.txt’); var data = file.read(); //block sleep(1); print(data); //block
node.js的寫法:
fs.open(‘my.txt’,function(err,data){ setTimeout(1000,function(){ console.log(data); } }); //non-block
這種架構的好處是performance會比較好,缺點是編程複雜,把以前連續的流程切成了很多片段。另外也不能充分發揮多核的能力。
Coroutine-協程
coroutine本質上是一種輕量級的thread,它的開銷會比使用thread少很多。多個coroutine可以按照次序在一個thread裏面執行,一個coroutine如果處於block狀態,可以交出執行權,讓其他的coroutine繼續執行。使用coroutine可以以清晰的編程模型實現狀態機。讓我們看看Lua語言的coroutine的例子。
> function foo() >> print("foo", 1) >> coroutine.yield() >> print("foo", 2) >> end > co = coroutine.create(foo) -- create a coroutine with foo as the entry > = coroutine.status(co) suspended > = coroutine.resume(co) <--第一次resume foo 1 > = coroutine.resume(co) <--第二次resume foo 2 > = coroutine.status(co) dead
Google go語言也對coroutine使用了語言級別的支持,使用關鍵字go來啓動一個coroutine(從這個關鍵字可以看出Go語言對coroutine的重視),結合chan(類似於message queue的概念)來實現coroutine的通訊,實現了Go的理念 ”Do not communicate by sharing memory; instead, share memory by communicating.”。一個例子:
func ComputeAValue(c chan float64) { // Do the computation. x := 2.3423 / 534.23423; c <- x; } func UseAGoroutine() { channel := make(chan float64); go ComputeAValue(channel); // do something else for a while value := <- channel; fmt.Printf("Result was: %v", value); }
coroutine的優點是編程簡單,流程清晰。但是內存消耗會比較大,畢竟每個coroutine要保留當前stack的一些內容,以便於恢復執行。
Callback vs Coroutine
在業務流程實現上,coroutine確實是更理想的實現,基於callback的風格,代碼確實不是那麼清晰,你可能會寫出這樣的代碼。
//pseudo code socket.read(function(data){ if(data==’1’) db.query(data,function(res){ socket.write(res,function(){ socket.read(function(data){ }); }); }); else doSomethingelse(...); });
當然你可以使用獨立的function函數來代替匿名的函數獲得更好的可讀性。如果使用coroutine就獲得比較清晰的模型。
//pseudo code coroutine handle(client){ var data = read(client); //coroutine will yield when read, resume when complete if(data==’1’){ res = db.query(data); … } else{ doSomething(); } }
但是現實世界中,coroutine到目前爲止並沒有真正流行起來,第一,是因爲支持的語言並不是很多,比較新的語言(python/lua/go/erlang)才支持,但是老一些的語言(java/c/c++)並沒有語言級別的支持。第二個原因是因爲coroutine的使用可能讓一些糟糕的代碼佔用過多的內存,而且比較難於排查。另外在實現一個工作流的構架中,流的暫停和恢復的時機都是未知的,系統的狀態並不能放在內存中存放,都必須序列化,所以coroutine本身要提供序列化的機制,纔可以實現穩定的系統。我想這些就是coroutine叫好不叫座的原因。
儘管有很多人要求node.js實現coroutine,Node.js的作者Ryan dahl在twitter上說: ”i’m not adding coroutines. stop asking”.至於他的理由,他在google group上提到:
Yes, there have been discussions. I think, I won’t be adding
coroutines. I don’t like the added machinery and I feel that if I add
them, then people will actually use them, and then they’ll benchmark
their programs and think that node is slow and/or hogging memory. I’d
rather force people into designing their programs efficiently with
callbacks.
我想這是一種風格的選擇,優劣並不是絕對的。