1. 压力测试基础:并发交易系统的核心概念

大家好,我是你们的讲师。今天咱们聊聊并发交易系统的压力测试。说实话,我入行那会儿,对压力测试的理解特别肤浅——以为就是多开几个线程发请求。直到有一次线上事故,让我彻底改变了看法。

那次事故是这样的:一个券商系统上线前,开发说测过了没问题。结果开盘半小时,系统直接挂了。后来复盘发现,他们只测了100个并发,但实际峰值到了2000。嗯,这就是典型的压力测试没做到位。

什么是并发交易系统?

并发交易系统,说白了就是能同时处理大量交易请求的系统。你想想看,双十一零点,几亿人同时下单;股票开盘瞬间,成千上万笔买卖单同时涌入。这些场景背后,都是并发交易系统在支撑。

我个人习惯把并发交易系统拆成三个核心部分:

  • 接入层:负责接收请求,做初步校验。比如限流、鉴权这些。
  • 业务层:处理核心交易逻辑。像订单匹配、资金结算。
  • 数据层:保证数据一致性。数据库、缓存、消息队列都在这一层。

我在项目中遇到过最头疼的问题,就是这三层之间的协调。接入层扛住了,业务层却崩了;业务层优化好了,数据库又成了瓶颈。所以做压力测试,不能只看单点,要看整体。

核心观点:并发交易系统的本质是「在有限资源下,最大化吞吐量并保证数据一致性」。

压力测试的目标与意义

压力测试的目标,其实就三个字:找极限。找到系统在什么情况下会崩,崩之前是什么表现。

具体来说,压力测试要回答这几个问题:

  1. 系统能扛多少并发? 比如1000个用户同时交易,系统还能正常响应吗?
  2. 响应时间会变多慢? 正常情况下50ms,并发上来后会不会变成5秒?
  3. 错误率会飙升吗? 超时、连接失败、数据不一致,这些错误在什么阈值下出现?
  4. 资源消耗怎么样? CPU、内存、网络IO,哪个先撑不住?

我曾经帮一个P2P平台做压测,发现他们的数据库连接池只配了20个。并发一上来,请求全在等连接,响应时间从200ms直接飙到30秒。这就是典型的「没测不知道,一测吓一跳」。

我的建议:压力测试不是「测一次就完事」。我习惯在每个迭代都跑一遍压测,哪怕只改了业务代码。因为很多时候,小改动会引发大问题。

核心指标:TPS、响应时间、错误率

这三个指标,是压力测试的「三驾马车」。少了任何一个,你都无法全面评估系统性能。

1. TPS(每秒事务数)

TPS就是系统每秒能处理多少个完整的事务。注意是「完整事务」,不是单个请求。比如一笔交易,可能包含查询、校验、扣款、写日志等多个步骤,只有全部完成才算一个事务。

我见过很多新手把QPS和TPS搞混。QPS只管请求量,TPS管的是业务完成量。举个例子:你发了100个请求,但只有80个成功返回了完整结果,那TPS就是80,不是100。

避坑指南:我曾经遇到一个团队,压测报告上写着TPS 5000,结果上线后实际只有800。为什么?因为他们把「请求到达」算成了「事务完成」。记住:TPS只看成功返回的完整事务。

2. 响应时间

响应时间就是用户发请求到收到响应的时间差。通常关注三个值:

指标 含义 我的经验值
平均响应时间 所有请求的平均值 容易受极端值影响,参考价值有限
P95响应时间 95%的请求都在这个时间内完成 我一般重点看这个,代表大多数用户的体验
P99响应时间 99%的请求都在这个时间内完成 用来排查长尾问题,比如慢查询

我个人习惯把P95作为核心指标。为什么?因为平均响应时间容易被几个超长请求拉高,而P99又太苛刻。P95是个不错的平衡点。

3. 错误率

错误率就是失败请求占总请求的比例。常见的错误类型有:

  • 超时错误:请求发出去,等了好久没回音
  • 连接错误:根本连不上服务器
  • 业务错误:请求成功了,但返回了错误码(比如余额不足)
  • 数据一致性错误:扣款成功了,但订单没生成(这种最要命)

我记得有一次压测,错误率一直维持在5%左右。开发说「5%可以接受」。我追问了一句:「这5%是哪些请求?」一查发现,全是金额超过10万的大额交易。嗯,这要是上线了,5%的大额交易失败,客户不得炸锅?

重要原则:错误率不能只看整体,要按业务类型、请求来源、时间窗口分别统计。有时候1%的错误率,可能集中在最核心的业务上。

知识体系总览

下面这张图,是我自己总结的压力测试知识体系。你可以把它当成一个「地图」,后续章节都会围绕这些展开。

压力测试基础 TPS(每秒事务数) 响应时间(P95/P99) 错误率(分类统计) 接入层(限流/鉴权) 业务层(交易逻辑) 数据层(DB/缓存) 图:压力测试知识体系 — 系统分层 + 三大核心指标

一个简单的压测脚本示例

光说不练假把式。我写个简单的Python脚本,演示怎么测TPS和响应时间。这个脚本虽然简单,但核心逻辑和工业级压测工具是一样的。

import time
import threading
import requests
from statistics import mean, median

# 压测配置
TARGET_URL = "http://localhost:8080/api/trade"
CONCURRENT_USERS = 50  # 模拟50个并发用户
REQUESTS_PER_USER = 20  # 每个用户发20个请求

# 结果收集
results = []
errors = 0
lock = threading.Lock()

def send_requests(user_id):
    global errors
    for i in range(REQUESTS_PER_USER):
        start = time.time()
        try:
            resp = requests.post(TARGET_URL, json={
                "user_id": user_id,
                "order_id": f"{user_id}_{i}",
                "amount": 100
            }, timeout=5)
            elapsed = time.time() - start
            with lock:
                if resp.status_code == 200:
                    results.append(elapsed)
                else:
                    errors += 1
        except Exception as e:
            with lock:
                errors += 1

# 启动压测
threads = []
start_time = time.time()
for uid in range(CONCURRENT_USERS):
    t = threading.Thread(target=send_requests, args=(uid,))
    threads.append(t)
    t.start()

for t in threads:
    t.join()

total_time = time.time() - start_time
total_requests = CONCURRENT_USERS * REQUESTS_PER_USER
tps = total_requests / total_time

# 输出结果
print(f"总耗时: {total_time:.2f}s")
print(f"总请求数: {total_requests}")
print(f"TPS: {tps:.2f}")
print(f"平均响应时间: {mean(results)*1000:.2f}ms")
print(f"P50响应时间: {median(results)*1000:.2f}ms")
print(f"错误数: {errors}")
print(f"错误率: {errors/total_requests*100:.2f}%")

小提示:这个脚本只是演示原理。实际项目中,我建议用Locust或JMeter。它们内置了更完善的统计和报告功能。但理解这个脚本的逻辑,能帮你更好地理解压测工具的工作原理。

总结一下

这一章我们聊了三个核心点:

  • 并发交易系统:三层架构,每层都可能成为瓶颈
  • 压力测试的目标:找极限,而不是证明系统没问题
  • 三大指标:TPS看吞吐,响应时间看体验,错误率看质量

嗯,内容就到这。记住一句话:压力测试不是「测过了就行」,而是「测透了才放心」。下一章我们会深入聊压测工具的选择和实战技巧。


公众号:蓝海资料掘金营,微信deep3321