【AI图像生成网站Golang】项目测试与优化

news2025/3/10 10:46:49

AI图像生成网站

目录

一、项目介绍

二、雪花算法

三、JWT认证与令牌桶算法

四、项目架构

五、图床上传与图像生成API搭建

六、项目测试与优化


六、项目测试与优化

在开发过程中,性能优化是保证项目可扩展性和用户体验的关键步骤。本文将详细介绍我如何使用一些主流的性能评估工具进行项目性能检测,并在此基础上进行优化。

1. 性能评估工具

我使用的性能检测工具如下:

  • k6: 用于负载测试的开源工具,可以通过编写简单的javascript测试脚本,模拟高并发场景下的用户行为。
  • k6 cloud: 是k6的云服务扩展,能够提供比本地运行更详细的性能监控和分析报告。
  • pprof: 用于捕获CPU、内存等的使用情况进行采样分析,帮助定位性能瓶颈
  • trace: 可以通过可视化的时间线查看goroutine的执行和调度情况。

在后文中,我们会使用k6运行测试文件,并根据k6 cloud上通过的请求数和RPS(Requests Per Second,每秒请求数)来简单评估性能,之后会使用pprof和trace工具查找性能瓶颈和突破口。

2. 性能评估与分析

测试流程

在进行负载测试之前,我首先进行了单用户的性能测试,用于了解每个请求的处理时长,确保基本功能的正常工作。之后,我模拟了并发场景,逐渐增加并发量测试系统的表现。

使用 k6 进行负载测试

单用户测试: 在单用户场景下,我编写了一个的k6脚本,模拟了一个用户从注册到上传作品的一套API调用流程。

import http from 'k6/http';
import { sleep, check } from 'k6';
import {generateImage, generateRandomPrompt, generateUniqueUsername,generateUniqueEmail} from "./dynamic_data.js";

// 配置接口的基本 URL
const BASE_URL = 'http://localhost:8080/api/v1';

// 读取 Base64 数据
const base64Image = open("image_base64.txt");

// 定义一个变量来跟踪是否正在刷新 token
let refreshing = false;

// k6 入口函数
export default function () {
    // 用户注册信息
    const USER_CREDENTIALS = {
        avatar: `data:image/png;base64,${generateImage(base64Image)}`,
        username: generateUniqueUsername(),
        password: 'password123',
        confirm_password: 'password123',
        email: generateUniqueEmail(),
    };
    // 1. 用户注册
    const res = http.post(`${BASE_URL}/signup`, JSON.stringify(USER_CREDENTIALS), {
        headers: { 'Content-Type': 'application/json' },
    });
    check(res, { 'signup succeeded': (r) => r.status === 200 });

    // 2. 用户登录
    const loginRes = http.post(`${BASE_URL}/login`, JSON.stringify(USER_CREDENTIALS), {
        headers: { 'Content-Type': 'application/json' },
    });

    check(loginRes, {
        'Login succeeded': (r) => r.status === 200,
    });

    const accessToken = loginRes.json('data.access_token');
    const refreshTokenValue = loginRes.json('data.refresh_token');
    if (!accessToken || !refreshTokenValue) {
        throw new Error('Login failed, tokens not received');
    }

    // 3. 创建分组
    const genCategory = {
        cover: `data:image/png;base64,${generateImage(base64Image)}`,
        category_name: `category_${Math.random().toString(36).substring(2, 15)}`,
        description: generateRandomPrompt(),
    };
    const createGroupRes = apiRequest('POST', `${BASE_URL}/createCategory`, genCategory, accessToken);
    check(createGroupRes, { 'Create group succeeded': (r) => r.status === 200 });

    // 4. 浏览分组
    const groupRes = apiRequest('GET', `${BASE_URL}/category`, null, accessToken);
    check(groupRes, { 'Browse groups succeeded': (r) => r.status === 200 });

    const groups = groupRes.json('data');

    const groupId = groups[0].category_id;

    // 5. 浏览作品
    const worksRes = apiRequest('GET', `${BASE_URL}/categoryDetail/${groupId}`, null, accessToken);
    check(worksRes, { 'Browse works succeeded': (r) => r.status === 200 });

    // 6. 提交图像处理任务
    const imageTaskPayload = {
        ori_image: `data:image/png;base64,${generateImage(base64Image)}`,
        category_id: groupId,
        prompt: generateRandomPrompt(),
    };

    const processRes = apiRequest('POST', `${BASE_URL}/processImage`, imageTaskPayload, accessToken);
    check(processRes, { 'Process task submitted': (r) => r.status === 200 });

    // 7. 上传作品
    const newWork = {
        work_image: `data:image/png;base64,${generateImage(base64Image)}`,
        // work_image: `data:image/png;base64,${genImage}`,
        category_id: groupId,
        prompt: generateRandomPrompt(),
    };

    const uploadRes = apiRequest('POST', `${BASE_URL}/uploadWork`, newWork, accessToken);
    check(uploadRes, { 'Upload work succeeded': (r) => r.status === 200 });
}

