python的高性能web應用的開發與測試實驗

python的高性能web應用的開發與測試實驗

tornado「同步和異步」網絡IO模型實驗

 

引言

python語言一直以開發效率高著稱,被普遍地應用於自動化領域:python

  • 測試自動化
  • 運維自動化
  • 構建發佈自動化

可是由於其也具備以下兩個特徵:git

  1. 解釋型語言
  2. GIL全局解釋器鎖

前者致使其性能自然就被編譯型語言在性能上落後了許多。然後者則在多核並行計算時代,極大的限制了python的應用場景。github

可是經過合理的web框架,則可使用python揚長避短,仍然可以在多核並行時代須保持其高效開發的生產力同時,在性能上也有出色表現。例如,tornado框架。web

tornado框架主要作了以下幾件事:apache

  • 使用單線程的方式,避免線程切換的性能開銷,同時避免在使用一些函數接口時出現線程不安全的狀況
  • 支持異步非阻塞網絡IO模型,避免主進程阻塞等待

前人實驗

基於python語言的web框架衆多,可是主流的有「Django」和「Tornado」基本上能夠表明了它們的實現理念。django

由於本文的重點是對 同步 和 異步 進行對比。因此關於不一樣web框架的性能對比實驗,就引用一位網友的帖子的實驗結果吧。編程

參考文章 [1]輕量級web server Tornado代碼分析瀏覽器

此文章有些部分寫得比較簡略,可是咱們先大膽的作一下假設,做者是使用不一樣的python的web框架對最基本的 HelloWorld 代碼進行了實現。安全

參考的Tornado實現以下:bash

import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")

application = tornado.web.Application([
    (r"/", MainHandler),
])

if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

最後使用 Apache Benchmark (ab),在另一臺機器上使用了以下指令進行負載測試:

ab -n 100000 -c 25 http://10.0.1.x/

在 AMD Opteron 2.4GHz 的四核機器上,結果以下圖所示:

相較於第二快的服務器,Tornado在數據上的表現也是它的4倍之多。即便只用了一個CPU核的裸跑模式,Tornado也有33%的優點。

根據引文做者的觀點:tornado是完虐其它的web框架的。

本文點評:此實驗只是暫時讓大夥創建一下宏觀的對不一樣的web框架的性能的認識,至於可信度是存疑的,由於實驗報告寫得不太規範,細節省略太多。本文的觀點是,若是都是採用同步的的寫法,tornado和django的性能差別應該沒有那麼大的。固然這不過重要了,後面提到的 同步 和 異步 纔是比較重要的。

下面則是本文的重點,同步和異步網絡IO的性能測試和差別對比。

