简单fastapi和压测实例

服务器

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  # 转换为毫秒

            # 验证请求是否成功(响应状态码200if 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数据库shopusers表,提供两大核心接口:

  • GET /api/shop/users:查询users表所有用户数据,返回结构化用户列表
  • POST /api/shop/users:插入一条用户数据到users表,返回插入后的完整用户信息(含自增ID)
  • 附加能力:参数校验、SQL注入防护、事务保障、异常处理、自动生成交互式接口文档

2. 依赖环境说明

依赖库版本要求核心作用
fastapi>=0.95.0构建高性能RESTful API,提供接口路由、响应模型、自动文档等能力
uvicorn>=0.22.0FastAPI官方推荐ASGI服务器,用于运行后端服务
pymysql>=1.0.0MySQL数据库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

# 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=直接报错)
)
  • 核心说明:
    1. FastAPI实例初始化时配置了文档元信息,访问http://localhost:8000/docs可查看交互式接口文档
    2. MYSQL_CONFIG需与实际MySQL环境匹配,charset=utf8mb4支持全量中文与特殊字符
    3. 连接池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
  • 核心说明:
    1. UserCreateRequest:用于POST接口的请求体校验,Field约束name为必填、非空、长度1-50,不符合约束时自动返回422错误
    2. UserInfo:用于接口响应数据格式化,统一返回idname字段;orm_mode=True支持将数据库查询结果(字典/元组)转换为模型实例
    3. 依赖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()
        # 创建游标(指定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()
  • 核心说明:
    1. 采用try-except-finally结构,保证游标关闭和连接归还(即使查询异常,也不会导致连接泄露)
    2. DictCursor游标返回字典格式结果(含字段名),便于后续接口返回结构化数据
    3. 连接从连接池获取(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()
        # 创建游标(普通游标,用于获取自增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()
  • 核心说明:
    1. 事务保障:conn.commit()提交事务(插入操作生效),异常时conn.rollback()回滚事务(避免脏数据)
    2. 防SQL注入:采用参数化查询(%s占位符),参数通过元组传入,避免字符串拼接带来的注入风险
    3. cursor.lastrowid获取插入数据的自增ID(要求usersid字段为AUTO_INCREMENT
    4. 插入后查询完整用户信息,保证返回数据与查询接口格式一致,提升接口易用性
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))
  • 核心说明:
    1. @app.get注解定义GET请求路由,response_model=List[UserInfo]约束返回数据格式为UserInfo模型列表
    2. 异常处理:捕获数据库查询异常,返回500状态码和详细错误信息,便于问题排查
    3. 异步函数(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  # 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))
  • 核心说明:
    1. @app.post注解定义POST请求路由,status_code=201符合RESTful规范(资源创建成功)
    2. user_request: UserCreateRequest自动解析请求体并完成参数校验,校验失败返回422错误
    3. 传入校验后的user_request.name到插入函数,保证数据库接收的是合法数据
3.5 服务运行模块
if __name__ == "__main__":
    import uvicorn
    uvicorn.run(
        app="__main__:app",
        host="0.0.0.0",
        port=8000,
        reload=True
    )
  • 核心说明:
    1. 采用uvicorn运行FastAPI应用,host="0.0.0.0"允许局域网内其他设备访问,port=8000为服务端口
    2. reload=True开启热重载(开发环境专用),代码修改后自动重启服务,无需手动重启
    3. 生产环境建议关闭reload,并配置workers(多进程)提升并发能力

4. 使用指南

4.1 前置准备
  1. 安装依赖:pip install fastapi uvicorn pymysql dbutils pydantic
  2. 配置MySQL环境:
    • 启动MySQL服务,创建数据库shopCREATE 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中的userpassword为实际MySQL账号密码
4.2 启动服务
  1. 将代码保存为user_service.py
  2. 运行命令:python user_service.py(或uvicorn user_service:app --host 0.0.0.0 --port 8000 --reload
  3. 服务启动成功标志:控制台输出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中的userpassword
接口返回500,提示“数据库查询失败:1049 (42000): Unknown database ‘shop’”未创建shop数据库执行创建数据库的SQL语句
POST接口返回422,提示“field required”请求体缺少name字段或字段名拼写错误确保请求体包含name字段,字段名拼写正确
POST接口插入成功,但返回无idusersid字段未设置为自增主键修改users表,将id字段设置为AUTO_INCREMENT
高并发下接口返回500,提示“Too many connections”MySQL最大连接数不足或连接池配置过小1. 增大MySQL最大连接数;2. 调大POOLmaxconnections参数

第二部分: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"  # 你的FastAPI接口地址
CONCURRENT_THREADS = 5  # 并发线程数(模拟20个同时请求的用户)
TOTAL_REQUESTS_PER_THREAD = 50  # 每个线程发送的请求数
TIMEOUT = 10  # 单个请求超时时间(秒)
  • 核心说明:
    1. 配置项集中管理,便于用户根据实际场景修改,无需改动核心逻辑
    2. TARGET_URL:目标压测接口地址,需与FastAPI服务地址保持一致
    3. CONCURRENT_THREADS:并发线程数,模拟多个用户同时发起请求(建议根据服务器性能调整,避免过度压测)
    4. TOTAL_REQUESTS_PER_THREAD:单个线程发送的请求总数,总请求数=并发线程数×单线程请求数
    5. TIMEOUT:单个请求的超时时间,超过该时间未响应则判定为请求异常
3.2 全局统计变量与线程锁模块
# ---------------------- 全局统计变量(线程安全) ----------------------
total_success = 0
total_failure = 0
response_times: List[float] = []
lock = threading.Lock()  # 线程锁,保证统计变量修改的安全性
  • 核心说明:
    1. 全局统计变量:
      • total_success:成功请求总数(响应状态码200)
      • total_failure:失败请求总数(非200状态码+请求异常)
      • response_times:所有成功请求的响应时间列表(用于计算平均响应时间)
    2. threading.Lock():创建线程锁,解决多线程并发修改全局变量时的数据竞争问题(确保同一时间只有一个线程修改全局变量,避免数据统计错误)
3.3 单个线程请求发送函数 send_requests
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)}")
  • 核心说明:
    1. requests.Session():每个线程创建一个Session对象,复用TCP连接(避免每次请求都创建新连接),大幅提升压测效率
    2. time.perf_counter():高精度时间统计函数,用于计算请求响应时间(转换为毫秒后,精度更高,便于分析接口性能)
    3. 异常处理:捕获请求超时、网络中断、接口报错等异常,统一计入total_failure
    4. with lock:上下文管理器自动加锁/释放锁,修改全局变量时保证线程安全,避免数据统计偏差
    5. 实时打印:输出单个请求的执行结果(成功/失败/异常),便于实时监控压测过程中的接口状态
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)
  • 核心说明:
    1. 压测前信息打印:输出压测配置参数,便于确认压测场景是否符合预期
    2. 线程创建与启动:
      • 循环创建指定数量的线程,每个线程绑定send_requests函数,并传入线程ID
      • thread.start()启动线程,所有线程同时开始执行请求发送逻辑
    3. thread.join():等待所有线程执行完成后,再进行统计结果计算,确保统计数据完整
    4. 核心指标计算:
      • total_duration:压测总耗时(从第一个线程启动到最后一个线程结束)
      • avg_response_time:成功请求的平均响应时间(仅统计成功请求,更能反映接口正常性能)
      • qps:每秒查询率(成功请求数/总耗时),衡量接口的并发处理能力
    5. 压测后汇总输出:以清晰格式输出核心统计指标,便于分析接口性能瓶颈
