本文最先發表於我的博客:Pylixm'Wikihtml
應項目的需求,咱們使用tornado開發了一個api系統,系統開發完後,在8核16G的虛機上通過壓測qps只有200+。與咱們當初定的QPS 大於2k差了一個數量級,因而便開始了漫長的優化之路。在優化過程當中,學了許多東西,有必要整理記錄下備查。python
咱們的技術選型:mysql
當初技術選型的時候選擇tornado,即是由於其優秀的性能,這麼低的QPS天然是不甘心。究竟tornado能夠達到多少QPS呢?因而編寫了簡單的hello world,在上邊的虛擬機中起16個進程下,使用ab壓測QPS居然達到了驚人的6K,平均響應時間在毫秒級。這下有信心將api的QPS繼續優化了。web
提高QPS, 可從兩方面入手,一個是增長併發數,其二是減小平均響應時間。從目前狀況看,增長進程併發數是最直接的手段,但當達到機器資源的瓶頸時,可靠堆疊機器來解決。那麼
相比較下,減少平均響應更爲重要。初步分析了咱們開發的api,平均響應時間在幾百毫秒級別。大部分的時間花在系統與數據庫的交互上,到這,便有了一個優化的主題思路:最大限度的下降平均響應時間。sql
咱們API完成的功能爲,接受請求參數作一些列的認證判斷(與數據庫交互),將消息以廣播的形式發送到rabbitmq供消費者消費,最後返回給客戶端發送結果。根據此邏輯,影響響應時間的地方,分析以下:數據庫
根據上邊的問題,從如下幾個方面入手:api
開發api時,由於對tornado 的異步特性不是很熟悉,便沒有使用。後來隨着測試的深刻,發現須要使用後,開始瞭解。
隨着瞭解的深刻,發現tornado是並無很好的支持數據庫的異步特性,更可能是對網絡的異步,官網上也是寫的」網絡非阻塞框架「。
查閱官方文檔,tornado的異步實現,見官方文檔
總的來講,使程序異步的方式有3種,參考這裏。以下:websocket
第一種,使用tornado 的 gen.coruntine。網絡
使用此種方式,須要異步數據庫的驅動庫,經查找現階段並無很好的成熟的支持異步查詢mysql的python驅動,放棄此種方案。
第二種,使用tornado 的線程模塊。併發
此種方式比較方便,只須要在耗時的函數上添加裝飾器便可,簡單方便,能夠說是一種萬能方案,但此方案耗費系統資源。 系統資源並非咱們的瓶頸,咱們最後採納了此種方式。
第三種,使用外部隊列,單獨其worker 進程或線程去處理。例如,celery 等。
此種方式增長了外部的依賴,增長了系統的複雜性和後期的維護難度,放棄此種方案。
增長了異步特性外有顯著的提高。
數據庫方便,咱們適用的是SQLAlchemy。使用ORM時,在減小裸sql帶來的查詢複雜度的同時,必然會增長查詢數據庫的耗時。咱們也作過測試,
使用pymsql連接mysql,直接使用裸sql查詢與使用sqlalcemy 的對象查詢的耗時差異有七、8個毫秒的時差,與sqlalchemy的裸sql方式執行時間幾乎一致。
可見,sqlalchemy的orm方式是有必定時間耗損的。stackoverflow的一個問題,也驗證了個人想法,見Why is loading SQLAlchemy objects via the ORM 5-8x slower than rows via a raw MySQLdb cursor?
針對數據庫方面,咱們作了以下優化:
rabbitmq 方面,使用的是pika 做爲驅動庫鏈接的,使用方式是每次發送數據的時候建立連接和通道,發送完畢後當即關閉連接。考慮到是否可使用長連接,建立連接後不關閉,只關閉channel。修改後發現報錯,具體代碼以下:
# -*- coding:utf-8 -*- import pika from settings import settings class Client(object): def __init__(self, host, port, username, pwd): self.host = host self.port = port self.username = username self.pwd = pwd self.init_connection() def init_connection(self): user_pwd = pika.PlainCredentials(self.username, self.pwd) self.connection = pika.BlockingConnection(pika.ConnectionParameters( host=self.host, port=self.port, credentials=user_pwd)) # ...
補充錯誤材料分析 - todo
翻閱了pika的文檔,發現其有異步的使用方式,且有與tornando 框架的結合的實例,見文檔。
pika的異步方式,使用了和tornado 相同的基於epull的事件循環模型,如何將其與tornado 的IOloop結合是個問題,
其有個tornado的連接適配器,翻看其代碼仍是有些不太明確如何使用,有時間的時候再繼續研究下。
針對rabbitmq的優化咱們放棄了,但優化過程當中有些值得分析的文章,整理以下:
代碼邏輯方便的優化,以下:
通過以上的優化,咱們的api 的 QPS 提高到了1200+, 因爲時間問題,咱們暫停了繼續的優化。經過本次QPS的優化過程,有幾點感悟: