在行业中,"压力测试"(简称"压测")是一个常听到的术语。你可能在项目开发过程中亲自进行过压力测试,因此对你来说这不是个新概念。想象一下你进行压力测试的方式,是否与许多人相同:首先建立一个与生产环境功能匹配的测试环境,并导入或生成一系列测试数据。接着,在另一台服务器上启动多个线程,以并发方式调用待测试的接口(接口参数通常设置为相同的,例如,在测试获取商品信息的接口时,可能会使用相同的商品ID进行压测)。最终,通过分析访问日志或检查测试环境的监控系统,记录压测期间的QPS(每秒查询率),然后报告测试结果。这个过程的描述,虽然换了一种说法,但意义基本相同。
使用线上数据和环境:进行压力测试时,最佳做法是直接使用实际的线上数据和环境。这是因为自行搭建的测试环境可能与生产环境存在差异,这些差异可能会影响到压力测试的准确性。
采用线上流量而非模拟请求:为了确保测试结果的有效性,应该使用真实的线上流量进行压力测试,而不是依赖于模拟请求。这可以通过流量复制技术实现,将实际的线上流量复制到测试环境中。这样做的原因是,模拟流量可能无法准确反映线上流量的真实行为模式,尤其是在访问模型和缓存命中率等方面,可能与实际情况大相径庭。
分散流量源,避免单点压力:不应该只从一台服务器发起所有测试流量,因为这样很容易触达单台服务器的性能瓶颈,从而影响整体的压力测试结果。为了更准确地模拟用户的真实请求,应该将产生流量的机器分布在地理位置上靠近最终用户的地点,比如CDN节点。如果条件不允许,至少也应该在不同的数据中心或机房内分布流量生成点,以增强测试结果的真实性和可靠性。
错误之处主要有以下几点:
压力测试是在高并发大流量条件下对系统进行的测试,旨在观察系统在峰值负荷下的表现,以发现性能隐患。这是发现系统问题和确保系统稳定性与可用性的关键方法。以下是对上述描述的简化和标号概述:
定义与目的:压力测试是在极端负载条件下进行的测试,用于观察和评估系统在面临高并发和大流量时的性能。这种测试帮助识别潜在的性能瓶颈,是维持系统稳定性和可用性的重要工具。
全链路压测的必要性:不应仅对系统的某个核心模块进行压力测试,而需要将接入层、后端服务、数据库、缓存、消息队列、中间件以及依赖的第三方服务和资源全面纳入测试范围。这种全方位的测试,也称为全链路压测,是因为系统的任何部分在用户访问量激增时都可能成为性能瓶颈。
周期性测试与自动化平台的建设:随着互联网项目功能的快速迭代和系统复杂性的增加,定期进行压力测试变得尤为重要。全链路压测通常需要跨团队合作,包括DBA、运维团队和依赖服务方等,带来较高的人力和协调成本。为减少这些成本和潜在的线上风险,建立一套自动化的全链路压测平台是解决方案之一。
搭建全链路压测平台,主要有两个关键点。
一点是流量的隔离。由于压力测试是在正式环境进行,所以需要区分压力测试流量和正式流量,这样可以针对压力测试的流量做单独的处理。
另一点是风险的控制。也就是尽量避免压力测试对于正常访问用户的影响。因此,一般来说全链路压测平台需要包含以下几个模块:流量构造和产生模块;压测数据隔离模块;系统健康度检查和压测流量干预模块:
整体压测平台的架构图可以是下面这样的:
压测数据的产生
为了实施压力测试,系统入口流量的复制是一项基础工作。这些流量数据,在经过清洗(如过滤无效请求)后,可存储于NoSQL数据库或云存储服务中,形成所谓的流量数据工厂。当进行压测时,可从此工厂获取数据,并将其分配至多个压测节点。在这一过程中,有几个关键点需要特别注意:
流量复制的方法:可以直接复制负载均衡服务器的访问日志到流量数据工厂,尽管这种方法在压测时增加了解析日志的成本。另一种推荐方法是使用开源工具,如GoReplay,来拷贝特定端口的流量,并将其保存至流量数据工厂,同时支持压测时的流量回放。
压测流量的分发:为确保压测的真实性,下发压测流量的节点应尽量接近用户地理位置,而不是和服务部署节点位于同一机房。
流量染色:为了区分压测流量和实际用户流量,在HTTP请求头中增加压测标记,如is stress test
,这样在流量复制后,可以批量标记请求,确保压测的准确执行和监控。
数据如何隔离
在进行压力测试时,除了复制流量以模拟真实的用户请求外,还需要对系统进行改造,以实现压测流量与正式流量的隔离。这样的隔离可以最大限度减少压力测试对线上系统的影响。具体而言,需要从两个方面进行工作:
对下行流量的处理:对于读取数据的请求(通常称为下行流量),某些服务或组件不适合进行压测。例如,在记录用户行为数据时,压测可能会导致大量的假数据生成,如商品浏览量的人为膨胀,这会影响到业务报表,进而影响产品或业务决策。为避免这种情况,需要对压测产生的数据进行特殊处理,比如不将这些数据记录到大数据日志中。
另外,考虑到系统可能依赖于推荐服务来展示用户可能感兴趣的商品,而这些服务通常不会重复推荐已展示的商品。如果压测流量通过这些推荐服务,可能会导致大量商品被“消耗”,影响线上用户的推荐效果。因此,需要对这部分服务进行Mock处理,即让带有压测标记的请求经过Mock服务,而非真实的推荐服务。
Mock服务的部署:在搭建Mock服务时,应注意将这些服务部署在与真实服务相同的机房内。这样做的目的是为了尽可能地模拟真实的服务部署结构,从而提高压测结果的真实性。部署在相同机房内的Mock服务可以更准确地反映出服务间的调用延迟和处理能力,确保压测结果的可靠性。
对于写入数据的请求(通常称作上行流量),实现压测流量与正式流量隔离的一个重要策略是使用影子库。影子库是一个与线上数据存储完全隔离的存储系统,用于存储压测期间产生的所有写入数据。这种隔离确保了压测不会影响到真实的生产数据,同时允许我们在一个尽可能接近真实环境的设置中测试写入操作的性能。具体到不同的存储类型,影子库的实现方式也有所不同:
MySQL影子库:对于存储在MySQL中的数据,可以在同一个MySQL实例中创建一个不同的Schema,其中包含一套与线上相同的库表结构。同时,为了模拟真实的数据环境,还需要将线上数据导入到这个影子库中。这样做可以确保压测环境在数据结构和数据量上都尽可能接近真实环境,从而提高测试的准确性和可靠性。
Redis影子库:对于存储在Redis中的数据,可以通过为压测流量产生的数据增加一个统一的前缀,并存储在同一份Redis实例中。这种方法通过命名空间的隔离来区分正式数据和压测数据,既保持了数据隔离,又避免了搭建完全独立的存储系统的复杂性和成本。
Elasticsearch影子库:针对存储在Elasticsearch中的数据,可以选择将压测数据放在一个单独的索引中。这样的做法便于管理和隔离压测数据,同时也方便在压测结束后对这部分数据进行清理或分析。
压力测试如何实施
在完成线上流量复制和线上系统改造后,便可以开始实施压力测试。通常,压力测试前会设定一个目标,例如要求系统整体QPS达到每秒20万次。但是,在测试中不会突然将请求量提升至每秒20万次,而是会逐步增加,例如每次增加一万QPS,然后让系统稳定运行一段时间以观察其性能表现。如果检测到任何服务或组件成为性能瓶颈,就会减少压测流量至上一次的QPS水平以维护服务稳定,并对相关服务或组件进行扩容处理后再次增加流量进行测试。
为了降低压力测试过程中的人力成本,开发流量监控组件是一个有效策略。该组件可以预设性能阈值,例如设定容器CPU使用率的阈值为60%-70%,系统平均响应时间不超过1秒,慢请求比例不超过1%等。一旦系统性能触及这些阈值,流量监控组件便能及时发现并通知减少压测流量,同时向开发和运维团队发出报警。这样,团队便能快速识别并解决性能瓶颈问题,完成必要的扩容后继续进行压力测试。
在全链路压测平台的探索方面,众多大型互联网公司如阿里、京东、美团和微博等都已经开发出适合自己业务需求的平台。这些平台虽然各有特点,但基本遵循相同的原则,包括流量复制、流量隔离、压力测试、监控和熔断等关键步骤,体现了全链路压测的核心理念。因此,在自研适合自己项目的全链路压测平台时,遵循这些已被验证的方法论是一个明智的选择。