目的:提高單個接口的QPS(每秒查詢率QPS是對一個特定的查詢服務器在規定時間內所處理流量多少的衡量標準。)html
工具:本文不關注工具的使用,簡單起見,使用apache的ab
工具git
步驟:先測試並優化一個空接口(直接返回請求的接口),拿到一個極限QPS,而後分別引進Redis、MySQL的接口分別進行測試並優化,使其儘可能接近極限QPS。步驟嚴格遵循控制變量法。github
環境:爲獲得儘可能接近真實生產環境的數據,須要提早創建一個與生產環境幾乎一致的壓測環境,在壓測環境上進行測試。當前環境爲:AWS EC2上部署的K8S集羣,共2個Node節點,CPU爲2核。spring
先在代碼中寫上一個直接返回請求的test接口,而後部署到容器集羣中做爲被壓測的對象,再登陸到另一個做爲施壓機進行壓測。sql
同時注意檢測CPU,內存,網絡狀態,由於對於一個空接口,QPS達到上限必定是由於該服務所在宿主機的某處性能達到瓶頸。數據庫
命令:ab -n 3000 -c 300 "http://10.1.2.3/test/"
apache
測試環境:編程
Requests per second: 1448.86 [#/sec] (mean)
Time per request: 69.020 [ms] (mean)
Connection Times (ms)
min mean[+/-sd] median max
Connect: 1 1 1.1 1 8
Processing: 5 66 29.4 63 164
Waiting: 5 66 29.4 63 164
Total: 5 67 29.7 64 165
複製代碼
在整個壓測過程當中,使用top -d 0.3
發現CPU達到瓶頸,爲驗證咱們的猜測,將相同的服務部署到另一個CPU性能更好的機器上測試。api
CPU性能提高以後的本地環境:瀏覽器
Requests per second: 3395.25 [#/sec] (mean)
Time per request: 29.453 [ms] (mean)
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.7 0 3
Processing: 2 28 13.3 26 84
Waiting: 2 28 13.3 26 84
Total: 2 29 13.4 26 86
複製代碼
Processing time的解釋:The server response time—i.e., the time it took for the server to process the request and send a reply
結論很明顯,在Connect鏈接時長几乎一致的狀況下,服務器端處理時間大大減小,因此CPU對QPS的影響是最直接的。
另外,依據經驗判斷,2核CPU的機器本地壓測只有3k是不正常的一個數據,因此猜想框架性能有差別,因此對不一樣框架進行了空接口的測試,數據以下:
beego: 12k , go micro: 3.5k, spring boot: 5.5k
結論是框架直接大幅度影響性能,分析一下,go micro與beego、spring boot的區別是它是一個微服務架構的框架,須要consul、api gateway、後臺服務一塊兒啓動,一個請求來到gateway以後,可能須要查詢consul拿到後臺服務的地址,還須要將JSON格式轉換爲gRPC格式發送給後臺服務,而後接收後臺服務的返回,這直接致使了在CPU計算量、網絡傳輸量最少兩倍於單體應用,即便go micro的網關和後臺服務分開在兩臺機器上部署,測試以後QPS也只能到達5.5k。
我是直接感覺到了一個微服務的缺點:相比於單體架構服務的直接返回請求,微服務架構服務的開銷是極可能更大的,因此在選擇架構的時候須要在性能與微服務帶來的優點(服務解耦、職責單1、獨立開發構建部署測試)上進行衡量,固然若是你服務器夠多性可以好,當我沒說。
在CPU沒法提高、框架沒法改變的狀況下,只能在框架和服務的配置、代碼的使用、架構層面進行優化。
參考go micro做者給的方法,對框架的配置進行優化:
--client_pool_size=10 # enables the client side connection pool
再次在測試環境下測試,QPS提高300,到達1700。
api gateway是全部流量都會走的地方,因此是優化的重要部分,咱們先測試一下刪掉其中的業務代碼,QPS提高了600,到達2300,因此優化了一下這裏的邏輯,QPS到達2100
if !strings.Contains(cookies, "xxx") {
return nil, errors.New("no xxx cookie found")
}
複製代碼
替換掉
cookiesArr := strings.Split(cookies, ";")
for _, v := range cookiesArr {
cookie := strings.Split(v, "=")
if strings.Trim(cookie[0], " ") == "xxx" {
sid = cookie[1]
break
}
}
複製代碼
最後,使用k8s的自動伸縮機制,另外部署了一組api gateway和後臺服務到不一樣的Node上,至關於配置好了兩臺機器的負載均衡,QPS達到3200
找到一個僅包含一次Redis get操做且無返回數據的接口做爲測試接口,在空接口優化以後的基礎上進行測試,QPS:2000
對於鏈接另外的一個服務或中間件的狀況,優化的方式並很少,最經常使用的優化方式就是提高鏈接數,在服務內部將鏈接Redis的鏈接數提MaxIdle提高到800,MaxActive提高到10000,QPS提高500,達到2500。
另外能夠查看Redis服務的一些配置和性能圖標,當前環境下使用的是AWS上的Redis服務,可配置的項目很少,使用的是兩個節點的配置,在壓測的過程當中查看CPU佔用、鏈接數佔用、內存佔用,均未發現達到上限,因此Redis服務沒有能夠優化的餘地。
在高併發場景下,爲了儘可能提高QPS,查詢的操做應該儘可能所有使用Redis作緩存代替或者將請求儘可能攔截在查詢數據庫以前,因此數據庫的查詢操做並非QPS提高關注的重點。
但須要對數據庫進行一些通用的優化,好比主從複製,讀寫分離、提高鏈接數、在大數據量下分表、優化SQL、創建索引。下面以一個查詢操做爲例,一條複雜的sql爲例作一次SQL優化與索引創建,以單次的查詢時間爲目標進行優化。
先用存儲過程準備50w條數據
DELIMITER $$
CREATE PROCEDURE prepare_data()
BEGIN
DECLARE i INT DEFAULT 1;
WHILE i < 500000 DO
INSERT INTO game_record (app_id, token, game_match_id, user_id, nickname, device_id, prize_grade, start_time, score) VALUES ('appid', 'abc', concat('xx',i), '70961908', 'asdfafd', 'xcao', 2, 1543894842, i );
SET i = i + 1;
END WHILE;
END$$
DELIMITER ;
call prepare_data()
複製代碼
如何選擇合適的列創建索引?
對於如下SQL:
select * from game_record where app_id = ? and token=? and score != -1 order by prize_grade, score limit 50
複製代碼
優化前時間: 0.551s
對於order by操做,能夠創建複合索引:
create index grade_and_score on game_record(prize_grade, score)
複製代碼
優化後時間: 0.194s
這裏不貼具體的SQL語句了,如下是一些SQL通用優化方式:
還有一些具體的數據庫優化策略能夠參考這裏
以上是僅僅是排除代碼以後的服務和中間件壓測,還應該加入代碼邏輯進行更加全面的測試,而後對代碼進行優化,具體優化方式請參考對應編程語言的優化方式。
若是之後碰到性能瓶頸,擴機器是最簡單高效的,或者更換其餘框架,或則還能夠深刻優化一下api gateway和後臺服務交互的數據傳輸性能,由於這塊是直接致使CPU達到瓶頸的緣由。
若是想作好一次壓測和優化,須要很是清晰的思路、高效的壓測方法、排查問題的套路、解決問題的方案,但最基礎的仍是須要知道一個請求從瀏覽器發送以後,到返回到瀏覽器,之間到底經歷過什麼,這篇比較基礎的文章能夠幫你瞭解一些,但還不夠,好比此次調優中還涉及到數據庫優化、Go語言、硬件性能、AWS、Docker、K8S這樣的雲平臺和容器技術、容器編排工具,因此壓測調優是一次對本身掌握服務端整個架構和細節的考驗和學習過程。
91Code-就要編碼,關注公衆號獲取更多內容!