服务器
cpp
from fastapi import FastAPI, HTTPException
import pymysql
from dbutils.pooled_db import PooledDB
from pydantic import BaseModel, Field
from typing import List, Optional, Dict
# 1. 初始化FastAPI应用实例
app = FastAPI(
title="Shop数据库用户管理接口",
description="支持查询所有用户、插入单条用户数据",
version="2.0.0"
)
# 2. 配置MySQL连接参数(与原有配置一致)
MYSQL_CONFIG = {
'host': 'localhost',
'port': 3306,
'user': 'root',
'password': '666',
'database': 'shop',
'charset': 'utf8mb4'
}
# 3. 初始化数据库连接池(全局唯一,仅启动时创建一次)
POOL = PooledDB(
creator=pymysql, # 指定使用的数据库驱动
**MYSQL_CONFIG, # 传入MySQL连接参数
maxconnections=10, # 连接池最大连接数(根据业务调整)
mincached=2, # 初始化时,连接池中至少创建的空闲连接数
maxcached=5, # 连接池中最多缓存的空闲连接数
maxusage=None, # 每个连接最多被使用的次数(None表示无限制)
blocking=True # 当连接池无可用连接时,是否阻塞等待(True=等待,False=直接报错)
)
# 4. 定义数据模型(请求/响应)
class UserCreateRequest(BaseModel):
"""
插入用户的请求数据模型(参数校验)
"""
name: str = Field(..., min_length=1, max_length=50, description="用户名,不能为空,长度1-50")
class UserInfo(BaseModel):
"""
用户信息响应模型
"""
id: Optional[int] = None
name: Optional[str] = None
class Config:
orm_mode = True
# 5. 核心工具函数(查询+插入)
def get_all_users_from_mysql() -> List[Dict]:
"""
从连接池获取连接,查询users表所有数据,返回格式化的字典列表
"""
conn = None
cursor = None
try:
# 从连接池获取连接
conn = POOL.connection()
# 创建游标(指定DictCursor,返回字典格式)
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
# 执行查询
sql = "SELECT * FROM users;"
cursor.execute(sql)
# 获取所有结果
all_results = cursor.fetchall()
return all_results
except pymysql.Error as e:
raise Exception(f"数据库查询失败:{str(e)}")
finally:
# 关闭游标和归还连接
if cursor:
cursor.close()
if conn:
conn.close()
def insert_one_user_to_mysql(user_name: str) -> Dict:
"""
从连接池获取连接,插入一条用户数据到users表,返回插入后的用户信息(含自增id)
开启事务保障,避免数据插入异常
"""
conn = None
cursor = None
try:
# 从连接池获取连接
conn = POOL.connection()
# 创建游标(普通游标,用于获取自增id)
cursor = conn.cursor()
# 定义插入SQL(使用参数化查询,避免SQL注入)
insert_sql = "INSERT INTO users (name) VALUES (%s);"
# 执行插入(传入参数元组,与SQL中的%s对应)
affected_rows = cursor.execute(insert_sql, (user_name,))
# 提交事务(关键:插入/更新/删除操作必须提交事务才会生效)
conn.commit()
# 获取插入数据的自增id
insert_id = cursor.lastrowid
# 查询插入后的完整用户信息,用于返回
select_sql = "SELECT * FROM users WHERE id = %s;"
cursor.execute(select_sql, (insert_id,))
inserted_user = cursor.fetchone()
# 转换为字典格式(与查询接口返回格式一致)
if inserted_user:
fields = [desc[0] for desc in cursor.description]
user_dict = dict(zip(fields, inserted_user))
return user_dict
else:
raise Exception("插入成功,但未查询到该用户数据")
except pymysql.Error as e:
# 回滚事务(插入失败时,撤销所有未提交的操作)
if conn:
conn.rollback()
raise Exception(f"数据库插入失败:{str(e)}")
finally:
# 关闭游标和归还连接
if cursor:
cursor.close()
if conn:
conn.close()
# 6. 定义FastAPI接口(查询+插入)
@app.get(
"/api/shop/users",
summary="查询shop库users表所有数据",
response_description="返回users表的所有用户信息(含字段名)",
response_model=List[UserInfo]
)
async def get_all_users():
try:
user_list = get_all_users_from_mysql()
if not user_list:
return []
return user_list
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.post(
"/api/shop/users",
summary="插入一条用户数据到shop库users表",
response_description="返回插入成功后的用户完整信息(含自增id)",
response_model=UserInfo,
status_code=201 # 201 Created:表示资源创建成功,符合RESTful规范
)
async def create_one_user(user_request: UserCreateRequest):
try:
# 调用插入工具函数,传入校验后的用户名
inserted_user = insert_one_user_to_mysql(user_name=user_request.name)
return inserted_user
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# 7. 运行服务器
if __name__ == "__main__":
import uvicorn
uvicorn.run(
app="__main__:app",
host="0.0.0.0",
port=8000,
reload=True
)
压测程序
cpp
import requests
import threading
import time
from datetime import datetime
from typing import List, Dict
# ---------------------- 压测配置项(可根据需求修改) ----------------------
TARGET_URL = "http://localhost:8000/api/shop/users" # 你的FastAPI接口地址
CONCURRENT_THREADS = 5 # 并发线程数(模拟20个同时请求的用户)
TOTAL_REQUESTS_PER_THREAD = 50 # 每个线程发送的请求数
TIMEOUT = 10 # 单个请求超时时间(秒)
# ---------------------- 全局统计变量(线程安全) ----------------------
total_success = 0
total_failure = 0
response_times: List[float] = []
lock = threading.Lock() # 线程锁,保证统计变量修改的安全性
def send_requests(thread_id: int) -> None:
"""
单个线程的请求发送逻辑:循环发送指定数量的请求,并记录结果
"""
global total_success, total_failure, response_times
session = requests.Session() # 每个线程创建一个Session,复用连接,提升效率
for req_id in range(1, TOTAL_REQUESTS_PER_THREAD + 1):
try:
# 记录请求开始时间
start_time = time.perf_counter()
# 发送GET请求
response = session.get(
url=TARGET_URL,
timeout=TIMEOUT
)
# 记录请求结束时间
end_time = time.perf_counter()
response_time = (end_time - start_time) * 1000 # 转换为毫秒
# 验证请求是否成功(响应状态码200)
if response.status_code == 200:
with lock: # 加锁修改全局变量,避免线程冲突
total_success += 1
response_times.append(response_time)
print(f"[线程{thread_id}] 请求{req_id} 成功 | 响应时间:{response_time:.2f}ms | 响应状态码:{response.status_code}")
else:
with lock:
total_failure += 1
print(f"[线程{thread_id}] 请求{req_id} 失败 | 响应状态码:{response.status_code}")
print(total_success)
except Exception as e:
with lock:
total_failure += 1
print(f"[线程{thread_id}] 请求{req_id} 异常 | 错误信息:{str(e)}")
def run_benchmark() -> None:
"""
启动压测:创建并启动所有线程,等待所有线程执行完成,输出统计结果
"""
start_time = datetime.now()
print("=" * 80)
print(f"压测开始时间:{start_time.strftime('%Y-%m-%d %H:%M:%S')}")
print(f"目标接口:{TARGET_URL}")
print(f"并发线程数:{CONCURRENT_THREADS}")
print(f"每个线程请求数:{TOTAL_REQUESTS_PER_THREAD}")
print(f"总请求数:{CONCURRENT_THREADS * TOTAL_REQUESTS_PER_THREAD}")
print("=" * 80 + "\n")
# 创建线程列表
threads: List[threading.Thread] = []
for thread_id in range(1, CONCURRENT_THREADS + 1):
thread = threading.Thread(
target=send_requests,
args=(thread_id,),
name=f"Benchmark-Thread-{thread_id}"
)
threads.append(thread)
thread.start() # 启动线程
# 等待所有线程执行完成
for thread in threads:
thread.join()
# 计算统计结果
end_time = datetime.now()
total_duration = (end_time - start_time).total_seconds()
avg_response_time = sum(response_times) / len(response_times) if response_times else 0
qps = total_success / total_duration if total_duration > 0 else 0 # 每秒查询率(QPS)
# 输出最终统计结果
print("\n" + "=" * 80)
print(f"压测结束时间:{end_time.strftime('%Y-%m-%d %H:%M:%S')}")
print(f"总耗时:{total_duration:.2f} 秒")
print(f"总请求数:{CONCURRENT_THREADS * TOTAL_REQUESTS_PER_THREAD}")
print(f"成功请求数:{total_success}")
print(f"失败请求数:{total_failure}")
print(f"成功率:{(total_success / (total_success + total_failure) * 100):.2f}%" if (total_success + total_failure) > 0 else "成功率:0%")
print(f"平均响应时间:{avg_response_time:.2f} ms")
print(f"QPS(每秒查询率):{qps:.2f}")
print("=" * 80)
if __name__ == "__main__":
run_benchmark()
概述
包含两份核心代码的详细说明:FastAPI用户管理接口(后端服务) 和 Python多线程接口压测工具(前端压测),涵盖代码功能、结构解析、使用指南、注意事项等内容,便于开发、测试、运维人员理解和使用。
第一部分:FastAPI用户管理接口(后端服务)
1. 代码核心功能
该代码实现了一个基于FastAPI框架的轻量级用户管理后端服务,依托dbutils连接池操作MySQL数据库shop的users表,提供两大核心接口:
GET /api/shop/users:查询users表所有用户数据,返回结构化用户列表POST /api/shop/users:插入一条用户数据到users表,返回插入后的完整用户信息(含自增ID)- 附加能力:参数校验、SQL注入防护、事务保障、异常处理、自动生成交互式接口文档
2. 依赖环境说明
| 依赖库 | 版本要求 | 核心作用 |
|---|---|---|
fastapi |
>=0.95.0 | 构建高性能RESTful API,提供接口路由、响应模型、自动文档等能力 |
uvicorn |
>=0.22.0 | FastAPI官方推荐ASGI服务器,用于运行后端服务 |
pymysql |
>=1.0.0 | MySQL数据库Python驱动,实现数据库连接与SQL执行 |
dbutils |
>=3.1.0 | 提供数据库连接池功能,复用连接、提升并发访问效率 |
pydantic |
>=2.0.0 | 数据模型定义与参数校验,保障请求数据合法性 |
3. 代码结构逐段解析
3.1 初始化与配置模块
python
from fastapi import FastAPI, HTTPException
import pymysql
from dbutils.pooled_db import PooledDB
from pydantic import BaseModel, Field
from typing import List, Optional, Dict
# 1. 初始化FastAPI应用实例
app = FastAPI(
title="Shop数据库用户管理接口",
description="支持查询所有用户、插入单条用户数据",
version="2.0.0"
)
# 2. 配置MySQL连接参数(与原有配置一致)
MYSQL_CONFIG = {
'host': 'localhost',
'port': 3306,
'user': 'root',
'password': '666',
'database': 'shop',
'charset': 'utf8mb4'
}
# 3. 初始化数据库连接池(全局唯一,仅启动时创建一次)
POOL = PooledDB(
creator=pymysql, # 指定使用的数据库驱动
**MYSQL_CONFIG, # 传入MySQL连接参数
maxconnections=10, # 连接池最大连接数(根据业务调整)
mincached=2, # 初始化时,连接池中至少创建的空闲连接数
maxcached=5, # 连接池中最多缓存的空闲连接数
maxusage=None, # 每个连接最多被使用的次数(None表示无限制)
blocking=True # 当连接池无可用连接时,是否阻塞等待(True=等待,False=直接报错)
)
- 核心说明:
FastAPI实例初始化时配置了文档元信息,访问http://localhost:8000/docs可查看交互式接口文档MYSQL_CONFIG需与实际MySQL环境匹配,charset=utf8mb4支持全量中文与特殊字符- 连接池
POOL为全局单例,避免频繁创建/关闭数据库连接,提升并发性能;blocking=True保证高并发下连接获取的稳定性(无可用连接时等待,而非直接报错)
3.2 数据模型定义模块
python
class UserCreateRequest(BaseModel):
"""
插入用户的请求数据模型(参数校验)
"""
name: str = Field(..., min_length=1, max_length=50, description="用户名,不能为空,长度1-50")
class UserInfo(BaseModel):
"""
用户信息响应模型
"""
id: Optional[int] = None
name: Optional[str] = None
class Config:
orm_mode = True
- 核心说明:
UserCreateRequest:用于POST接口的请求体校验,Field约束name为必填、非空、长度1-50,不符合约束时自动返回422错误UserInfo:用于接口响应数据格式化,统一返回id和name字段;orm_mode=True支持将数据库查询结果(字典/元组)转换为模型实例- 依赖
pydantic实现自动校验与序列化,无需手动编写校验逻辑
3.3 数据库操作工具函数
3.3.1 全量用户查询函数 get_all_users_from_mysql
python
def get_all_users_from_mysql() -> List[Dict]:
"""
从连接池获取连接,查询users表所有数据,返回格式化的字典列表
"""
conn = None
cursor = None
try:
# 从连接池获取连接
conn = POOL.connection()
# 创建游标(指定DictCursor,返回字典格式)
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
# 执行查询
sql = "SELECT * FROM users;"
cursor.execute(sql)
# 获取所有结果
all_results = cursor.fetchall()
return all_results
except pymysql.Error as e:
raise Exception(f"数据库查询失败:{str(e)}")
finally:
# 关闭游标和归还连接
if cursor:
cursor.close()
if conn:
conn.close()
- 核心说明:
- 采用
try-except-finally结构,保证游标关闭和连接归还(即使查询异常,也不会导致连接泄露) DictCursor游标返回字典格式结果(含字段名),便于后续接口返回结构化数据- 连接从连接池获取(
POOL.connection()),使用完毕后通过conn.close()归还到连接池(非真正关闭连接)
- 采用
3.3.2 单条用户插入函数 insert_one_user_to_mysql
python
def insert_one_user_to_mysql(user_name: str) -> Dict:
"""
从连接池获取连接,插入一条用户数据到users表,返回插入后的用户信息(含自增id)
开启事务保障,避免数据插入异常
"""
conn = None
cursor = None
try:
# 从连接池获取连接
conn = POOL.connection()
# 创建游标(普通游标,用于获取自增id)
cursor = conn.cursor()
# 定义插入SQL(使用参数化查询,避免SQL注入)
insert_sql = "INSERT INTO users (name) VALUES (%s);"
# 执行插入(传入参数元组,与SQL中的%s对应)
affected_rows = cursor.execute(insert_sql, (user_name,))
# 提交事务(关键:插入/更新/删除操作必须提交事务才会生效)
conn.commit()
# 获取插入数据的自增id
insert_id = cursor.lastrowid
# 查询插入后的完整用户信息,用于返回
select_sql = "SELECT * FROM users WHERE id = %s;"
cursor.execute(select_sql, (insert_id,))
inserted_user = cursor.fetchone()
# 转换为字典格式(与查询接口返回格式一致)
if inserted_user:
fields = [desc[0] for desc in cursor.description]
user_dict = dict(zip(fields, inserted_user))
return user_dict
else:
raise Exception("插入成功,但未查询到该用户数据")
except pymysql.Error as e:
# 回滚事务(插入失败时,撤销所有未提交的操作)
if conn:
conn.rollback()
raise Exception(f"数据库插入失败:{str(e)}")
finally:
# 关闭游标和归还连接
if cursor:
cursor.close()
if conn:
conn.close()
- 核心说明:
- 事务保障:
conn.commit()提交事务(插入操作生效),异常时conn.rollback()回滚事务(避免脏数据) - 防SQL注入:采用参数化查询(
%s占位符),参数通过元组传入,避免字符串拼接带来的注入风险 cursor.lastrowid获取插入数据的自增ID(要求users表id字段为AUTO_INCREMENT)- 插入后查询完整用户信息,保证返回数据与查询接口格式一致,提升接口易用性
- 事务保障:
3.4 接口定义模块
3.4.1 全量用户查询接口 GET /api/shop/users
python
@app.get(
"/api/shop/users",
summary="查询shop库users表所有数据",
response_description="返回users表的所有用户信息(含字段名)",
response_model=List[UserInfo]
)
async def get_all_users():
try:
user_list = get_all_users_from_mysql()
if not user_list:
return []
return user_list
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
- 核心说明:
@app.get注解定义GET请求路由,response_model=List[UserInfo]约束返回数据格式为UserInfo模型列表- 异常处理:捕获数据库查询异常,返回500状态码和详细错误信息,便于问题排查
- 异步函数(
async):适配FastAPI的异步架构,提升高并发下的响应性能(实际数据库操作为同步,可后续优化为异步驱动)
3.4.2 单条用户插入接口 POST /api/shop/users
python
@app.post(
"/api/shop/users",
summary="插入一条用户数据到shop库users表",
response_description="返回插入成功后的用户完整信息(含自增id)",
response_model=UserInfo,
status_code=201 # 201 Created:表示资源创建成功,符合RESTful规范
)
async def create_one_user(user_request: UserCreateRequest):
try:
# 调用插入工具函数,传入校验后的用户名
inserted_user = insert_one_user_to_mysql(user_name=user_request.name)
return inserted_user
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
- 核心说明:
@app.post注解定义POST请求路由,status_code=201符合RESTful规范(资源创建成功)user_request: UserCreateRequest自动解析请求体并完成参数校验,校验失败返回422错误- 传入校验后的
user_request.name到插入函数,保证数据库接收的是合法数据
3.5 服务运行模块
python
if __name__ == "__main__":
import uvicorn
uvicorn.run(
app="__main__:app",
host="0.0.0.0",
port=8000,
reload=True
)
- 核心说明:
- 采用
uvicorn运行FastAPI应用,host="0.0.0.0"允许局域网内其他设备访问,port=8000为服务端口 reload=True开启热重载(开发环境专用),代码修改后自动重启服务,无需手动重启- 生产环境建议关闭
reload,并配置workers(多进程)提升并发能力
- 采用
4. 使用指南
4.1 前置准备
- 安装依赖:
pip install fastapi uvicorn pymysql dbutils pydantic - 配置MySQL环境:
-
启动MySQL服务,创建数据库
shop:CREATE DATABASE IF NOT EXISTS shop DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -
创建
users表:sqlCREATE TABLE IF NOT EXISTS users ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT '自增用户ID', name VARCHAR(50) NOT NULL COMMENT '用户名', create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户信息表'; -
修改
MYSQL_CONFIG中的user、password为实际MySQL账号密码
-
4.2 启动服务
- 将代码保存为
user_service.py - 运行命令:
python user_service.py(或uvicorn user_service:app --host 0.0.0.0 --port 8000 --reload) - 服务启动成功标志:控制台输出
Application startup complete.
4.3 接口调用与调试
方式1:交互式接口文档(推荐)
- 访问
http://localhost:8000/docs,进入FastAPI自动生成的Swagger文档 - 对于
GET /api/shop/users:点击「Try it out」→「Execute」,即可查看响应结果 - 对于
POST /api/shop/users:点击「Try it out」,填写请求体(如{"name": "张三"})→「Execute」,查看插入结果
方式2:curl命令调用
bash
# 查询所有用户
curl -X GET "http://localhost:8000/api/shop/users" -H "Content-Type: application/json"
# 插入一条用户数据
curl -X POST "http://localhost:8000/api/shop/users" -H "Content-Type: application/json" -d "{\"name\": \"李四\"}"
方式3:第三方工具(Postman/Insomnia)
- 配置请求方法、地址、请求头(
Content-Type: application/json)和请求体,发送请求即可
5. 常见问题排查
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 服务启动失败,提示"Access denied for user 'root'@'localhost'" | MySQL账号密码错误 | 修正MYSQL_CONFIG中的user、password |
| 接口返回500,提示"数据库查询失败:1049 (42000): Unknown database 'shop'" | 未创建shop数据库 |
执行创建数据库的SQL语句 |
| POST接口返回422,提示"field required" | 请求体缺少name字段或字段名拼写错误 |
确保请求体包含name字段,字段名拼写正确 |
POST接口插入成功,但返回无id |
users表id字段未设置为自增主键 |
修改users表,将id字段设置为AUTO_INCREMENT |
| 高并发下接口返回500,提示"Too many connections" | MySQL最大连接数不足或连接池配置过小 | 1. 增大MySQL最大连接数;2. 调大POOL的maxconnections参数 |
第二部分:Python多线程接口压测工具
1. 代码核心功能
该代码是一款轻量级的多线程接口压测工具,专门针对GET /api/shop/users接口进行压测,实现以下功能:
- 模拟多线程并发请求(可配置并发线程数)
- 每个线程发送指定数量的请求(可配置单线程请求数)
- 记录请求成功率、失败率、响应时间(毫秒级)
- 计算并输出压测汇总数据(总耗时、QPS、平均响应时间)
- 线程安全的统计计数,避免并发修改数据冲突
- 实时打印单个请求的执行结果,便于实时监控
2. 依赖环境说明
| 依赖库 | 版本要求 | 核心作用 |
|---|---|---|
requests |
>=2.28.0 | 发送HTTP请求,获取接口响应结果 |
threading |
内置模块 | 实现多线程并发,模拟高并发场景 |
time |
内置模块 | 记录请求开始/结束时间,计算响应时间 |
datetime |
内置模块 | 记录压测开始/结束时间,格式化时间输出 |
typing |
内置模块 | 类型注解,提升代码可读性和可维护性 |
3. 代码结构逐段解析
3.1 压测配置项模块
python
import requests
import threading
import time
from datetime import datetime
from typing import List, Dict
# ---------------------- 压测配置项(可根据需求修改) ----------------------
TARGET_URL = "http://localhost:8000/api/shop/users" # 你的FastAPI接口地址
CONCURRENT_THREADS = 5 # 并发线程数(模拟20个同时请求的用户)
TOTAL_REQUESTS_PER_THREAD = 50 # 每个线程发送的请求数
TIMEOUT = 10 # 单个请求超时时间(秒)
- 核心说明:
- 配置项集中管理,便于用户根据实际场景修改,无需改动核心逻辑
TARGET_URL:目标压测接口地址,需与FastAPI服务地址保持一致CONCURRENT_THREADS:并发线程数,模拟多个用户同时发起请求(建议根据服务器性能调整,避免过度压测)TOTAL_REQUESTS_PER_THREAD:单个线程发送的请求总数,总请求数=并发线程数×单线程请求数TIMEOUT:单个请求的超时时间,超过该时间未响应则判定为请求异常
3.2 全局统计变量与线程锁模块
python
# ---------------------- 全局统计变量(线程安全) ----------------------
total_success = 0
total_failure = 0
response_times: List[float] = []
lock = threading.Lock() # 线程锁,保证统计变量修改的安全性
- 核心说明:
- 全局统计变量:
total_success:成功请求总数(响应状态码200)total_failure:失败请求总数(非200状态码+请求异常)response_times:所有成功请求的响应时间列表(用于计算平均响应时间)
threading.Lock():创建线程锁,解决多线程并发修改全局变量时的数据竞争问题(确保同一时间只有一个线程修改全局变量,避免数据统计错误)
- 全局统计变量:
3.3 单个线程请求发送函数 send_requests
python
def send_requests(thread_id: int) -> None:
"""
单个线程的请求发送逻辑:循环发送指定数量的请求,并记录结果
"""
global total_success, total_failure, response_times
session = requests.Session() # 每个线程创建一个Session,复用连接,提升效率
for req_id in range(1, TOTAL_REQUESTS_PER_THREAD + 1):
try:
# 记录请求开始时间
start_time = time.perf_counter()
# 发送GET请求
response = session.get(
url=TARGET_URL,
timeout=TIMEOUT
)
# 记录请求结束时间
end_time = time.perf_counter()
response_time = (end_time - start_time) * 1000 # 转换为毫秒
# 验证请求是否成功(响应状态码200)
if response.status_code == 200:
with lock: # 加锁修改全局变量,避免线程冲突
total_success += 1
response_times.append(response_time)
print(f"[线程{thread_id}] 请求{req_id} 成功 | 响应时间:{response_time:.2f}ms | 响应状态码:{response.status_code}")
else:
with lock:
total_failure += 1
print(f"[线程{thread_id}] 请求{req_id} 失败 | 响应状态码:{response.status_code}")
print(total_success)
except Exception as e:
with lock:
total_failure += 1
print(f"[线程{thread_id}] 请求{req_id} 异常 | 错误信息:{str(e)}")
- 核心说明:
requests.Session():每个线程创建一个Session对象,复用TCP连接(避免每次请求都创建新连接),大幅提升压测效率time.perf_counter():高精度时间统计函数,用于计算请求响应时间(转换为毫秒后,精度更高,便于分析接口性能)- 异常处理:捕获请求超时、网络中断、接口报错等异常,统一计入
total_failure with lock:上下文管理器自动加锁/释放锁,修改全局变量时保证线程安全,避免数据统计偏差- 实时打印:输出单个请求的执行结果(成功/失败/异常),便于实时监控压测过程中的接口状态
3.4 压测主函数 run_benchmark
python
def run_benchmark() -> None:
"""
启动压测:创建并启动所有线程,等待所有线程执行完成,输出统计结果
"""
start_time = datetime.now()
print("=" * 80)
print(f"压测开始时间:{start_time.strftime('%Y-%m-%d %H:%M:%S')}")
print(f"目标接口:{TARGET_URL}")
print(f"并发线程数:{CONCURRENT_THREADS}")
print(f"每个线程请求数:{TOTAL_REQUESTS_PER_THREAD}")
print(f"总请求数:{CONCURRENT_THREADS * TOTAL_REQUESTS_PER_THREAD}")
print("=" * 80 + "\n")
# 创建线程列表
threads: List[threading.Thread] = []
for thread_id in range(1, CONCURRENT_THREADS + 1):
thread = threading.Thread(
target=send_requests,
args=(thread_id,),
name=f"Benchmark-Thread-{thread_id}"
)
threads.append(thread)
thread.start() # 启动线程
# 等待所有线程执行完成
for thread in threads:
thread.join()
# 计算统计结果
end_time = datetime.now()
total_duration = (end_time - start_time).total_seconds()
avg_response_time = sum(response_times) / len(response_times) if response_times else 0
qps = total_success / total_duration if total_duration > 0 else 0 # 每秒查询率(仅统计成功请求)
# 输出最终统计结果
print("\n" + "=" * 80)
print(f"压测结束时间:{end_time.strftime('%Y-%m-%d %H:%M:%S')}")
print(f"总耗时:{total_duration:.2f} 秒")
print(f"总请求数:{CONCURRENT_THREADS * TOTAL_REQUESTS_PER_THREAD}")
print(f"成功请求数:{total_success}")
print(f"失败请求数:{total_failure}")
print(f"成功率:{(total_success / (total_success + total_failure) * 100):.2f}%" if (total_success + total_failure) > 0 else "成功率:0%")
print(f"平均响应时间:{avg_response_time:.2f} ms")
print(f"QPS(每秒查询率):{qps:.2f}")
print("=" * 80)
- 核心说明:
- 压测前信息打印:输出压测配置参数,便于确认压测场景是否符合预期
- 线程创建与启动:
- 循环创建指定数量的线程,每个线程绑定
send_requests函数,并传入线程ID thread.start()启动线程,所有线程同时开始执行请求发送逻辑
- 循环创建指定数量的线程,每个线程绑定
thread.join():等待所有线程执行完成后,再进行统计结果计算,确保统计数据完整- 核心指标计算:
total_duration:压测总耗时(从第一个线程启动到最后一个线程结束)avg_response_time:成功请求的平均响应时间(仅统计成功请求,更能反映接口正常性能)qps:每秒查询率(成功请求数/总耗时),衡量接口的并发处理能力
- 压测后汇总输出:以清晰格式输出核心统计指标,便于分析接口性能瓶颈
3.5 程序入口
python
if __name__ == "__main__":
run_benchmark()
- 核心说明:程序运行时,自动调用
run_benchmark函数,启动完整压测流程
4. 使用指南
4.1 前置准备
- 安装依赖:
pip install requests - 确保FastAPI用户管理接口服务已正常启动(
http://localhost:8000/api/shop/users可正常访问并返回数据) - 根据实际测试需求,修改压测配置项(如调整并发线程数、单线程请求数)
4.2 启动压测
- 将代码保存为
api_benchmark.py - 运行命令:
python api_benchmark.py - 压测启动标志:控制台输出压测配置信息,随后开始实时打印单个请求的执行结果
4.3 压测结果解读
| 指标名称 | 指标含义 | 解读要点 |
|---|---|---|
| 总耗时 | 整个压测流程的持续时间 | 反映完成所有请求所需的时间,结合总请求数可初步判断接口处理效率 |
| 成功请求数/失败请求数 | 压测过程中成功/失败的请求总量 | 失败请求数>0时,需排查接口是否存在性能瓶颈或稳定性问题 |
| 成功率 | 成功请求数占总请求数的比例 | 理想状态下成功率应为100%,若低于99%,需重点分析失败原因 |
| 平均响应时间 | 所有成功请求的响应时间平均值 | 反映接口的平均处理速度,数值越低,接口性能越好 |
| QPS(每秒查询率) | 每秒能处理的成功请求数 | 衡量接口并发处理能力的核心指标,数值越高,接口的高并发支撑能力越强 |
5. 优化建议与注意事项
5.1 压测配置优化建议
- 逐步提升并发量:从低并发(如5线程)开始,逐步增加到高并发(如50、100线程),避免一次性高并发压垮服务器
- 控制单线程请求数:单线程请求数建议设置为50-200,过多可能导致单个线程运行时间过长,影响压测效率
- 合理设置超时时间:根据接口正常响应时间调整
TIMEOUT(如接口正常响应时间<1秒,可设置为5秒),避免超时时间过长导致压测结果失真
5.2 线程安全注意事项
- 全局变量修改必须加锁:
total_success、total_failure、response_times为全局变量,多线程修改时必须通过with lock保证线程安全,否则会出现统计数据错误 - 每个线程独立创建
requests.Session:避免多个线程共享一个Session对象,导致请求冲突或连接泄露
5.3 压测环境注意事项
- 压测环境与生产环境尽量一致:确保硬件配置、MySQL配置、服务部署方式与生产环境一致,压测结果才具有参考价值
- 避免在生产环境直接压测:压测会占用大量服务器资源,可能影响生产业务正常运行,建议在测试环境进行压测
- 压测前备份数据库:高并发压测可能导致数据库负载过高,甚至出现数据异常,压测前建议备份
shop数据库
5.4 功能扩展建议
- 支持POST接口压测:添加请求体配置项,修改
session.get为session.post,支持对插入接口进行压测 - 增加响应数据校验:校验接口返回数据的格式和内容,避免接口返回200但数据异常的情况
- 生成压测报告:将压测结果保存为CSV/HTML文件,便于后续分析和归档
- 支持自定义请求头:添加请求头配置项,支持携带认证信息、Content-Type等自定义请求头
第三部分:两份代码的联动说明与整体总结
1. 联动流程
- 启动FastAPI用户管理接口服务,确保
GET /api/shop/users接口可正常访问 - 配置压测工具的
TARGET_URL与FastAPI接口地址一致 - 启动压测工具,模拟多线程并发请求,对接口进行性能测试
- 查看压测结果,分析接口成功率、响应时间、QPS等指标
- 根据压测结果优化FastAPI服务(如调整连接池配置、优化SQL、开启多进程运行)
2. 整体总结
- 两份代码实现了「后端服务搭建」与「接口性能压测」的完整闭环,便于快速验证用户管理接口的功能和性能
- FastAPI接口服务具备高可用性、安全性、可扩展性,支持后续功能扩展(如用户更新、删除、分页查询)
- 多线程压测工具轻量易用、配置灵活,可快速适配不同接口的压测需求,为接口性能优化提供数据支撑
- 核心亮点:
- 后端服务:连接池复用、参数校验、事务保障、防SQL注入,确保服务稳定安全
- 压测工具:线程安全、实时监控、核心指标统计,确保压测结果准确可靠
- 后续优化方向:
- 后端:采用异步数据库驱动(
asyncmy),提升接口并发处理能力;添加日志模块,便于问题排查 - 压测工具:支持分布式压测、自定义压测场景,提升压测的覆盖度和专业性
- 后端:采用异步数据库驱动(