服务器
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
)
压测程序
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 初始化与配置模块
from fastapi import FastAPI, HTTPException
import pymysql
from dbutils. pooled_db import PooledDB
from pydantic import BaseModel, Field
from typing import List, Optional, Dict
app = FastAPI(
title= "Shop数据库用户管理接口" ,
description= "支持查询所有用户、插入单条用户数据" ,
version= "2.0.0"
)
MYSQL_CONFIG = {
'host' : 'localhost' ,
'port' : 3306 ,
'user' : 'root' ,
'password' : '666' ,
'database' : 'shop' ,
'charset' : 'utf8mb4'
}
POOL = PooledDB(
creator= pymysql,
** MYSQL_CONFIG,
maxconnections= 10 ,
mincached= 2 ,
maxcached= 5 ,
maxusage= None ,
blocking= True
)
核心说明:
FastAPI实例初始化时配置了文档元信息,访问http://localhost:8000/docs可查看交互式接口文档MYSQL_CONFIG需与实际MySQL环境匹配,charset=utf8mb4支持全量中文与特殊字符连接池POOL为全局单例,避免频繁创建/关闭数据库连接,提升并发性能;blocking=True保证高并发下连接获取的稳定性(无可用连接时等待,而非直接报错)
3.2 数据模型定义模块
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
def get_all_users_from_mysql ( ) - > List[ Dict] :
"""
从连接池获取连接,查询users表所有数据,返回格式化的字典列表
"""
conn = None
cursor = None
try :
conn = POOL. connection( )
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
def insert_one_user_to_mysql ( user_name: str ) - > Dict:
"""
从连接池获取连接,插入一条用户数据到users表,返回插入后的用户信息(含自增id)
开启事务保障,避免数据插入异常
"""
conn = None
cursor = None
try :
conn = POOL. connection( )
cursor = conn. cursor( )
insert_sql = "INSERT INTO users (name) VALUES (%s);"
affected_rows = cursor. execute( insert_sql, ( user_name, ) )
conn. commit( )
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
@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
@app. post (
"/api/shop/users" ,
summary= "插入一条用户数据到shop库users表" ,
response_description= "返回插入成功后的用户完整信息(含自增id)" ,
response_model= UserInfo,
status_code= 201
)
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 服务运行模块
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表:CREATE 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命令调用
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 压测配置项模块
import requests
import threading
import time
from datetime import datetime
from typing import List, Dict
TARGET_URL = "http://localhost:8000/api/shop/users"
CONCURRENT_THREADS = 5
TOTAL_REQUESTS_PER_THREAD = 50
TIMEOUT = 10
核心说明:
配置项集中管理,便于用户根据实际场景修改,无需改动核心逻辑 TARGET_URL:目标压测接口地址,需与FastAPI服务地址保持一致CONCURRENT_THREADS:并发线程数,模拟多个用户同时发起请求(建议根据服务器性能调整,避免过度压测)TOTAL_REQUESTS_PER_THREAD:单个线程发送的请求总数,总请求数=并发线程数×单线程请求数TIMEOUT:单个请求的超时时间,超过该时间未响应则判定为请求异常
3.2 全局统计变量与线程锁模块
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
def send_requests ( thread_id: int ) - > None :
"""
单个线程的请求发送逻辑:循环发送指定数量的请求,并记录结果
"""
global total_success, total_failure, response_times
session = requests. Session( )
for req_id in range ( 1 , TOTAL_REQUESTS_PER_THREAD + 1 ) :
try :
start_time = time. perf_counter( )
response = session. get(
url= TARGET_URL,
timeout= TIMEOUT
)
end_time = time. perf_counter( )
response_time = ( end_time - start_time) * 1000
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
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 程序入口
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),提升接口并发处理能力;添加日志模块,便于问题排查 压测工具:支持分布式压测、自定义压测场景,提升压测的覆盖度和专业性