強悍的WEB服務器Gevent簡要介紹

原文轉自:http://django-china.cn/topic/114/

“Gevent是一種基於協程的Python網絡庫,它用到Greenlet提供的,封裝了libevent事件循環的高層同步API。”

在python裏,按照官方解釋greenlet是輕量級的並行編程,而gevent呢,就是利用greenlet實現的基於協程的python的網絡library

http://django-china.cn/wiki/webfram/

http://www.gevent.org/

優點

首先Gevent最明顯的特徵就是它驚人的性能,尤其是當與傳統線程解決方案對比的時候。

在這一點上,當負載超過一定程度的時候,異步I/O的性能會大大的優於基於獨立線程的同步I/O這幾乎是常識了。

同時Gevent提供了看上去非常像傳統的基於線程模型編程的接口,但是在隱藏在下面做的是異步I/O。

更妙的是,它使得這一切透明。(此處意思是你可以不用關心其如何實現,Gevent會自動幫你轉換)

你可以繼續使用這些普通的Python模塊,比如用urllib2去處理HTTP請求,它會用Gevent替換那些普通的阻塞的Socket操作。

當然也有一些需要注意的問題,我稍後會闡述。

接下來,見證性能奇蹟的時候到了。

從上圖看出

忽略其他因素,Gevent性能是線程方案的4倍左右(在這個測試中對比的是Paste,譯者注:這是Python另一個基於線程的網絡庫)

在線程方案中,錯誤數隨着併發連接數的增長線性上升(這些錯誤都是超時,我完全可以增加超時限制,但是從用戶的角度來看漫長的等待和失敗其實是一個媽生的)。

Gevent則是直到10,000個併發連接的時候都沒有任何錯誤,或者說能處理至少5,000併發連接。

在這2種情況下,每秒實際完成的請求數都非常的穩定,至少直到Gevent在10,000個併發連接測試崩潰之前是如此。這一點讓我感到非常的驚訝。我原本猜想的是RPS(每秒完成請求數)會隨着併發的增多而下降。

線程模型在10,000併發連接測試中完全失敗。我完全可以讓它正常的工作(比如用一些資源優化技巧應該能做到),不過我純粹是出於玩蛋來做這個測試的,所以沒有在這上面花太多功夫。

測試方法

這是我用來測試的Python代碼:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#!/usr/bin/env python

import sys

def serve_page(env, start_response): 
    paragraph = '''
        Lorem ipsum dolor sit amet,
        consectetur adipisicing elit,
        sed do eiusmod tempor incididunt ut labore et
        dolore magna aliqua. Ut enim adminim veniam,
        quis nostrud exercitation ullamco laboris nisi ut aliquip
        ex ea commodo consequat.
        Duis aute irure dolor in reprehenderit in
        voluptate velit esse cillum dolore eu fugiat nulla pariatur.
        Excepteur sint occaecat cupidatat non proident,
        sunt in culpa qui officia deserunt mollit anim id est laborum.
    ''' 
    page = '''
        \<html\>
            \<head\>
                \<title\>Static Page\</title\>
            \</head\>
            \<body\>
                \
<h1\>Static Content\</h1\>
                %s
            \</body\>
        \</html\>
    ''' % (paragraph * 10,)

    start_response('200 OK', [('Content-Type', 'text/html')]) 
    return [page]

