编者按:高可用架构分享及传播在架构领域具有典型意义的文章,本文由杨硕在高可用架构群分享。
杨硕,现就职于美团,负责广告运营平台、美团点评广告系统对接等工作。曾就职于 Yahoo ! 北研,从事广告产品的研发工作。对于架构设计、性能调优、大数据、前端等领域均有所涉猎。业余时间除了 side project 外,还经常参加 Hackathon 比赛,并获得了美团第四届 Hackathon 优胜奖等奖项。
“目前美团压测项目接入的服务 40+,打压的次数 1000+,很好的完成了线上业务的打压需求。” —— 杨硕
美团内部的 RPC 服务大多构建在 Thrift 之上,在日常开发服务的过程中,需要针对这些服务进行压力测试(以下简称压测)来发现潜在问题。常用的方法有:
- 使用一些脚本语言如:Python、Ruby 等,读取线上日志构建请求,用多线程模拟用户请求进行压测;
- 使用开源工具进行压测。
然而,无论采取哪种方法,压测都是一个十分耗时而又繁琐的过程,主要痛点有:
- 需要写很多代码解析日志,还原请求,对于比较复杂的请求,解析很容易出错;
- 需要搭建脚本或者工具的运行环境,通常这一过程比较耗时;
- 由于打压方法没有统一,导致打压的结果指标比较混乱,有的结果甚至以终端输出的方式展示,非常不直观。
通常要让一个同学压测某一服务,很多时候需要耗费 2~3 天时间,并且很多同学的表情都是这样的:
为了解决这个问题提供一个简单好用的压测工具是十分有必要的。 有没有必要自己造轮子呢?如果有现成的解决方案,那再好不过了。
JMeter
JMeter 是一个比较老牌的压测工具,主要针对 HTTP 服务进行打压,该工具在以下方面并不满足美团内部的压测需求:
- 默认不支持 Thrift 的打压测试;
- 需要本地安装,并且配置复杂;
- 对于用户操作并不友好。
Twitter/iago
Twitter/iago 是一个由 Twitter 开源的压测工具,支持对 HTTP、Thrift 等服务进行压测,其主要问题如下:
- 对每个压测应用都需要创建一个项目;
- 压测结果并不直观;
- 流量重放依赖本地文件;
- 项目依赖于一个较老版本的 Scala,搭建不便;
- 相关文档比较少 。
除此之外,当时还考察了 Gatling、Grinder、Locust 等一些常见的压测工具,都因为适用场景和美团的需求有些出入而排除了。综上,针对当前压测工具的一些现状,构建一个简单易用的压测工具还是很有必要的。
针对之前提到的痛点,新的压测工具主要提供以下功能:
- 简单易用的操作界面(服务接入压测的时间应该控制在 1 小时以内);
- 清晰的图表能反映压测服务的各项指标;
- 满足包括 Thrift、HTTP 等服务的压测需求。
其实很多复杂问题在实用性和灵活性权衡过后,都能用一个简单模型表示,压测问题也一样,我们将压测过程抽象,可以用这个图表示:
首先,在 init 方法里面,进行一些初始化的工作,比如连接数据库,创建客户端等。
其次,在 run 方法里面发出压测请求,为了保证能够对服务产生足够的压力,这里通常采用多线程并发访问,同时记录每次请求的发起时间和结束时间,这两个时间的简单相减就能够得到每次请求的响应时间,利用该结果就可以计算出 TP90、平均响应时间、最大响应时间等指标。
最后,等压测结束后,通过 destroy 方法进行资源回收等工作,比如关闭 RPC服务的连接,关闭数据库等。
如果用接口可以这样表示:
无论是 HTTP 还是 Thrift 服务的压测,本质都是 run 方法的不同。
有了基本模型后,我们要解决的另外一个痛点是,如何简单地拷贝线上真实流量用来构建打压请求,一些大型的 Thrift 服务数据结构非常复杂,写打压脚本的时候需要很多代码来解析日志,而且容易出错。
为了避免这种情况,我们提供了一个叫 VCR(录像机)的工具来拷贝流量。VCR 能够将线上的请求序列化后写到 Redis 里面。考虑到用户需要查看具体请求和易用性等需求,最终选取了 JSON 格式作为序列化和反序列化的协议。同时需要部署在生产环境的一台机器上,为了降低对线上服务的影响,这里采取了单线程异步写的方式来拷贝流量,如下图:
解决了流量拷贝问题后,我们接下来需要按照我们采集的指标进行数据聚合运算。常见的指标有:最大响应时间,平均响应时间,QPS,TP90,TP50,这些指标可以评估服务的性能。
这里我们采用了 InfluxDB 来完成数据的聚合工作,所有聚合指标都是一行 SQL 搞定,非常快速。以 TP90 为例子,仅需要一行查询就能实现需求。
SELECT PERCENTILE(response_time, 90) FROM test_series GROUP BY time(10s)
综上,压测工具的流程图如下:
这是第一期的实现,大概花了 1 周左右的业余时间,等项目上线后,应用的压测接入时间得到了明显的缩短。下图是每个步骤的平均耗时:
这是第一期的实现,大概花了 1 周左右的业余时间,等项目上线后,应用的压测接入时间得到了明显的缩短。下图是每个步骤的平均耗时:
细心的同学可能已经发现了,VCR 是用虚线表示的,原因是对于新上线的服务,不需要拷贝流量,只需要在实现 runner 的过程中,通过代码构造请求数据即可。项目刚上线时,大家还不熟悉,第一次应用接入需要耗时 20 到 40min,等大家有经验后,下个应用的接入时间会缩短到 15min 左右。对于服务的 owner 来说,该压测工具更为灵活,他可以在 runner 接口中自由实现压测逻辑。如果二次打压的话或者修改参数的话,只需要在网页上点击鼠标即可完成。下图是其中某个服务的压测结果:
项目上线后,陆陆续续有各事业部的同学开始使用,大家普遍反应操作简单,但是也存在以下几个问题:
- 打压力度有时不够,这是由于一期项目开发时时间比较紧迫,只实现了单机的版本,单台机器自然难免有些”马力不足" ;
- 在接入 Thrift 应用的时候,需要用户提供 Thrift 生成的客户端代码,对于这种方式大家依然觉得繁琐,希望只上传一个 Thrift 文件就能够打压指定服务;
- 用户希望看到被打压机器的一些资源利用情况,比如 CPU 使用率,内存使用率等。
我们开始针对上述问题进行了解决,针对第一个问题,我们采用 Akka 来做一个分布式的扩展,将应用的角色分为 master、worker、counter。 Master 负责进行任务分发,worker 进行打压,然后将打压的的结果发给 counter 进行汇总写入 InfluxDB。
第二个问题,我们在简单模型的基础上进行更高级别的封装,用户上传 Thrift 文件后,我们将 Thrift 文件编译成为模板代码后,自动生成对应的接口。同时我们也有相应的 HTTP 服务适配模块。
针对最后一个问题,我们在压测机器上部署了 agent,压测期间机器的数据能够通过 agent 源源不断的提交上来,这样用户能够清晰的看到被打压服务所在机器的性能指标。
这是架构升级图,大家可以结合我刚刚说的三点看看:
整个项目的技术栈是 Ratpack + InfluxDB + ActiveJDBC + AngularJS,主力开发语言是 Groovy。
最后说下项目的发展情况,项目接入的服务 40+,打压的次数 1000+,很好的完成了同学们的打压需求。在做压测工具的时候最大的感触是:很多复杂的问题都能用很简单的模型来解释,在简单模型的基础上按照用户的需求不断开发适配才能成为一个好的框架,Storm,Hadoop 都是如此。项目有些细节的部分大家可以参考我们美团的技术 Blog http://t.cn/R4KWHrA。
1. 能否对比下 TCPCopy?
TCPCopy 不能做到对请求的灵活控制,而且 TCPCopy 需要单独在机器上部署,比较麻烦,当时确实是考虑的方案之一。
2. 线上流量拷贝是跟线上请求同时的吗?对线上业务影响有多大?
是和线上请求同时进行,美团的做法是单独拉一个拷贝流量的分支,修改两行代码上线,流量拷贝的话是采取单线程异步的方式,最多采集 1w 条日志。
3. VCR 是在代理服务器上完成流量拷贝还是跟业务代码同一个进程?
VCR 和业务代码是同一个进程,单开异步线程进行拷贝。
4. 为何选择 InfluxDB 数据库?有什么优势吗?不选其他的,比如 MongoDB 等等。
选用 InfluxDB 原因有两点,第一点是 InfluxDB 的底层是 KV 模型,可以兼容 RocksDB、Redis 等常见的 KV (最新版本不行了),写入速度较快。第二点是用 InfluxDB 进行数据聚合运算比较给力,一行 SQL 就能搞定。如果硬要比较的话,Druid 和 influxDB 都能快速实现数据的聚合运算,但是 Druid 主要用在 Big Data 方向。
5. 是否考虑开源?
开源在计划之中。
6. 业务新功能上线,需要修改压测代码吗?还是说只要在 UI 里面修改压测配置?
如果 RPC 接口不变,不用修改任何代码,直接 Web 界面上点击就行。
7. 项目只适合压测还是也可以做自动化测试?
现在主要用来压测,自动化测试的话如果是界面方面的测试,当前没有应用场景。如果涉及到具体业务逻辑的测试,则可以配合压测一起完成,主要逻辑写在 run 方法中即可。
8. 有没有压测策略配置错误,压错的时候?
有配置错误的时候,这时压测工具会在 Web 界面上打出出错信息,如果错误的压测线上服务,通常会有线上报警,一般情况下很难出现。
9. 新应用不兼容 VCR 如何处理,还有真实流量可以放大和缩小吗? 怎么做?
新应用的话没有流量拷贝的环节,一般 RD 会自己实现一个构建 Mock 请求的函数,这样就实现了压测功能,等项目上线后采集流量,真实流量一旦采集,框架就可以帮你进行日志读取、多倍打压等功能,流量的放大及缩小取决你希望能有多少 worker 进行打压,worker 的数量可配。
10.VCR 录制的序列化的是 httpRequest 对象吗?
VCR 序列化的是 RPC 请求,主要针对 Thrift。
11.测试结果能反应整个链路的瓶颈在哪吗?另外测试环境是不是跟线上隔离的?
压测不能反应整个链路的瓶颈,美团内部采用 Falcon 和 Octo 收集依赖服务的指标,如果依赖服务撑不住,会直接报警,目前测试环境都只会压测线下的服务,很少出现压线上的情况,不过机器之间的网络是打通的。