马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
在制作 AI GO ADMIN 的接口节流功能的时候,需要跨平台进行测试,突发奇想,何不找个全新的线上服务器,压测一下常见 PHP 框架和 Go 的来个对比,看看性能差距到底如何。
ai-go-admin 是一款主打可视化 CRUD 和极简内核的开源后台体系,完全诞生于 AI 时代,技术栈是 Golang + VUE3,最近刚做完,完全开源免费商用,欢迎体验:https://github.com/ai-go-hub/ai-go-admin
测试环境
- CPU:4核 AMD EPYC 9K65 192-Core Processor
- 内存:8G
- 硬盘:通用 SSD(50 GB)
- 操作体系:Ubuntu Server 26.04 LTS 64位
- 基础资源占用:CPU 约 1%,内存约 12%
测试代码
代码直接让 AI 写的,目检了一下没啥大问题,放在文末。
测试代码规划纯凭感觉,一是需要含数据库业务,同一使用 PostgreSQL 数据库,同一个表,其中预设了 10 行数据,测试程序需要查询所有数据,然后直接输出数据条数。
人工确定了查库的逻辑是存在(生性多疑的我怕 AI 直接给我输出 10 😂),然后是支持常驻内存的,需要对数据库连接句柄进行维护,而不是每次都重新连接。
不返回所有数据内容,只是单纯的返回数据条数;不返回 JSON,各人都直接返回纯文本即可。
所有框架都没有引入多余依赖,做最简实现。
框架/语言 版本 说明 Go 1.27.0 net/http、pgx 连接池 php-fpm 8.2 使用了 PHP 自带的 pgsql 扩展 thinkphp 8.0 think-orm webman 2.1 使用了 PHP 自带的 pgsql 扩展 ai go admin 1.26.6 这份是功能齐全的完整体系,关闭节流后进行测试,全套生产级依赖,含:Gin、GORM 等 测试流程
首先将 pgsql 服务性能开到最高,最大连接数搞到最大,再对以上五份代码逐一、单独测试。
开始测试前总是确保服务器处于空闲状态,然后直接压测,确定已经将服务器资源全部占满且压测无任何报错为止。
测试结果
框架/语言 RPS 原生 PHP 1363.16 Thinkphp 142.88 Webman 58084.70 原生 Go 51993.83 首先是 ThinkPHP 属实是拉完了,PHP-FPM 也不太给力。
然后 PHP 这边的大亮点是 Webman,它比原生 Go 还多点,从另外的角度证实 PHP 确实另有一战之力;
另外值得表明一下的是:Webman 基于多进程模子,单个请求发生 IO 阻塞时会占用整个工作进程,导致该进程上的全部请求被阻塞(以上压测体现不出来);而 Go 的 Goroutine 调度可以做到 M:N 调度隔离,单个请求阻塞不会影响其他请求。同时 Go 的内存管理心智负担小得多,所以并不是 Go 拉胯,应该是有所取舍?
根据 workerman 官方的数据,使用 ORM 的话每秒请求数大概要降一半,也就是大概 2.9W,Go 加上 Gin + GORM + 生产全套依赖 后另有 3.3W 左右(见后续 AI GO ADMIN 压测),个人感觉综合下来两者并发性能其实大差不差,实在要对比语言的话还是得看其他方面的差异,不再本文讨论范围内。
以下依次列出了具体压测数据
原生 PHP
- wrk -t4 -c100 -d30s http://127.0.0.1:8081/index.php
- Running 30s test @ http://127.0.0.1:8081/index.php
- 4 threads and 100 connections
- Thread Stats Avg Stdev Max +/- Stdev
- Latency 73.84ms 21.63ms 427.87ms 84.75%
- Req/Sec 342.38 37.92 480.00 70.83%
- 40941 requests in 30.03s, 7.77MB read
- Requests/sec: 1363.16
- Transfer/sec: 264.91KB
复制代码 Thinkphp
- wrk -t4 -c100 -d30s http://127.0.0.1:8082/bench
- Running 30s test @ http://127.0.0.1:8082/bench
- 4 threads and 100 connections
- Thread Stats Avg Stdev Max +/- Stdev
- Latency 691.23ms 121.58ms 1.27s 71.48%
- Req/Sec 36.56 18.96 121.00 70.87%
- 4291 requests in 30.03s, 838.09KB read
- Requests/sec: 142.88
- Transfer/sec: 27.91KB
复制代码 Webman
- wrk -t4 -c100 -d30s http://127.0.0.1:8787/bench
- Running 30s test @ http://127.0.0.1:8787/bench
- 4 threads and 100 connections
- Thread Stats Avg Stdev Max +/- Stdev
- Latency 2.20ms 3.33ms 129.03ms 94.51%
- Req/Sec 14.63k 7.28k 56.68k 62.20%
- 1748345 requests in 30.10s, 175.07MB read
- Requests/sec: 58084.70
- Transfer/sec: 5.82MB
复制代码 原生 Go
- wrk -t4 -c100 -d30s http://127.0.0.1:8090/bench
- Running 30s test @ http://127.0.0.1:8090/bench
- 4 threads and 100 connections
- Thread Stats Avg Stdev Max +/- Stdev
- Latency 2.26ms 3.20ms 77.06ms 97.91%
- Req/Sec 13.07k 1.81k 16.08k 92.67%
- 1560300 requests in 30.01s, 175.59MB read
- Requests/sec: 51993.83
- Transfer/sec: 5.85MB
复制代码 ai-go-admin 压测
首先得关闭接口节流(config/throttle.yaml),然后调高数据库连接池配置(config/config.yaml 里边的 max_open_conns: 200 和 max_idle_conns: 100)。
先做了个不带数据库业务的
- wrk -t4 -c100 -d30s http://127.0.0.1:8080/ping
- Running 30s test @ http://127.0.0.1:8080/ping
- 4 threads and 100 connections
- Thread Stats Avg Stdev Max +/- Stdev
- Latency 1.05ms 1.36ms 28.50ms 88.09%
- Req/Sec 36.98k 3.51k 50.69k 72.75%
- 4416345 requests in 30.01s, 690.73MB read
- Requests/sec: 147154.69
- Transfer/sec: 23.02MB
复制代码 带数据库业务
- wrk -t4 -c100 -d30s http://127.0.0.1:8080/bench
- Running 30s test @ http://127.0.0.1:8080/bench
- 4 threads and 100 connections
- Thread Stats Avg Stdev Max +/- Stdev
- Latency 3.33ms 2.79ms 57.06ms 78.72%
- Req/Sec 8.47k 675.08 10.81k 75.58%
- 1010958 requests in 30.00s, 176.43MB read
- Requests/sec: 33694.62
- Transfer/sec: 5.88MB
复制代码 测试代码
pgsql 初始化
- -- 测试表:5 个字段
- CREATE TABLE test_users (
- id SERIAL PRIMARY KEY,
- username VARCHAR(64) NOT NULL,
- email VARCHAR(128) NOT NULL,
- age INT NOT NULL,
- created_at TIMESTAMP NOT NULL DEFAULT NOW()
- );
- -- 插入 10 行测试数据
- INSERT INTO test_users (username, email, age, created_at) VALUES
- ('alice01', '[email protected]', 21, '2026-01-01 09:00:00'),
- ('bob02', '[email protected]', 22, '2026-01-02 09:00:00'),
- ('carol03', '[email protected]', 23, '2026-01-03 09:00:00'),
- ('dave04', '[email protected]', 24, '2026-01-04 09:00:00'),
- ('eve05', '[email protected]', 25, '2026-01-05 09:00:00'),
- ('frank06', '[email protected]', 26, '2026-01-06 09:00:00'),
- ('grace07', '[email protected]', 27, '2026-01-07 09:00:00'),
- ('henry08', '[email protected]', 28, '2026-01-08 09:00:00'),
- ('iris09', '[email protected]', 29, '2026-01-09 09:00:00'),
- ('jack10', '[email protected]', 30, '2026-01-10 09:00:00');
复制代码 Golang 原生
- // Go 基准测试程序:net/http + pgx 连接池
- // 每次请求:从连接池取连接 -> 查询 test_users 全表 -> 返回行数
- package main
- import (
- "context"
- "fmt"
- "log"
- "net/http"
- "os"
- "strconv"
- "github.com/jackc/pgx/v5/pgxpool"
- )
- // 数据库配置(硬编码,与其它测试程序保持一致)
- const (
- dbHost = "127.0.0.1"
- dbPort = 5432
- dbName = "phpgo_bench"
- dbUser = "bench"
- dbPassword = "postgres"
- // 连接池大小:默认等于 GOMAXPROCS,压测时可按 CPU 核数调整
- poolMaxConns = 8
- listenAddr = ":8090"
- )
- var pool *pgxpool.Pool
- func benchHandler(w http.ResponseWriter, r *http.Request) {
- var n int
- err := pool.QueryRow(r.Context(),
- "SELECT count(*) FROM (SELECT id, username, email, age, created_at FROM test_users) t",
- ).Scan(&n)
- if err != nil {
- http.Error(w, "query failed: "+err.Error(), http.StatusInternalServerError)
- return
- }
- w.Header().Set("Content-Type", "text/plain; charset=utf-8")
- fmt.Fprint(w, strconv.Itoa(n))
- }
- func main() {
- dsn := fmt.Sprintf("host=%s port=%d dbname=%s user=%s password=%s pool_max_conns=%d",
- dbHost, dbPort, dbName, dbUser, dbPassword, poolMaxConns)
- cfg, err := pgxpool.ParseConfig(dsn)
- if err != nil {
- log.Fatalf("parse dsn: %v", err)
- }
- pool, err = pgxpool.NewWithConfig(context.Background(), cfg)
- if err != nil {
- log.Fatalf("create pool: %v", err)
- }
- if err := pool.Ping(context.Background()); err != nil {
- log.Fatalf("ping db: %v", err)
- }
- mux := http.NewServeMux()
- mux.HandleFunc("/bench", benchHandler)
- mux.HandleFunc("/", benchHandler)
- fmt.Printf("go bench listening on %s\n", listenAddr)
- if err := http.ListenAndServe(listenAddr, mux); err != nil {
- log.Fatalf("listen: %v", err)
- os.Exit(1)
- }
- }
复制代码 php-fpm
[code] |