if __name__ == '__main__': 
    def usage(): 
        print 'usage:', sys.argv[0], 'gevent|threaded CONCURRENCY' 
        sys.exit(1)

    if len(sys.argv) != 3 
        or sys.argv[1] not in ['gevent', 'threaded']: 
        usage()

    try: 
        concurrency = int(sys.argv[2]) 
    except ValueError: 
        usage()

    if sys.argv[1] == 'gevent': 
        from gevent import wsgi 
        wsgi.WSGIServer( 
            ('127.0.0.1', 10001), 
            serve_page, 
            log=None, 
            spawn=concurrency 
        ).serve_forever() 
    else: 
        from paste import httpserver 
        httpserver.serve( 
            serve_page, 
            host='127.0.0.1', 
            port='10001', 
            use_threadpool=True, 
            threadpool_workers=concurrency 
        

在客戶端,我在下面這些參數下使用Apache Bench:

  • -c NUM: 這裏的NUM是併發連接數。在每一個測試中這個數與服務器命令行中使用的那個數是匹配的。(譯者注:這裏指腳本運行時需要提供的第二個參數)

  • -n 100000: 所有的測試都需要完成100,000個請求。在上面的圖中,錯誤率並沒有統計,而是實際100,000請求中失敗的請求數。

  • -r: 如果請求失敗,自動重試 所有的測試包括服務端和客戶端都是運行在一個低配置並且只有512MB內存的VPS上。

我最初以爲我需要用一些方法來限制線程方案到一個CPU上,但事實證明就算這VPS號稱是“四核”,你也就只能讓一個核心到100%。就是這麼蛋疼。

負載測試中所做的Linux優化

  • 當Linux處理超過500連接每秒的時候我遇到了一大堆的問題。 基本上這些問題都是因爲所有連接都是從一個IP到另一個相同的IP(127.0.0.1 <-> 127.0.0.1)。 換句話說,你可能在生產環境中不會遇到這些問題,但幾乎可以肯定的是這些問題一定會出現在測試環境中(除非你在後端跑一個單向代理)。 增加客戶端端口範圍 echo -e ’1024\t65535′ | sudo tee /proc/sys/net/ipv4/ip_local_port_range

  • 這一步將會使得客戶端的連接有更多的可用的端口。沒有這個的話你會很快的用盡所有端口(然後連接就處於TIME_WAIT狀態)。

  • 啓用TIME_WAIT複用 echo 1 | sudo tee /proc/sys/net/ipv4/tcp_tw_recycle

    這也會優化停留在TIME_WAIT的連接,當然這種優化至少需要每秒含有同樣IP對連接超過一定數量的時候纔會起作用。同時另一個叫做tcp_tw_reuse的參數也能起到同樣的作用,但我不需要用到它。

  • 關閉同步標籤 echo 1 | sudo tee /proc/sys/net/ipv4/tcp_syncookies

  • 當你看到”possible SYN flooding on port 10001. Sending cookies.”這種信息的時候,你可能需要關閉同步標籤(tcp_syncookies)。在你生產環境的服務器上不要做這樣的事情,這樣做會導致連接重置,只是測試的話還是沒問題的。

  • 如果用到了連接追蹤,關閉iptables 你將會很快的填滿你netfiler表。當然咯,你可以嘗試增加/proc/sys/net/netfilter/nf_conntrack_max中的數值,但是我想最簡單的還是在測試的時候關閉防火牆更好吧。

  • 提高文件描述符限制

至少在Ubuntu上,默認每一個普通用戶的文件描述符限制數是4096。所以咯,如果你想測試超過4000併發連接的時候,你需要調高這個數值。最簡單的方法就是你測試之前在/etc/security/limits.conf中增加一行類似於”* hard nofile 16384″的東西,然後運行ulimit -n 16384這條shell命令。

缺點

當然所有的事情不會這麼好對吧?沒錯。事實上,如果有更完整的文檔的話,很多我在用Gevent的問題會被解決得更好。(譯者對於這類句子毫無抵抗力,湊合着看吧╮(╯_╰)╭)

文檔

簡單的說,這貨一般般。我大概讀了比文檔更多的Gevent源碼(這樣很有用!)。事實上最好的文檔就是源碼目錄下的那些示例代碼。如果你有問題,認真的瞄瞄看它們先。同時我也花了很多時間用Google去搜索郵件列表的存單。

兼容性

這裏我特別想提到eventlet。回想起來,這是有一定道理的,它會導致一些匪夷所思的故障。我們用了一些eventlet在MongoDB客戶端(譯者注:一種高性能文檔型數據庫)代碼上。當我使用Gevent的時候,它根本不能在服務器上運行。

呃,使用順序錯誤

在你導入Gevent或者說至少在你調用Monkey.path_all()之前啓動監聽進程。我不知道爲什麼,但這是我從郵件列表中學到的,另一點則是Gevent修改了Python內部Socket的實現。當你啓動一個監聽進程,所有已經打開的文件描述符會被關閉,因此在子進程中,Socket會以未修改過的形式重新創建出來,當然啦,這就會運行異常。Gevent需要處理這類的異常,或者說至少提供一個兼容的守護進程函數。

Monkey Pathing,抽風咩?

這麼說吧,當你執行monkey.path_all()的時候,很多操作會被打上補丁修改掉。我不是很好這口,但是這樣使得普通Python模塊能夠很好的繼續運行下去。奇怪的是,這丫的不是所有的東西都打上了這種補丁。我瞄了很久想去找出爲毛的Signals模塊不能運行,直到我發現是Gevent.signal的問題。如果你想給函數打補丁,爲毛的不全部打上咩?

這問題同樣適用於Gevent.queue與標準的Python queue模塊。總之,當你需要用到Gevent特定的API去替換標準模塊/類/函數的時候,它需要更清晰(就像簡單的list一樣)。

不優美的地方

Gevent不能支持多進程。這是比其他問題更加蛋疼的部署問題, 這意味着如果你要完全用到多核,你需要在多個端口上運行多個監聽進程。然後捏,你可能需要運行類似於Nginx的東西去在這些服務監聽進程中分發請求(如果你服務需要處理HTTP請求的話)。

說真的,多進程能力的缺乏意味着爲了使用中可用你又要在服務器上多加上一層東西。(譯者注:真蛋疼的句子,不就是多一層Nginx或者HA麼)

在使用Gevent客戶端負載測試中,這真是一個大問題。我最終是實現了一個使用共享內存的多進程負載的客戶端去統計以及打印狀態。這需花費更多的工作量在上面。(如果有人需要做同樣的事情,聯繫我,我會給你這個客戶端腳本程序)

最後

如果你已經看到這裏,你會發現我用了2個章節去闡述Gevent的缺點。不要被這些東西蒙蔽了你的眼睛。我相信Gevent對於Python網絡編程來說是一種很偉大的解決方案。誠然它有各種各樣的問題,但是大多數問題也僅僅是文檔的缺失罷了,撐死了多用點時間嘛。

我們內部正在使用Gevent。事實上我們的服務非常的高效,以至於在我們所用的那種規格的VPS上,很容易在用光計算資源(包括CPU和內存)之前用光帶寬資源。

這是一篇翻譯的文章,原文見http://code.mixpanel.com/gevent-the-good-the-bad-the-ugly/。

原文中由於有大量中英對照,考慮到適合閱讀性,去掉了原文部分。

作者:In the Milky way

Last edited by Django中國社區 (2013-05-15 23:27)


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章