1. 前言
下面咱们曾经做到了接口以及场景压测,通过控制台输入后果,咱们只须要将后果收集整理下来,最初汇总到excel上,此次压测报告就能够实现了,但收集报告也挺麻烦的,交给谁呢……
找了一圈、没找到违心接手的人,该怎么办呢……思考了会儿还是决定看看是否通过程序解决咱们的难题吧,毕竟整顿表格太累╯﹏╰
2. 收集后果
通过查阅官网文档,咱们发现官网提供了把数据保留成Json、csv、以及数据库三种形式,甚至还有小伙伴踊跃的对接要把数据保留到Es中,那选个最简略的吧!
要不抉择Json吧,不须要依赖内部存储,很简略,我感觉应该可试,试一下看看:输出命令:
crank --config load.benchmarks.yml --scenario api --load.framework net5.0 --application.framework net5.0 --json 1.json --profile local --profile crankAgent1 --description "wrk2-获取用户详情" --profile defaultParamLocal
最初失去后果:
{ "returnCode": 0, "jobResults": { "jobs": { "load": { "results": { "http/firstrequest": 85.0, "wrk2/latency/mean": 1.81, "wrk2/latency/max": 1.81, "wrk2/requests": 2.0, "wrk2/errors/badresponses": 0.0, "wrk2/errors/socketerrors": 0.0, "wrk2/latency/50": 1.81, "wrk2/latency/distribution": [ [ { "latency_us": 1.812, "count": 1.0, "percentile": 0.0 }, { "latency_us": 1.812, "count": 1.0, "percentile": 1.0 } ] ] } } } }}
残缺的导出后果
好吧,数据有点少,如同数据不太够吧,这些信息怎么解决能做成报表呢,再说了数据不对吧,QPS、提早呢?好吧,被看进去了,因为信息太多,我删了一点点(也就1000多行指标信息吧),看来这个不行,用json的话还得配合个程序好难……
csv不必再试了,如果也是单个文本的话,也是这样,还得配个程序,都不能单干,干啥都得搭伴,那试试数据库如何
crank --config load.benchmarks.yml --scenario api --load.framework net5.0 --application.framework net5.0 --sql "Server=localhost;DataBase=crank;uid=sa;pwd=P@ssw0rd;" --table "local" --profile local --profile crankAgent1 --description "wrk2-获取用户详情" --profile defaultParamLocal
咱们依据压测环境,把不同的压测指标存储到不同的数据库的表中,以后是本地环境,即 table = local
最初咱们把数据保留到了数据库中,那这样做回头须要报告的时候,我查问下数据库搞进去就好了,终于松了一口气,但好景不长,发现数据库存储也有个坑,之前json中看到的后果居然在一个字段中存储,不过幸好SqlServer 2016之后反对了json,能够通过json解析搞定,但其中参数名有/等特殊字符,sql server解决不了,难道又得写个网站能力展现这些数据了吗??真的绕不开搭伴干活这个坑吗?
微软不会就做出个这么鸡肋的货色,还必须要配个前端能力分明的搞进去指标吧……还得用vue、好吧,我晓得尽管当初有blazer,能够用C#开发,但还是心愿不那么麻烦,又认真查找了一番,发现Crank能够对后果做二次解决,能够通过script,不错的货色,既然sql server数据库无奈反对特殊字符,那我加些新参数勾销特殊字符不就好了,新建scripts.profiles.yml
scripts: changeTarget: | benchmarks.jobs.load.results["cpu"] = benchmarks.jobs.load.results["benchmarks/cpu"] benchmarks.jobs.load.results["cpuRaw"] = benchmarks.jobs.load.results["benchmarks/cpu/raw"] benchmarks.jobs.load.results["workingSet"] = benchmarks.jobs.load.results["benchmarks/working-set"] benchmarks.jobs.load.results["privateMemory"] = benchmarks.jobs.load.results["benchmarks/private-memory"] benchmarks.jobs.load.results["totalRequests"] = benchmarks.jobs.load.results["bombardier/requests;http/requests"] benchmarks.jobs.load.results["badResponses"] = benchmarks.jobs.load.results["bombardier/badresponses;http/requests/badresponses"] benchmarks.jobs.load.results["requestSec"] = benchmarks.jobs.load.results["bombardier/rps/mean;http/rps/mean"] benchmarks.jobs.load.results["requestSecMax"] = benchmarks.jobs.load.results["bombardier/rps/max;http/rps/max"] benchmarks.jobs.load.results["latencyMean"] = benchmarks.jobs.load.results["bombardier/latency/mean;http/latency/mean"] benchmarks.jobs.load.results["latencyMax"] = benchmarks.jobs.load.results["bombardier/latency/max;http/latency/max"] benchmarks.jobs.load.results["bombardierRaw"] = benchmarks.jobs.load.results["bombardier/raw"]
以上解决的数据是基于bombardier的,同理大家能够实现对wrk或者其余的数据处理
通过以上操作,咱们胜利的把特殊字符的参数改成了没有特殊字符的参数,那接下来执行查问sql就能够了。
SELECT Description as '场景', JSON_VALUE (Document,'$.jobs.load.results.cpu') AS 'CPU使用率(%)', JSON_VALUE (Document,'$.jobs.load.results.cpuRaw') AS '多核CPU使用率(%)', JSON_VALUE (Document,'$.jobs.load.results.workingSet') AS '内存应用(MB)', JSON_VALUE (Document,'$.jobs.load.results.privateMemory') AS '过程应用的公有内存量(MB)', ROUND(JSON_VALUE (Document,'$.jobs.load.results.totalRequests'),0) AS '总发送申请数', ROUND(JSON_VALUE (Document,'$.jobs.load.results.badResponses'),0) AS '异样申请数', ROUND(JSON_VALUE (Document,'$.jobs.load.results.requestSec'),0) AS '每秒反对申请数', ROUND(JSON_VALUE (Document,'$.jobs.load.results.requestSecMax'),0) AS '每秒最大反对申请数', ROUND(JSON_VALUE (Document,'$.jobs.load.results.latencyMean'),0) AS '均匀延迟时间(us)', ROUND(JSON_VALUE (Document,'$.jobs.load.results.latencyMax'),0) AS '最大延迟时间(us)', CONVERT(varchar(100),DATEADD(HOUR, 8, DateTimeUtc),20) as '工夫'FROM dev;
3. 如何剖析瓶颈
通过下面的操作,咱们曾经能够轻松的实现对场景的压测,并能疾速生成绝对应的报表信息,那正题来了,能够模仿高并发场景,那如何剖析瓶颈呢?毕竟报告只是为了通晓以后的零碎指标,而咱们更心愿的是晓得以后零碎的瓶颈是多少,怎么突破瓶颈,实现冲破呢……
首先咱们要先理解咱们以后的利用的架构,比方咱们当初应用的是微服务架构,那么
- 利用拆分为几个服务?理解分明每个服务的作用
- 服务之间的调用关系
- 各服务依赖的根底服务有哪些、根底服务根本的信息状况
举例咱们以后的微服务架构如下:
通过架构图能够疾速理解到我的项目构造,咱们能够看到用户拜访web端,web端依据申请对应去查问redis或者通过http、grpc调用服务获取数据、各服务又通过redis、db获取数据。
首先咱们先通过crank把以后的数据指标保留入库。调出其中不太现实的接口开始剖析。
在这里咱们拿两个压测接口举例:
- 获取首页Banner、QPS:3800 /s (Get)
- 下单、QPS:8 /s (Post)
3.1. 获取首页Banner
通过单测首页banner的接口,QPS是3800多不到4000这样,尽管这个指标还不错,但咱们依然感觉很慢,毕竟首页banner就是很简略几个图片+题目组合的数据,数据量不大,并且是直连Redis,仅在Redis不存在时才查问对应服务获取banner数据,这样的QPS切实不应该,并且这个还是仅压测独自的banner,如果首页同时压测十几个接口,那其性能会暴降十倍不止,这样必定是不行的
咱们又压测了一次首页banner接口,发现有几个疑点:
- redis申请数彷徨在3800左右的样子,网络带宽占用1M的样子,无奈持续上涨
- 查看web服务,发现时不时的会有调用服务超时出错的问题,Db的访问量有上涨,但不显著,很快就上来了
思考: Redis的申请数与最初的压测后果差不多,最初倒也对上了,但为什么redis的申请数这么低呢?难道是带宽限度!!
尽管是单机redis,但4000也相对不可能是它的瓶颈,狐疑是带宽被限度了,应该就是带宽被限度了,起初跟运维一番切磋后,失去论断是redis没限度带宽……
那为什么不行呢,这么奇怪,redis不可能就这么点并发就不行了,算了还是写个程序试一下吧,看看是不是真的测试环境不给力,redis配置太差了,一番操作后发现,同一个redis数据,redis读能够到6万8,不到7万、带宽占用10M,redis终于洗清了它的嫌疑,此接口的QPS不行与Redis无关,但这么简略的一个构造为什么QPS就上不去呢……,如果不是redis的问题,那会不会是因为申请就没到redis上,是因为压测机的强度不够,导致申请没到redis……过后冒出来这个有点愚昧的想法,那就减少压测机的数量,通过更改负载压测机配置,1台压测机升到了3台,但惋惜的是单台压测机的指标不升反降,最初所有压测机的指标加到一起正好与之前一台压测机的压测后果差不多一样,那阐明QPS低与压测机无关,起初想到试试通过减少多副原本晋升QPS,起初web正本由1台晋升到了3台,之前提到的服务调用报错的状况更加重大,之前只是偶然有一个谬误,但晋升web正本后,看到一大片的谬误
- 提醒Thread is busy,很多线程开始期待
- 大量的服务调用超时,DB查问迟缓
最初QPS 1000多一点,有几千个失败的谬误,这自觉的晋升正本貌似不大无效,之前只管Qps不高,但起码也在4000,DB也没事,这波神操作后QPS直降4分之3,DB还差点崩了,思维滑坡了,做了负优化……
持续思考,为何晋升正本,QPS不升反降,为何呈现大量的调用超时、为何DB会差点被干崩,我只是查问个redis,跟DB有毛关系啊!奇了怪了,看看代码怎么写的吧……烧脑
public async Task<List<BannerResponse>> GetListAsync(){ List<BannerResponse> result = new List<BannerResponse>(); try { var cacheKey = "banner_all"; var cacheResult = await _redisClient.GetAsync<List<BannerResponse>>(cacheKey); if (cacheResult == null) { result = this.GetListServiceAsync().Result; _redisClient.SetAsync(cacheKey, result, new() { DistributedCacheEntryOptions = new() { AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(5) } }).Wait(); } else { result = cacheResult; } } catch (Exception e) { result = await this.GetListServiceAsync(); } return result;}
看了代码后发现,仅当Reids查问不到的时候,会调用对应服务查问数据,对应服务再查问DB获取数据,另外查问异样时,会再次调用服务查问后果,确保返回后果肯定是正确的,看似没问题,但为何压测会呈现下面那些奇怪景象呢……
申请超时、大量期待,那就是正好redis不存在,穿透到对应的服务查问DB了,而后压测同一时刻数据量过大,同一时刻查问到的Reids都是没有数据,最初导致调用服务的数量急剧回升,导致响应迟缓,超时加剧,线程因超时开释不及时,又导致可用线程较少。
这块咱们查找到对应的日志显示以下信息
System.TimeoutException: Timeout performing GET MyKey, inst: 2, mgr: Inactive, queue: 6, qu: 0, qs: 6, qc: 0, wr: 0, wq: 0, in: 0, ar: 0,IOCP: (Busy=6,Free=994,Min=8,Max=1000), WORKER: (Busy=152,Free=816,Min=8,Max=32767)
- 那么咱们能够调整Startup.cs:
public void ConfigureServices(IServiceCollection services){ ThreadPool.GetMinThreads(out int workerThreads, out int completionPortThreads); ThreadPool.SetMinThreads(1000, completionPortThreads);//依据状况调整最小工作线程,防止因创立线程导致的耗时操作 ……………………………………………………………此处省略…………………………………………………………………………………………………………}
- web服务调用底层服务太慢,那么晋升底层服务的响应速度(优化代码)或者进步解决能力(晋升正本)
- 避免高并发状况下全副穿透到上层,减少底层服务的压力
前两点也是一个好的方法,但不是最好的解决办法,最好还是不要穿透到底层服务,如果reids不存在,就放一个申请过来是最好的,拿到数据就长久化到redis,不要总穿透到上层服务,那么怎么做呢,最简略的方法就是应用加锁,但加锁会影响性能,但这个咱们能承受,起初调整加锁测试,穿透到底层服务的状况没有了,但很惋惜,申请数的确会随着正本的减少而减少,然而切实是有点不难看,起初又测试了下另外一个获取缓存数据的后果,后果QPS:1000多一点,比banner还要低的多,两边明明都应用的是Reids,性能为何还有这么大的差异,为何咱们写的redis的demo就能到6万多的QPS,两边都是拿的一个缓存,差距有这么大?难道是封装redis的sdk有问题?起初认真比照了起初写的redis的demo与banner调用redis的接口发现,一个是间接查问的redis的字符串,一个是封装redis的sdk,多了一个反序列化的过程,最初通过测试,反序列化之后性能升高了十几倍,好吧看来只能晋升正本了……但为何另外的接口也是从redis获取,性能跟banner的接口不一样呢!!
通过认真比照发现,差异是信息量,QPS更低的接口的数据量更大,那后果就有了,随着数据量的减少,QPS会进一步升高,那这样一来的话,减少正本的作用不大啊,谁晓得会不会有一个接口的数据量很大,那性能岂不是差的要死,那还怎么玩,能不能晋升反序列化的性能或者不反序列化呢,通过认真思考,想到了二级缓存,如果用到了二级缓存,内存中有就不须要查问redis,也不须要再反序列化,那么性能应该有所晋升,最初的构造如下图:
最初通过压测发现,单正本QPS靠近50000,比最开始晋升12倍,并且也不会呈现服务调用超时,DB解体等问题、且内存应用安稳
此次压测发现其banner这类场景的性能瓶颈在反序列化,而非Redis、DB,如果依照一开始不分明其工作原理、自觉的调整正本数,可能最初会加剧零碎的雪崩,而如果咱们把DB资源、Redis资源自觉上调、并不会对最初的后果有太大帮忙,最多也只是延缓解体的工夫而已
3.2. 下单
下单的QPS是8,这样的QPS曾经无法忍受了,每秒只有十个申请能够下单胜利,如果两头再呈现一个库存有余、账户余额有余、流动资格不够等等,理论能下单的人用一个手能够数过来,真的就这么惨……尽管下单的确很费性能,不过的确不至于这么低吧,先看下下单流程吧
简化后的下单流程就这么简略,web通过dapr的actor服务调用order service,而后就是漫长的查问db、操作redis操作,因波及业务代码、具体代码就不再放出,但能够简略说一下其中做的事件,查看账户余额、重复的减少redis库存确保库存平安、查看是否满足流动、为推荐人计算待结算佣金等等一系列操作,整个看下来把人看懵了,经常是刚看了下面的,看上面代码的时候遗记下面具体干了什么事,代码太多了,一个办法数千行,其中再调用一些数百行的代码,真的吐血了,未免感叹我司的开发小哥哥是真的弱小,这么简单的业务竟然能这么"顺畅"的跑起来,前面还有N个需要期待加到下单上,果然不是个别人
不过话说回来,尽管是业务是真的多,也真的乱,不过这样搞也不至于QPS才只有8这么可怜吧,服务器的解决能力可不是二十几年前的电脑能够比较的,单正本8核16G的配置不反对这么拉胯吧,再看一下到底谁才是真正的幕后黑手……
但到底哪里性能瓶颈在哪里,这块就要出杀手锏了
通过Tracing能够很分明的看到各节点的耗时状况,这将对咱们剖析瓶颈提供了十分大的帮忙、咱们看到了尽管有几十次的查问DB操作,但DB还挺给力,根本也再很短时间内就给出了响应,那剩余时间消耗到了哪里呢?咱们看到整体耗时11s、但查问Db加起来也仅仅不到1s,那么残余操作都在哪里?要晓得哪怕咱们优化DB查问性能,缩小DB查问,那晋升的性能对当初的后果也是微不足道
联合Tracing以及下单流程图,咱们发现从Web到Order Service是通过actor来实现的,那会不是这里耗时影响的呢?
但dapr是个新常识、开发的小哥哥速度真快,这么快就用上dapr了()不晓得小哥哥的头发还有多少……
疾速去找到下单应用actor的中央,如下:
[HttpPost][Authorize]public async Task<CreateOrderResponse> CreeateOrder([FromBody] CreateOrderModel request){ string actionType = "SalesOrderActor"; var salesOrderActor = ActorProxy.Create<ISalesOrderActor>(new ActorId(request.SkuList.OrderBy(sku => sku.Sku).FirstOrDefault().Sku), actionType); request.AccountId = Account.Id; var result = await salesOrderActor.CreateOrderAsync(request); return new Mapping<ParentSalesOrderListViewModel, CreateOrderResponse>().Map(result);}
咱们看到了这边代码非常简略,获取商品信息的第一个sku编号作为actor的actorid应用,而后失去下单的actor,之后调用actor中的创立订单办法最初失去下单后果,这边的代码太简略了,让人心情愉快,那这块会不会有可能影响下单速度呢?它是不是那个性能瓶颈最大的幕后黑手?
首先这块咱们就须要理解下什么是Dapr、Actor又是什么,不理解这些常识咱们只能靠抓阄来猜这块是不是瓶颈了……
Dapr 全称是Distributed Application Runtime,分布式应用运行时,并于往年退出了 CNCF 的孵化我的项目,目前Github的star高达16k,相干的学习文档在文档底部能够找到,我也是看着上面的文档理解dapr
通过理解actor,咱们发现用sku作为actorid是极不明智的抉择,像秒杀这类商品不就是抢的指定规格的商品吗?如果这样一来,这不是在压测actor吗?这块咱们跟对应的开发小哥哥沟通了下,通过调整actorid顺利将Qps晋升到了60作用,前面又通过优化缩小db查问、调整业务规定的程序等操作顺利将QPS晋升到了不到一倍,尽管还是很低,不过接下来的优化工作就须要再深层次的调整业务代码了……
4. 总结
通过实战咱们总结出剖析瓶颈从以下几步走:
- 通过第一轮的压测获取性能差的接口以及指标
- 通过与开发沟通或者本人查看源码的形式梳理接口流程
- 通过剖析其我的项目所占用资源状况、依赖第三方根底占用资源状况以及Tracing更进一步的确定瓶颈大略的点在哪几块
- 通过重复测试调整确定性能瓶颈的最大黑手
- 将最初的论断与相干开发、运维人员沟通,确保都通晓瓶颈在哪里,最初优化瓶颈
知识点:
Dapr
- 手把手教你学Dapr系列
Tracing
OpenTracing 简介、对于OpenTracing后续咱们也会开源,能够提前关注咱们的开源我的项目
- Masa.BuildingBlocks
- Masa.Contrib
开源地址
MASA.BuildingBlocks:https://github.com/masastack/...
MASA.Contrib:https://github.com/masastack/...
MASA.Utils:https://github.com/masastack/...
MASA.EShop:https://github.com/masalabs/M...
MASA.Blazor:https://github.com/BlazorComp...
如果你对咱们的 MASA Framework 感兴趣,无论是代码奉献、应用、提 Issue,欢送分割咱们