3.5 程序入口
if __name__ == "__main__":
    run_benchmark()
  • 核心说明:程序运行时,自动调用run_benchmark函数,启动完整压测流程

4. 使用指南

4.1 前置准备
  1. 安装依赖:pip install requests
  2. 确保FastAPI用户管理接口服务已正常启动(http://localhost:8000/api/shop/users可正常访问并返回数据)
  3. 根据实际测试需求,修改压测配置项(如调整并发线程数、单线程请求数)
4.2 启动压测
  1. 将代码保存为api_benchmark.py
  2. 运行命令:python api_benchmark.py
  3. 压测启动标志:控制台输出压测配置信息,随后开始实时打印单个请求的执行结果
4.3 压测结果解读
指标名称指标含义解读要点
总耗时整个压测流程的持续时间反映完成所有请求所需的时间,结合总请求数可初步判断接口处理效率
成功请求数/失败请求数压测过程中成功/失败的请求总量失败请求数>0时,需排查接口是否存在性能瓶颈或稳定性问题
成功率成功请求数占总请求数的比例理想状态下成功率应为100%,若低于99%,需重点分析失败原因
平均响应时间所有成功请求的响应时间平均值反映接口的平均处理速度,数值越低,接口性能越好
QPS(每秒查询率)每秒能处理的成功请求数衡量接口并发处理能力的核心指标,数值越高,接口的高并发支撑能力越强

5. 优化建议与注意事项

5.1 压测配置优化建议
  1. 逐步提升并发量:从低并发(如5线程)开始,逐步增加到高并发(如50、100线程),避免一次性高并发压垮服务器
  2. 控制单线程请求数:单线程请求数建议设置为50-200,过多可能导致单个线程运行时间过长,影响压测效率
  3. 合理设置超时时间:根据接口正常响应时间调整TIMEOUT(如接口正常响应时间<1秒,可设置为5秒),避免超时时间过长导致压测结果失真
5.2 线程安全注意事项
  1. 全局变量修改必须加锁:total_successtotal_failureresponse_times为全局变量,多线程修改时必须通过with lock保证线程安全,否则会出现统计数据错误
  2. 每个线程独立创建requests.Session:避免多个线程共享一个Session对象,导致请求冲突或连接泄露
5.3 压测环境注意事项
  1. 压测环境与生产环境尽量一致:确保硬件配置、MySQL配置、服务部署方式与生产环境一致,压测结果才具有参考价值
  2. 避免在生产环境直接压测:压测会占用大量服务器资源,可能影响生产业务正常运行,建议在测试环境进行压测
  3. 压测前备份数据库:高并发压测可能导致数据库负载过高,甚至出现数据异常,压测前建议备份shop数据库
5.4 功能扩展建议
  1. 支持POST接口压测:添加请求体配置项,修改session.getsession.post,支持对插入接口进行压测
  2. 增加响应数据校验:校验接口返回数据的格式和内容,避免接口返回200但数据异常的情况
  3. 生成压测报告:将压测结果保存为CSV/HTML文件,便于后续分析和归档
  4. 支持自定义请求头:添加请求头配置项,支持携带认证信息、Content-Type等自定义请求头

第三部分:两份代码的联动说明与整体总结

1. 联动流程

  1. 启动FastAPI用户管理接口服务,确保GET /api/shop/users接口可正常访问
  2. 配置压测工具的TARGET_URL与FastAPI接口地址一致
  3. 启动压测工具,模拟多线程并发请求,对接口进行性能测试
  4. 查看压测结果,分析接口成功率、响应时间、QPS等指标
  5. 根据压测结果优化FastAPI服务(如调整连接池配置、优化SQL、开启多进程运行)

2. 整体总结

  1. 两份代码实现了「后端服务搭建」与「接口性能压测」的完整闭环,便于快速验证用户管理接口的功能和性能
  2. FastAPI接口服务具备高可用性、安全性、可扩展性,支持后续功能扩展(如用户更新、删除、分页查询)
  3. 多线程压测工具轻量易用、配置灵活,可快速适配不同接口的压测需求,为接口性能优化提供数据支撑
  4. 核心亮点:
    • 后端服务:连接池复用、参数校验、事务保障、防SQL注入,确保服务稳定安全
    • 压测工具:线程安全、实时监控、核心指标统计,确保压测结果准确可靠
  5. 后续优化方向:
    • 后端:采用异步数据库驱动(asyncmy),提升接口并发处理能力;添加日志模块,便于问题排查
    • 压测工具:支持分布式压测、自定义压测场景,提升压测的覆盖度和专业性
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值