[1] 輕量級web server Tornado代碼分析(http://blog.csdn.net/goldlevi/article/details/7047726)

測試環境

環境

  • CPU:core i3
  • 操做系統:Ubuntu 14.0
  • Python框架:py2.7
  • Web服務器:Tornado 4.2.0,服務器只啓用一核心

內容

使用同步和異步的方式來寫一段延時代碼,而後再使用 apachebench進行壓力測試:

  • 併發量 40
  • 總請求量 200

因爲本文只是作性能對比,而不是性能的上限對比,因此都使用的是比較少的壓力。

同步和異步代碼

class SyncSleepHandler(RequestHandler):
    """
    同步的方式,一個延時1s的接口
    """
    def get(self):
        time.sleep(1)
        self.write("when i sleep 5s")


class SleepHandler(RequestHandler):
    """
    異步的延時1秒的接口
    """
    @tornado.gen.coroutine
    def get(self):
        yield tornado.gen.Task(
            tornado.ioloop.IOLoop.instance().add_timeout,
            time.time() + 1
        )
        self.write("when i sleep 5s")

同步測試結果

➜  /  ab -n 200 -c 40 http://localhost:8009/demo/syncsleep-handler/
This is ApacheBench, Version 2.3 <$Revision: 1528965 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Finished 200 requests


Server Software:        TornadoServer/4.2.1
Server Hostname:        localhost
Server Port:            8009

Document Path:          /demo/syncsleep-handler/
Document Length:        15 bytes

Concurrency Level:      40
Time taken for tests:   200.746 seconds
Complete requests:      200
Failed requests:        0
Total transferred:      42000 bytes
HTML transferred:       3000 bytes
Requests per second:    1.00 [#/sec] (mean)
Time per request:       40149.159 [ms] (mean)
Time per request:       1003.729 [ms] (mean, across all concurrent requests)
Transfer rate:          0.20 [Kbytes/sec] received

Connection Times (ms)
            min  mean[+/-sd] median   max
Connect:        0    0   0.2      0       1
Processing:  1005 36235 18692.2  38133  200745
Waiting:     1005 36234 18692.2  38133  200745
Total:       1006 36235 18692.2  38133  200746

Percentage of the requests served within a certain time (ms)
50%  38133
66%  38137
75%  38142
80%  38161
90%  38171
95%  38176
98%  38179
99%  199742
100%  200746 (longest request)

異步測試結果

➜  /  ab -n 200 -c 40 http://localhost:8009/demo/sleep-handler/
This is ApacheBench, Version 2.3 <$Revision: 1528965 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Finished 200 requests


Server Software:        TornadoServer/4.2.1
Server Hostname:        localhost
Server Port:            8009

Document Path:          /demo/sleep-handler/
Document Length:        15 bytes

Concurrency Level:      40
Time taken for tests:   5.083 seconds
Complete requests:      200
Failed requests:        0
Total transferred:      42000 bytes
HTML transferred:       3000 bytes
Requests per second:    39.35 [#/sec] (mean)
Time per request:       1016.611 [ms] (mean)
Time per request:       25.415 [ms] (mean, across all concurrent requests)
Transfer rate:          8.07 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.4      0       2
Processing:  1001 1010  12.0   1005    1053
Waiting:     1001 1010  12.0   1005    1053
Total:       1001 1010  12.3   1005    1055

Percentage of the requests served within a certain time (ms)
  50%   1005
  66%   1009
  75%   1011
  80%   1015
  90%   1032
  95%   1044
  98%   1045
  99%   1054
 100%   1055 (longest request)

結果對比

在併發量爲40,總請求量爲200的簡單的壓力測試裏面,兩種網絡IO模型的編程方式的性能對好比下:

同步和異步性能對比
性能指標 同步阻塞式 異步非阻塞式
每秒處理請求數(Requests per second) 1 39
請求平均等待時間-ms(Time per request,mean) 40149 1017
請求平均處理時間-ms(Time per request,across all ) 1003 25

測試的結果比較符合被測試程序的理論預期,由於被測試程序就功能就是:一個1s的延時等待。

顯然:異步非阻塞式 和性能是遠高於 同步阻塞式 的。

在上表中的 同步IO模型 數據裏:只要是進入了單個請求的處理環節,進入到睡眠等待的 內核態 操做時,就會將整個進程給 阻塞,別的程序就只能進入 等待 狀態了,這樣本質上仍是使用的 串行 的處理方式,因此 請求平均處理時間 大概是1000ms(1秒)左右,而後完成一個併發度爲40的請求平均等待時間爲40149ms。

關於上面參數的理解能夠進行簡單的類比解釋。

以以下場景爲例子:客戶去銀行處理業務的窗口辦理業務。

  • 並行度:銀行開設的服務窗口數和前臺服務員

    對應CPU,窗口數對應着核心數,即真正的實現並行的能力,即不是在時間分片後交錯進行的 「假象並行」

  • 併發度:大廳裏面全部服務窗口等待服務的人數

    對應着單次的併發度,即本次做業須要處理的任務量

  • 總請求量:從銀行大廳外面陸續過來加入到大廳隊伍的客戶的累計人數

  • 內核態操做:銀行業務中必須只能由前臺服務員處理的操做

  • 用戶態操做:客戶本身要處理的工做,好比:準備好本身的身份證,到外面複印證件,打電話和公司同事確認信息等等。

那麼關於 同步 和 異步 的概念類好比下:

  • 同步阻塞系統:銀行 沒有 排隊叫號系統 ,客戶(Web服務器進程) 只能 在隊伍人羣裏面傻等輪到本身,沒有在排隊時間幹其它事的機會。隨着外面的人不斷地進入大廳,新請求的每一個人都要等前面的隊伍的所有處理完畢後( 40149ms)才能等到業務員(CPU)花1003ms 來處理本身的業務
  • 異步非阻塞系統:銀行  排隊叫號系統 ,客戶有能夠 不用 在擁擠的人羣中傻等,旁邊的休息區打開處理其它事情。客戶直接領取叫號單據,花掉 5ms 遞交準備材料(發起內核態操做請求) 要麼收發郵件,要麼看下小電影,而後等叫號系統叫本身後,馬上上去 20ms的時間解決掉問題。客戶實際浪費在這上面的時間爲 25ms ,固然銀行業務員(CPU)仍是要花 1000ms 去處理這個任務的

在這個假設的場景裏面,不論是同步仍是異步,業務員(CPU)都是 滿負荷 的工做,可是卻極大的節省了客戶(web服務器進程) 的時間。這樣客戶自身能夠把等待業務員響應的時間都利用起來作一些其它工做,這樣就極大地提升了總體的工做效率。

衆所周知,python有GIL,因此多線程實際上是僞多線程。tornado因而就單進程只用單線程,不作線程切換,可是又要實現並行的方式,就所有使用異步了。只要是某個請求進入了內核態的耗時的IO操做,tornado的主進程在發起內核IO初始化以後就作無論它了,馬上回到web的監控中來去響應別的請求。等內核態的IO完成以後,再回調到用戶態的主進程處理結果。若是是用同步模型,若是是使用單進程多線程,則會形成線程切換的開銷,若是使用單進程單線程(像django同樣),若是有一個請求比較耗時,第二我的的請求只會排隊等候的,Web服務進程絕大多數狀況都是被阻塞狀態,性能就極大地下降了。

最後結合前面的延時1s的例子,再加一個即時響應的接口示例:

class JustNowHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("i hope just now see you")

有興趣的同窗能夠本身作實驗。 事先約定:

  • 同步延時1s的接口爲:A
  • 異步延時1s的接口爲:B
  • 即時響應的接口爲:C

使用單核模式運行web服務器。

而後在瀏覽器中以不一樣的順序組合運行程序請求接口:

  • 先即時再延時
    • 先C再A:總共是1s後響應完畢C和A,C馬上響應
    • 先C再B:總共是1s後響應完畢C和B,C馬上響應
  • 先延時再即時
    • 先A再C:總共是1s後響應完畢C和A,C必須等A處理完畢後,才能在1s後響應
    • 先B再C:總共是1s後響應完畢C和B,C能馬上響應

同步模型中,一旦進程被阻塞掉,那麼程序的效率就被等待的時間給嚴重下降了。

總結

有興趣的同窗,能夠更深刻的研究一下 《Unix網絡編程-卷1,套接字聯網API》(W.Richard Stevens) 的第6章第2節 I/O模型

在python的web框架裏面,tornado就是採用的最高效的異步非阻塞框架,能夠在python語言下提供高性能的web應用服務。


做者: Harmo哈莫
做者介紹: https://zhengwh.github.io
技術博客: http://www.cnblogs.com/beer
Email: dreamzsm@gmail.com
QQ: 1295351490
時間: 2015-10
版權聲明: 歡迎以學習交流爲目的讀者隨意轉載,可是請 【註明出處】
支持本文: 若是文章對您有啓發,能夠點擊博客右下角的按鈕進行 【推薦】
相關文章
相關標籤/搜索