基於sanic打造python web框架

0x00 Why

爲何做這件事,在去年的一個項目中,算法同學要使用在線模型訓練,不得不使用python的tf框架,這樣我們不得不是python web框架,當時因爲團隊裏面沒人懂python相關的知識,只是簡單的用tornado搭建的一個。但是在後期使用過程中,壓測發現了tornado在低耗時的接口不夠穩定,波動比較大。我們的接口一般20ms,但是經常波動到40ms,併發其實也不大。所以經歷了痛苦過後,爲了滿足後續類似的場景,我們需要打造一款內部使用的python web框架,能夠支持低耗時接口的穩定性,以及稍微性能強點。

0x01 How

基於以上的出發點,開始調研python web相關性能比較高的框架,看到了sanic。說是性能比較高。先看下sanic作者開發sanic的動機,是因爲他看下了下面這篇文章:

uvloop: Blazing fast Python networking

是因爲python3.4推出了asyncio,解決令人詬病的異步io性能問題,3.5後推出了uvloop,基於libuv,libuv是一個使用C語言實現的高性能異步I/O庫,uvloop用來代替asyncio默認事件循環,可以進一步加快異步I/O操作的速度,而tornado在python3中還沒有使用uvloop。

首先來看下uvloop的性能表現:

在這裏插入圖片描述

0x02 tornado vs sanic

口說無憑,直接壓測看數據,我們同時構造兩個框架的一個接口,接口裏面沒有任務邏輯,就是簡單的返回一個helloworld,都是起一個進程,實測的數據

  • sanic:18000/s
  • tornado:5000/s

就框架本身而言,sanic確實性能高。然後我又對各個耗時階段的接口進行壓測,來判斷不同耗時,兩個框架的表現。

Sanic

Sleep(ms)\Thread 1 10 50 100
1 1-3之間波動,波動範圍爲0%~300%,週期性的 1-6之間波動,波動範圍爲0%~600%,週期性的 1-11之間波動,波動範圍爲0%~1100%,週期性的 1-7之間波動,波動範圍爲0%~700%,週期性的
10 11-15之間波動,波動範圍爲10%~50%,週期性的 12-17之間波動,波動範圍爲20%~70%,週期性的 11-20之間波動,波動範圍爲10%~100%,週期性的 11-31之間波動,波動範圍爲10%~310%,週期性的
50 51-56,週期性的波動 54-60,週期性 54-71,週期性的 51-61,週期的
100 101-106之間波動,波動範圍爲1%~6%,週期性的 103-110之間波動,波動範圍爲3%~10%,週期性的 103-120之間波動,波動範圍爲3%~20%,週期性的 105-130之間波動,波動範圍爲5%~30%,週期性的

Tornado

Sleep(ms)\Thread 1 10 50 100
1 2-4之間波動,波動範圍爲200%~400%,週期性的 6-11之間波動,波動範圍爲600%~1100%,週期性的 21-56之間波動,波動範圍爲2100%~5600%,週期性的 50-106之間波動,波動範圍爲5000%~10600%,週期性的
10 11-15之間波動,波動範圍爲110%~150%,週期性的 11-18之間波動,波動範圍爲110%~180%,週期性的 28-43之間波動,波動範圍爲280%~430%,週期性的 62-104之間波動,波動範圍爲620%~1040%,週期性的
50 52-57之間波動,週期性的 54-62,週期性的 51-80,週期性的 53-75,週期性的
100 101-108之間波動,波動範圍爲1%~8%,週期性的 101-111之間波動,波動範圍爲1%~11%,週期性的 101-111之間波動,波動範圍爲1%~11%,週期性的 101-140之間波動,波動範圍爲1%~40%,週期性的

result

sleep 1ms&100個線程壓的情況

Sanic波動爲1-7ms,而Tornado已經最低到50ms,最高飆到100多ms,顯然在10ms以內的接口,併發超過50的,不適合使用Tornado,推薦使用Sanic,Sanic贏一局,Sanic1:0Tornado

sleep 10ms &100個線程壓的情況

Sanic波動爲11-31ms,而Tornado已經最低到62了,最高到100多毫秒,顯然在100毫秒內,併發超過50的時候,還是Sanic更勝一籌,Sanic再贏一局,Sanic2:0Tornado

sleep 50ms&100個線程壓的情況

Sanic波動爲51-61,Tornado爲53-73,兩個框架之間差別不大,建議使用Sanic,Sanic3:0Tornado

sleep 100ms & 100個線程壓的情況

Sanic波動爲105到130,Tornado波動爲101-140,選擇誰都可以。這次打個平手Sanic2:0Tornado

綜上所述,sanic性能確實優於tornado。所以新框架採用sanic來開發。

