目的:提高单个接口的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-就要编码,关注公众号获取更多内容!