// 刷新 Token 的逻辑
function refreshToken(refreshToken) {
    const refreshRes = http.post(`${BASE_URL}/refresh_token`, JSON.stringify({ refresh_token: refreshToken }), {
        headers: { 'Content-Type': 'application/json' },
    });

    check(refreshRes, {
        'Token refreshed': (r) => r.status === 200,
    });

    const newToken = refreshRes.json('access_token');
    if (!newToken) {
        throw new Error('Failed to refresh token');
    }

    return newToken;
}

// API 请求方法
function apiRequest(method, url, body, token) {
    const headers = {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${token}`,
    };

    let res;
    if (method === 'GET') {
        res = http.get(url, { headers });
    } else if (method === 'POST') {
        res = http.post(url, JSON.stringify(body), { headers });
    }

    // 如果遇到 401 错误,且没有正在刷新 token
    if (res.status === 401 && !refreshing) {
        console.log('Access token expired, refreshing...');

        // 设置 refreshing 为 true,避免并发请求时重复刷新
        refreshing = true;

        // 刷新 token
        const newToken = refreshToken(refreshTokenValue);

        // 刷新完成后,重新发送请求
        res = apiRequest(method, url, body, newToken);

        // 刷新完成,重置 refreshing 状态
        refreshing = false;
    }

    return res;
}

测试结果(单用户场景):

点击图中链接即可进入 k6 cloud 界面查看更详细的数据,可以看到单例用户的运行时长为8.3s。

在这里插入图片描述在这里插入图片描述
进入可视化界面可以看到,单例用户的总请求为7个,平均响应时间为 4372 ms,失败率为 0% ,每秒请求数(RPS)在 1~2 之间。
在这里插入图片描述在这里插入图片描述
在单用户场景下,系统表现良好,响应时间低,且无错误发生。接下来,我开始使用并发用户场景进行测试,模拟“高负载”下的表现(展示实验用的电脑配置低,不能模拟真正的高并发,在此仅做示例
^_^")。

并发测试

在使用之前编写的Python API代码进行并发测试时,出现了报错: Model inference error: index 11 is out of bounds for dimension 0 with size 11 ,这时将 Python API 中这一段的 num_inference_steps=10 改小一些即可。

具体原因不在这里占用篇幅,请移步这里查看:解决并发情况下调用 Instruct-pix2pix 模型推理错误

images = pipe(prompt=request.prompt, image=image, num_inference_steps=5, image_guidance_scale=1).images

在之前的代码中加入以下部分即可进行并发测试。这里我使用了 k6 的 constant-arrival-rate 执行器模式来控制请求的速率和并发用户数。

constant-arrival-rate 配置下,k6 会持续不断地以固定速率发送请求,在本测试环境下,rate: 5 意味着 k6 会保持每秒发送 5 个请求,直到测试结束。这种配置适合模拟某些稳定的请求负载。

export const options = {
    scenarios: {
        my_test: {
            executor: 'constant-arrival-rate',
            rate: 5, // 每秒发送 5 个请求
            duration: '5m', // 持续 5 分钟
            preAllocatedVUs: 5, // 提前分配的虚拟用户数
            maxVUs: 10, // 最大虚拟用户数
        },
    },
};

当然,除了 constant-arrival-rate 之外,k6 还提供了其他几种执行器模式,适用于不同的负载场景,比如:

  • virtual-users (VUs):

这是最常见的执行器模式,适用于模拟一群用户的行为,可以模拟不同用户的交替请求。

export const options = {
  stages: [
    { duration: '10s', target: 10 }, // 10 秒内增加到 10 个虚拟用户
    { duration: '1m', target: 10 },  // 保持 10 个虚拟用户 1 分钟
    { duration: '10s', target: 0 },  // 10 秒内减少到 0
  ],
};
  • ramping-arrival-rate

这种模式用于模拟请求逐步增加的情况,可以通过配置startRate(起始速率)、endRate(结束速率)和 duration(持续时间)来设置请求的增长速率。

export const options = {
  scenarios: {
    my_test: {
      executor: 'ramping-arrival-rate',
      startRate: 10, // 初始请求速率每秒 10 个请求
      endRate: 50, // 最终请求速率每秒 50 个请求
      duration: '2m', // 持续 2 分钟
    },
  },
};
  • shared-iterations

在这种模式下,所有虚拟用户共同执行相同数量的请求,直到所有请求完成。

export const options = {
  scenarios: {
    my_test: {
      executor: 'shared-iterations',
      iterations: 100, // 共有 100 次请求
      vus: 10, // 使用 10 个虚拟用户
    },
  },
};
  • per-vu-iterations

这种模式下,每个虚拟用户会执行一定次数的操作。

export const options = {
  scenarios: {
    my_test: {
      executor: 'per-vu-iterations',
      vus: 10, // 使用 10 个虚拟用户
      iterations: 100, // 每个虚拟用户执行 100 次操作
    },
  },
};

并发测试完成后,进入 k6 cloud 界面查看测试数据。

在这里插入图片描述
可以看出,虽然我使用了 constant-arrival-rate 配置,但是图中的请求数量和响应数量很不稳定。

并且可以观察到,随着并发量的增加,一些需要调用API的接口响应时间明显上升,尤其是 p95p99

初步判断是 API 调用部分出现了阻塞。

在这里插入图片描述
pprof 分析

使用 pprof 跟踪代码的 CPU 占用率发现,耗时最长的是 net.(*Resolver) lookupIP ,占用了 56.46% 的 CPU 时间。该函数负责 DNS 查询,用于将域名解析为IP地址,解析流程通常包括:

  • 本地缓存查询
  • 向 DNS 服务器发送请求
  • 等待服务器返回解析结果

在项目中,如果短时间内发起大量外部请求,且每次请求都需要进行完整的 DNS 查询流程,将会导致 DNS 查询成为性能瓶颈。

此外,SetupRouter 中的日志记录(19.24%)和身份验证(12.48%)中间件是系统的次要瓶颈。

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

3. 优化方案

在 Go 中,默认 HTTP 客户端会在每次请求时触发新的 DNS 查询,如果复用 htttp.Client,则可以有效减少 DNS 查询次数。

同时,将外部 API 调用解耦到 Goroutine 中异步执行,可以让主线程快速处理其他任务,如日志写入等。

我们还需要引入 redis 来缓存一些会重复查询的信息,如已经验证过的用户信息等。

4. 优化实践

4.1 引入 Goroutine 解耦外部 API 调用

之前,外部 API 调用在主线程中直接执行,导致主线程需要等待外部服务返回结果。我们通过以下方式进行优化:

(1)图像上传与处理的异步化:

  • 将图像上传和处理 API 任务拆分为两个 Goroutine。
  • 使用 Redis 和 RabbitMQ 实现消息队列,将任务放入队列中,由独立的消费者处理,避免主线程阻塞。
  • 使用 sync.WaitGroupchannel 进行任务同步与通信。
var wg sync.WaitGroup
	wg.Add(3) // 我们要等待两个消费者 goroutine

	go func() {
		defer wg.Done()
		controller.ConsumeUploadTasks("upload_image")
	}()

	go func() {
		defer wg.Done()
		controller.ConsumeProcessTasks("process_image")
	}()

(2)HTTP 客户端复用:

  • 创建一个全局的 http.Client,启用连接池复用。
var ossClient *oss.Client

func init() {
	// 确保配置已经加载
	if err := settings.Init(); err != nil {
		log.Fatalf("failed to load settings: %v", err)
	}
	
	// 初始化 OSS 客户端配置
	cfg := oss.LoadDefaultConfig().
		WithRegion(settings.Conf.Region).
		WithCredentialsProvider(credentials.NewStaticCredentialsProvider(settings.Conf.AccessKeyID, settings.Conf.AccessKeySecret, ""))

	// 创建全局 OSS 客户端实例
	ossClient = oss.NewClient(cfg)
}

// UploadImageToOSS 上传图片的函数
func UploadImageToOSS(base64Image string) (string, error) {

	if strings.HasPrefix(base64Image, "data:image") {
		base64Image = strings.Split(base64Image, ",")[1]
	}

	// 解码 Base64 数据
	imageData, err := base64.StdEncoding.DecodeString(base64Image)
	if err != nil {
		return "", fmt.Errorf("failed to decode base64 data: %v", err)
	}

	// 生成文件名
	objectName := GenerateUniqueFileName("png")

	// 创建上传对象的请求
	request := &oss.PutObjectRequest{
		Bucket: oss.Ptr(settings.Conf.Bucket), // 存储空间名称
		Key:    oss.Ptr(objectName),           // 对象名称
		Body:   bytes.NewReader(imageData),    // 图片数据
	}

	// 上传文件
	_, err = ossClient.PutObject(context.TODO(), request)
	if err != nil {
		return "", fmt.Errorf("failed to upload image to OSS: %v", err)
	}

	// 生成文件的 URL
	url := fmt.Sprintf("%s/%s", settings.Conf.Endpoint, objectName)

	return url, nil
}

4.2 身份验证优化

对于身份验证逻辑,我引入了 Redis 缓存来避免频繁查询数据库。

// CacheUserInfo 缓存用户信息
func CacheUserInfo(userInfo map[string]interface{}) error {

	key := fmt.Sprintf("user_info:%s", userInfo["user_id"])
	err := client.HMSet(ctx, key, userInfo).Err()
	if err != nil {
		return fmt.Errorf("failed to HMSet: %w", err)
	}

	err = client.Expire(ctx, key, time.Duration(settings.Conf.RedisConfig.DefaultTTL)).Err()
	if err != nil {
		return fmt.Errorf("failed to set expire: %w", err)
	}

	// 如果都成功,返回 nil
	return nil

}

// GetCachedUserInfo 获取缓存的用户信息
func GetCachedUserInfo(userID string) (map[string]string, error) {
	key := fmt.Sprintf("user_info:%s", userID)
	return client.HGetAll(ctx, key).Result()
}

4.3 效果测试

在对项目进行优化后,通过 k6 cloud 工具对性能进行对比测试发现,系统的并发能力提升了近 7 倍,总请求数较之前增长了 2.3 倍,系统处理请求的效率得到了显著提升。

项目优化前优化后
总请求数5981.4K
请求速率峰值(reqs/s)6.3344.33
95% 的响应时间(ms)321323.3

在 k6 可视化界面,可以看到在测试初期,请求速率(RPS)和响应时间会快速上升到一个较高的峰值。这说明系统能够快速响应并处理高并发请求。

峰值过后,请求速率和响应时间会迅速下降到一个较低的数值,并且请求速率始终保持一定的小幅波动。这是因为在高并发情况下,CPU 和内存资源的分配会随着请求数量的波动而调整,导致 RPS 的小幅变化。

在这里插入图片描述
在优化前,系统中耗时最多的四个请求占用大量时间,优化后有了显著的降低。

在这里插入图片描述

DNS 查询的 CPU 占用率从 56.46% 降低到 21.16% 。

主线程负载显著减轻,高耗时任务被转移到 Goroutine 中异步处理。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在 trace 可视化界面中,能看到Goroutines 使用数量平稳,分布较为均匀,未出现 Goroutine 阻塞的尖峰现象。同时运行的 Goroutines 数量略高,但在合理范围内。

Heap 的内存分配曲线波动较大,但回收相对及时,未出现内存泄漏或超量分配的现象。GC 活跃,Heap 内存峰值控制在可接受范围。

Threads 数量略高,可能与 HTTP 客户端连接池、Redis 以及 RabbitMQ 的 I/O 操作相关。

GC 曲线与 Heap 使用曲线基本同步,垃圾回收频繁但时间较短。未来会考虑进一步优化数据结构设计,避免频繁分配临时对象。

网络 I/O 分布均匀,RabbitMQ 和 Redis 的使用改善了对数据库的直接依赖。外部 API 调用已异步化,但响应时间稍长。

在这里插入图片描述
对各个 Proc,PU 核心的使用较为均匀,任务分配合理。Goroutine 与 Proc 的调度平衡较好,未观察到 Proc 长时间空闲。

在未来的工作中,将会考虑尝试将 RabbitMQ 的消费者分布式部署,以进一步增加并发量。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2262824.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

单节点calico性能优化

在单节点上部署calicov3273后,发现资源占用 修改calico以下配置是资源消耗降低 1、因为是单节点,没有跨节点pod网段组网需要,禁用overlay方式网络(ipip,vxlan),使用route方式网络 配置calico-node的环境变量 CALICO_IPV4POOL_I…

Redis 中 IntSet 底层数据结构

IntSet 底层数据结构 序言: 像字符串 SDS 只是保存了一个变量的值,但是像 Redis 中也是需要保存一些集合元素的,这里就介绍一下其中一种集合 IntSet,由于是 Set 所以也有 Set 的一些特性,不过也多加了一些特性: ● 唯…

Java——网络编程(下)

(UDP通讯的实现) 1 UDP通信介绍 (面向无链接的一个传输协议——>不会创建连接——>效率高) (发送数据要经行封包操作——>使用DatagramPacket类——>底层是UDP) (DatagramPacket——>数据封包——>发送数据和接收数据都要去包装对象!&#xf…

《Django 5 By Example》读后感

一、 为什么选择这本书? 本人的工作方向为Python Web方向,想了解下今年该方向有哪些新书出版,遂上packt出版社网站上看了看,发现这本书出版时间比较新(2024年9月),那就它了。 从2024年11月11日至2024年12月18日期间&…

基于Spring Boot的校园商城系统

一、系统背景与意义 随着互联网技术的快速发展,电子商务已经渗透到生活的方方面面。校园作为一个相对封闭但活跃的社群,同样需要一个专门的线上平台来满足其特殊的需求。基于Spring Boot的校园商城系统正是为此目的而设计,它结合了微服务架构…

感知机收敛性定理证明

1. 问题描述 感知机收敛性定理假设: 存在一个参数向量 θ(被归一化为单位向量,,以及一个正数 ,使得对所有训练样本 满足: 这是线性可分的假设,意味着每个样本点与正确超平面之间有一个至少为的…

ai绘图丨中国新年春节背景第二弹(附关键词

使用工具:千鹿AI 咒语:圆形平面讲台,5 个礼品盒和台灯交错排列,红色背景上的圆形,中国唐朝风格,红色和金色主题,3D 效果图,摄影棚灯光,简约产品展示模型,逼真…

深度学习每周学习总结J9(Inception V3 算法实战与解析 - 天气识别)

🍨 本文为🔗365天深度学习训练营 中的学习记录博客🍖 原作者:K同学啊 | 接辅导、项目定制 目录 0. 总结Inception V1 简介Inception V3 简介1. 设置GPU2. 导入数据及处理部分3. 划分数据集4. 模型构建部分5. 设置超参数&#xff1…

Restaurants WebAPI(一)—— clean architecture

文章目录 项目地址一、Restaurants.Domain 核心业务层1.1 Entities实体层1.2 Repositories 数据操作EF的接口二、Restaurants.Infrastructure 基础设施层2.1 Persistence 数据EF CORE配置2.2 Repositories 数据查询实现2.3 Extensions 服务注册三、Restaurants.Application用例…

道路运输企业安全生产管理人员安全考核试题

道路运输企业安全生产管理人员安全考核试题 一、单选题 题干:在公交车行驶过程中,乘客王某因与驾驶员发生矛盾,遂殴打驾驶员并抢夺方向盘,造成其他乘客受轻微伤,依照《中华人民共和国刑法》的规定,王某触…

FFmpeg库之ffplay

文章目录 FFmpeg环境搭建ffplay使用通用选项视频选项音频选项快捷键使用滤镜直播拉流 FFmpeg环境搭建 FFmpeg官网 FFmpeg环境搭建 ./configure \--prefix"$HOME/ffmpeg" \--extra-cflags"-I$HOME/ffmpeg/include" \--extra-ldflags"-L$HOME/ffmpeg…

HTTP 协议报文结构 | 返回状态码详解

注:本文为 “HTTP 历史 | 协议报文结构 | 返回状态码” 相关文章合辑。 未整理去重。 HTTP 历史 wangjunliang 最后更新: 2024/3/16 上午10:29 超文本传输协议(英语:HyperTextTransferProtocol,缩写:HTTP)是 万维网(World Wide Web)的基础协议。自 蒂姆…

springboot444新冠物资管理系统的设计与实现(论文+源码)_kaic

摘 要 传统办法管理信息首先需要花费的时间比较多,其次数据出错率比较高,而且对错误的数据进行更改也比较困难,最后,检索数据费事费力。因此,在计算机上安装新冠物资管理系统软件来发挥其高效地信息处理的作用&#x…

【数字信号处理】数字信号处理试题及答案,离散序列,Z变换,傅里叶变换

关注作者了解更多 我的其他CSDN专栏 过程控制系统 工程测试技术 虚拟仪器技术 可编程控制器 工业现场总线 数字图像处理 智能控制 传感器技术 嵌入式系统 复变函数与积分变换 单片机原理 线性代数 大学物理 热工与工程流体力学 数字信号处理 光电融合集成电路…

【系统】Mac crontab 无法退出编辑模式问题

【系统】Mac crontab 无法退出编辑模式问题 背景一、问题回答1.定位原因:2.确认编辑器类型3.确保编辑器进入正确3.1 确认是否有crontab调度任务3.2 进入编辑器并确保编辑器正常3.3 保存操作 4.确认crontab任务存在5.确保脚本的可执行性和正确性 二、后续 背景 之前…

6.3.1 MR实战:计算总分与平均分

在本次实战中,我们的目标是利用Apache Hadoop的MapReduce框架来处理和分析学生成绩数据。具体来说,我们将计算一个包含五名学生五门科目成绩的数据集的总分和平均分。这个过程包括在云主机上准备数据,将成绩数据存储为文本文件,并…

开发平台接口规范:北斗终端->客户平台(上行)| 时空信息产品

文章目录 引言I 技术架构和业务流程II 渠道接口验证签名白名单IP渠道配置表设计III 其他辅助功能TCP 发送消息到消息中心nginx转发网关服务异常捕获日志采集IV 知识扩展对请求参数进行校验引言 开发平台的应用场景:平台需要开发能力给下游平台需要接收上游的回调数据,例如接收…

MySQL知识汇总(一)

一些命令行操作注意加 分号 “ ; ” show databases 查看所有数据库 use 数据库名 切换数据库 show tables 查看数据库中所有表 describe 表名 显示表中所有信息 create database [if not exists] 新库名 创…

vsCode怎么使用vue指令快捷生成代码

1.下载Vetur插件 2.在文件-首选项-配置代码片段中找到vue.json文件 (注:旧版本的编辑器路径为文件-首选项-用户片段) 3.在打开的配置代码片段弹窗中搜索vue.json,找到并打开 (注:如果搜不到的话就按住鼠标…

QAnything源码学习

以下解读基于时间:20241218 概述 官方架构图如下: 该有的模块基本上都有了: Query理解检索 召回重排 大模型生成数据入库 下面就从以上几个模块分别看看对应的源码 讲源码之前还是想先讲讲这个项目的目录结构,这样可能会更方…