記一次性能優化,單臺4核8G機器支撐5萬QPS

前言

這篇文章的主題是記錄一次Python程序的性能優化,在優化的過程當中遇到的問題,以及如何去解決的。爲你們提供一個優化的思路,首先要聲明的一點是,個人方式不是惟一的,你們在性能優化之路上遇到的問題都絕對不止一個解決方案。html

如何優化

首先你們要明確的一點是,脫離需求談優化都是耍流氓,因此有誰跟你說在xx機器上實現了百萬併發,基本上能夠認爲是不懂裝懂了,單純的併發數徹底是無心義的。其次,咱們優化以前必需要有一個目標,須要優化到什麼程度,沒有明確目標的優化是不可控的。再而後,咱們必須明確的找出性能瓶頸在哪裏,而不能漫無目的的一通亂搞。前端

需求描述

這個項目是我在上家公司負責一個單獨的模塊,原本是集成在主站代碼中的,後來由於併發太大,爲了防止出現問題後拖累主站服務,全部由我一我的負責拆分出來。對這個模塊的拆分要求是,壓力測試QPS不能低於3萬,數據庫負責不能超過50%,服務器負載不能超過70%, 單次請求時長不能超過70ms,錯誤率不能超過5%。linux

環境的配置以下:
服務器:4核8G內存,centos7系統,ssd硬盤
數據庫:Mysql5.7,最大鏈接數800
緩存: redis, 1G容量。
以上環境都是購買自騰訊雲的服務。
壓測工具:locust,使用騰訊的彈性伸縮實現分佈式的壓測。web

需求描述以下:
用戶進入首頁,從數據庫中查詢是否有合適的彈窗配置,若是沒有,則繼續等待下一次請求、若是有合適的配置,則返回給前端。這裏開始則有多個條件分支,若是用戶點擊了彈窗,則記錄用戶點擊,而且在配置的時間內再也不返回配置,若是用戶未點擊,則24小時後繼續返回本次配置,若是用戶點擊了,可是後續沒有配置了,則接着等待下一次。redis

重點分析

根據需求,咱們知道了有幾個重要的點,一、須要找出合適用戶的彈窗配置,二、須要記錄用戶下一次返回配置的時間並記錄到數據庫中,三、須要記錄用戶對返回的配置執行了什麼操做並記錄到數據庫中。sql

調優

咱們能夠看到,上述三個重點都存在數據庫的操做,不僅有讀庫,還有寫庫操做。從這裏咱們能夠看到若是不加緩存的話,全部的請求都壓到數據庫,勢必會佔滿所有鏈接數,出現拒絕訪問的錯誤,同時由於sql執行過慢,致使請求沒法及時返回。因此,咱們首先要作的就是講寫庫操做剝離開來,提高每一次請求響應速度,優化數據庫鏈接。整個系統的架構圖以下:shell

clipboard.png

將寫庫操做放到一個先進先出的消息隊列中來作,爲了減小複雜度,使用了redis的list來作這個消息隊列。數據庫

而後進行壓測,結果以下:編程

QPS在6000左右502錯誤大幅上升至30%,服務器cpu在60%-70%之間來回跳動,數據庫鏈接數被佔滿tcp鏈接數爲6000左右,很明顯,問題仍是出在數據庫,通過排查sql語句,查詢到緣由就是找出合適用戶的配置操做時每次請求都要讀取數據庫所致使的鏈接數被用完。由於咱們的鏈接數只有800,一旦請求過多,勢必會致使數據庫瓶頸。好了,問題找到了,咱們繼續優化,更新的架構以下segmentfault

clipboard.png

咱們將所有的配置都加載到緩存中,只有在緩存中沒有配置的時候纔會去讀取數據庫。

接下來咱們再次壓測,結果以下:
QPS壓到2萬左右的時候就上不去了,服務器cpu在60%-80%之間跳動,數據庫鏈接數爲300個左右,每秒tpc鏈接數爲1.5萬左右。

這個問題是困擾我比較久的一個問題,由於咱們能夠看到,咱們2萬的QPS,可是tcp鏈接數卻並無達到2萬,我猜想,tcp鏈接數就是引起瓶頸的問題,可是由於什麼緣由所引起的暫時沒法找出來。

這個時候猜想,既然是沒法創建tcp鏈接,是否有多是服務器限制了socket鏈接數,驗證猜想,咱們看一下,在終端輸入ulimit -n命令,顯示的結果爲65535,看到這裏,以爲socket鏈接數並非限制咱們的緣由,爲了驗證猜想,將socket鏈接數調大爲100001.

再次進行壓測,結果以下:

QPS壓到2.2萬左右的時候就上不去了,服務器cpu在60%-80%之間跳動,數據庫鏈接數爲300個左右,每秒tpc鏈接數爲1.7萬左右。

雖然有一點提高,可是並無實質性的變化,接下來的幾天時間,我發現都沒法找到優化的方案,那幾天確實很難受,找不出來優化的方案,過了幾天,再次將問題梳理了一遍,發現,雖然socket鏈接數足夠,可是並無所有被用上,猜想,每次請求事後,tcp鏈接並無當即被釋放,致使socket沒法重用。通過查找資料,找到了問題所在,

tcp連接在通過四次握手結束鏈接後並不會當即釋放,而是處於timewait狀態,會等待一段時間,以防止客戶端後續的數據未被接收。

好了,問題找到了,咱們要接着優化,首先想到的就是調整tcp連接結束後等待時間,可是linux並無提供這一內核參數的調整,若是要改,必需要本身從新編譯內核,幸虧還有另外一個參數net.ipv4.tcp_max_tw_buckets, timewait 的數量,默認是 180000。咱們調整爲6000,而後打開timewait快速回收,和開啓重用,完整的參數優化以下

#timewait 的數量,默認是 180000。
net.ipv4.tcp_max_tw_buckets = 6000

net.ipv4.ip_local_port_range = 1024 65000

#啓用 timewait 快速回收。
net.ipv4.tcp_tw_recycle = 1

#開啓重用。容許將 TIME-WAIT sockets 從新用於新的 TCP 鏈接。
net.ipv4.tcp_tw_reuse = 1

咱們再次壓測,結果顯示:
QPS5萬,服務器cpu70%,數據庫鏈接正常,tcp鏈接正常,響應時間平均爲60ms,錯誤率爲0%。

結語

到此爲止,整個服務的開發、調優、和壓測就結束了。回顧這一次調優,獲得了不少經驗,最重要的是,深入理解了web開發不是一個獨立的個體,而是網絡、數據庫、編程語言、操做系統等多門學科結合的工程實踐,這就要求web開發人員有牢固的基礎知識,不然出現了問題還不知道怎麼分析查找。

ps:服務端開啓了 tcp_tw_recycle 和 tcp_tw_reuse是會致使一些問題的,咱們爲了優化選擇犧牲了一部分,得到另外一部分,這也是咱們要明確的,具體的問題能夠查看耗子叔的文章TCP 的那些事兒(上)

關於做者

  • Leoython,擅長Javascript, Python, Go,最近在研究Rust和k8s
  • E-Mail: leoython@gmail.com
  • 文章編寫於: 2019/01/31

轉載請註明出處:

https://segmentfault.com/a/11...

相關文章
相關標籤/搜索