0x04 框架設計

  • log庫:傳遞logid和自我生成logid,且能支撐多進程,不丟日誌的情況。
  • redis庫:擁有連接池,
  • mysql庫:擁有連接池。
  • python版本庫管理要簡單,易發佈,易部署。
  • 要有利用新框架新建項目,更新框架的工具,不用用戶每次自己下載然後copy,改名。

基於以上的設計,調研了一下用到的庫。

sanic

  • python3.7+
  • sanic最新版

redis

redis-py:官方推薦,配合hiredis-py會得到性能的極致提升。

mysql

pymysqlpool:mysql連接池,包裝pymysql提供連接池的功能。
還可以使用pymysql+dbutils自己編寫連接池的代碼。

config

配置文件支持ini格式的,保持跟go的一樣。
ConfigParser

官方使用py的配置文件,採用該方式更方便。

logging

  • 日誌庫要有logid
  • sanic自帶有log庫,使用的就是python自帶的基礎庫logging。
  • 解決多進程丟日誌的問題使用concurrent-log-handler,不要使用ConcurrentLogHandler很久不更新了,bug多。
  • concurrent-log-handler只支持日誌大小滾動,不支持時間滾動。
  • 目前python最吃香的log庫是loguru,我們也集成了,該庫支持多線程,雖然不是進程安全的,但是也有方式支持多進程log。
  • 需要對比下這兩個庫在sanic下性能,選擇一個最優的。
  • logging(logging.handlers.TimedRotatingFileHandler多進程丟日誌)
  • logging(concurrent-log-handler),帶了進程鎖以後嚴重影響了框架性能.
  • loguru
  • 對於高qps的場景下,可以讓其丟日誌,使用普通的TimedRotatingFileHandler。對於qps要求低的場景,建議使用concurrent-log-handler保證多進程下日誌不丟失。

pipenv

  • python依賴包管理
  • python項目遷移

click構建命令行工具

利用click開發。一條命令就能新建一個新的項目,直接可運行。

框架性能

目前框架開發已經完成,內部已經發布了第一個版本,對該框架的性能也進行了一個壓測。

sanic空邏輯性能測試(只返回一個json串)

  • 1個線程壓1個進程qps:6000
  • 1個線程壓2個進程qps:6600
  • 2個線程壓1個進程qps:10000
  • 4個線程壓1個進程qps:9600,單進程支持4個併發時出現瓶頸。

單進程處理能力比較強,建議優先單進程

  • 2個線程壓2個進程qps:11000
  • 4個線程壓4個進程qps:13000
  • 8個線程壓8個進程qps:14000
  • 16個線程壓8個進程qps:13000,瓶頸出現

sanic加入log測試(打印一條log到文件,多進程不丟日誌,啓用進程安全的log)

  • 1個線程壓1個進程qps:1500

  • 1個線程壓2個進程qps:1300

  • 2個線程壓1個進程qps:1900

  • 4個線程壓1個進程qps:2100,但是耗時已經上來了。

  • 2個線程壓2個進程qps:2000

  • 4個線程壓4個進程qps:2400,耗時已經增加許多,瓶頸

  • 8個線程壓8個進程qps:2600,耗時增加。

  • 16個線程壓8個進程qps:2800,耗時已經飆升了。

sanic加入log測試(打印一條log到文件,多進程丟日誌,不啓用進程安全的log)

  • 1個線程壓1個進程qps:3200

  • 1個線程壓2個進程qps:2200

  • 2個線程壓1個進程qps:4800

  • 4個線程壓1個進程qps:4800

  • 2個線程壓2個進程qps:4000

  • 4個線程壓4個進程qps:5200

  • 8個線程壓8個進程qps:4400

sanic加入redis性能測試

  • redis(set)操作,1個線程壓1個進程qps:3700

  • redis(set)操作,1個線程壓2個進程qps:3200,反而降低了。

  • redis(set)操作,2個線程壓1個進程qps:4700

  • redis(set)操作,4個線程壓1個進程qps:4600,單進程出現瓶頸。

  • redis(set)操作,2個線程壓2個進程qps:5500

  • redis(set)操作,4個線程壓4個進程qps:8100

  • redis(set)操作,8個線程壓8個進程qps:8000,下降,瓶頸

  • redis(set)操作,16個線程壓8個進程qps:8300,瓶頸

Redis穩定性較好,且增加線程或者進程呈線性增長,直到到達瓶頸。且嘗試增加服務能力的時候,最好是起和併發量一樣的進程數,不然來回切進程反而會降低qps。

壓測結果分析

建議採用單進程,開啓普通log,無包裝多進程安全的log。
最後我們測試了下4個線程壓一個進程,打印一條log(普通log),set一次redis的業務,qps:3200

在不要求日誌不丟的,耗時可以放寬的情況下,可以提升進程數來提升性能。

0x05

雖然sanic框架性能很好,但是python自身的性能,以及python庫的一些影響,仍然不能達到go的性能。但是應付一些中小型項目是可以了。後面持續優化,有機會開源出來。

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