golang workerman webman swoole压测对比

songsong

看到哔哩哔哩有个视频对比golang和webman helloword压力测试性能,webman比goloang低了很多,低我理解,golang毕竟多线程自带协程,而且webman是框架,golang是http标准库,不在一个层次,但是感觉不会低这么多才对。还有我觉得应该是golang的http标准库和workerman对比才公平。所以我特地买了台aliyun服务器测试下,顺便把swoole workerman也压测了下,结果也发到了群里,既然测试了就记录下来给大家参考下,代码都有大家可以自行测试。

环境配置:

4核(vCPU) 4 GiB Ubuntu 20.04 64位
PHP 7.4.3
Go version go1.13.8 linux/amd64

压测命令

ab -n100000 -c200 http://127.0.0.1:xxx/ 没开keepalive
ab -n100000 -c200 -k http://127.0.0.1:xxx/ 开了keepalive

进程数(线程数数):

对go不熟悉,不知道go怎么开多进程,为了公平起见我默认workerman webman swoole 全部1个进程,go就是教程里的helloword代码,应该也是一个进程,是不是多线程我不知道。

先贴结果:

没开keepalive 开了keepalive
golang 19995 98546
workerman 30120 125986
webman 29301 85938
swoole 25836 73304
swoole+协程 27093 54596

结果是workeman压测性能高于golang,webman短连接高于golang,keepalive长连接略低于golang。
swoole短连接高于golang,keepalive低于golang。workerman和webman不管是短连接还是keepliave都高于swoole。

go代码

package main

import (
    "net/http"
)

func handler(w http.ResponseWriter,r *http.Request) {
   w.Write([]byte("hello"))
}

func main() {
    http.HandleFunc("/",handler)
    http.ListenAndServe(":8080",nil)
}

workerman代码

require_once __DIR__ . '/vendor/autoload.php';
use Workerman\Worker;
$worker = new Worker('http://0.0.0.0:12345');
$worker->onMessage = function($connection, $request)
{
     // 不加-k参数时要用close才行?
     $connection->close('hello');
};
Worker::runAll();
require_once __DIR__ . '/vendor/autoload.php';
use Workerman\Worker;
$worker = new Worker('http://0.0.0.0:12345');
$worker->onMessage = function($connection, $request)
{
    // 不加-k时用send
     $connection->send('hello');
};
Worker::runAll();

workerman这里不太好,ab测试时-k 参数需要自己区别处理,所以需要两个脚本

webman代码

<?php
namespace app\controller;
use support\Request;
class Index
{
    public function index(Request $request)
    {
        return 'hello';
    }
}

swoole代码

<?php
$http = new Swoole\Http\Server('0.0.0.0', 12346, SWOOLE_BASE);
$http->on('Request', function ($request, $response) {
    $response->end('hello');
});
$http->start();

swoole协程代码

<?php
use Swoole\Coroutine\Http\Server;
use function Swoole\Coroutine\run;

run(function () {
    $server = new Server('127.0.0.1', 9502, false);
    $server->handle('/', function ($request, $response) {
        $response->end("hello");
    });
    $server->start();
});

不开keep-alive结果截图:

截图
截图
截图
截图
截图

开keep-alive结果截图

截图
截图
截图
截图
截图

最后

我知道肯定会有人说helloword压测没有意义,但是我觉得还有一定意义的,毕竟代表了框架的极限性能啊。
https://learnku.com/laravel/t/63523
另外这里也有一个golang框架与webman的比较,带简单业务的,大家也可以参考的

11951 12 5
12个评论

鲁达

支持一下

  • 暂无评论
Tinywan

群主:我这里只开一个进程 helloworld 压测QPS达到10万左右,其它框架包括c写的扩展,只开一个进程QPS最多6万多。
Intel Core i7 处理器,php7.3。https://www.workerman.net/q/5328#reply_11278

  • 暂无评论
鲁达


亲测单进程11w++

  • 暂无评论
admin

hellword不能代表什么,但是连helloword都不行,那...

第二,框架选择并不是最先进最好,而是最适合团队的,团队易用性,协作,门槛等等。
许多知名的软件依然是最稳定的,而不是最先进的

chaz6chez

这里我有必要说一下,PHP好在开发效率上,综合下来,性能够用、开发迅速;

golang的http库更像php-fpm,fast-http在地位上有点类似workerman,golang的web开发框架里利用了http-fast且支持fork多进程的框架fiber,还有个evio库和gnet库是类似于workerman但更低层,更像一个libevent、libuv、libev

比来比去没用,用适合的语言干适合的事,高效完成任务工作,才是应该干的事

  • chaz6chez 2022-05-14

    感兴趣的人可以试一下gnet网络库

a6965921

现在不是流行协程+epoll吗

  • 暂无评论
shicaijun

你这个测试不具备实际的参考意义,真实的web应用业务场景是io密集型的,io操作是影响性能的关键,我在真实业务的测试结果是golang(gin)>swoole(hyperf)>workerman(webman),不信你可以试试,在一个接口中查几次数据库,插入或修改数据,然后写入一条日志,协程在处理io机密性操作还是有优势的,阻塞了会自动切换,要不然swoole花这么多精力搞个协程有什么意义呢

  • chaz6chez 2022-12-01

    协程不具备并发能力,协程只是调度,协程需要结合线程,swoole是单线程,golang是多线程;swoole和libco应该有渊源关系;
    另外您所谓的真实业务也需要建立在这几次查询数据库和插入及修改是否是原子性、是否有关联等前提下,顺带一提,gin+gorm的性能其实不如webman和hyperf;在重查询的实际项目中gorm+gin的组合的结果大约是hyperf、webman的80%,在重更新的实际项目中大约是hyperf、webman的105%左右,前提是webman使用常驻单例的数据库连接。

  • chaz6chez 2022-12-01

    这个实际项目中还需要考虑是否有过重的json序列化需求,如果结合json序列化,golang所谓的性能及开发效率还要大打折扣

  • chaz6chez 2022-12-01

    如果喜欢golang,我比较推荐一个基于fasthttp的框架fiber,这个框架包含了高性能orm及周边插件;如果是涉及到游戏服务开发,我推荐gnet网络框架。

  • payne 2022-12-07

    如果请求中有需要访问第三方接口的情况,比如2s才返回结果,那么webman的性能应该就不如自带协程的golang了吧,不阻塞的情况下webman性能确实不错

  • chaz6chez 2022-12-08

    类似的,golang用的线程池,和worker用多进程差不太多,线程更方便管理和弹性,但开多了一样的,区别不大;实际上整个生命周期属于阻塞队列

  • army 2023-03-07

    chaz6chez 说的很对,golang在重业务下很糟糕,我们将php项目转成了go,逻辑照搬,跑起来很差,fiber比gin要强很多,两个框架我们都上了,最后放弃go了

  • Tinywan 2023-03-07

    @army 为什么?

  • army 2023-03-07

    @Tinywan 初步断定为json的问题,起初用自带的库能干到CPU80%,换成sonic后好了很多,但还是不理想,还不如phpfpm, 其实qps500不算大,但是go表现真的不佳,不带业务或者业务小确实很流弊。

  • Tinywan 2023-03-07

    好吧!没用过go,有webman,基本够了,webman+fpm完全够用了

  • army 2023-03-07

    花了几天时间也用workerman重构了,今天在本地带业务压测,是原来php-fpm的6倍,感觉很棒,看线上效果了

  • Tinywan 2023-03-08

    可以的

  • a6965921 2023-04-18

    webman够用了 多花点精力在数据库上

  • zpw 2023-11-29

    协程不具备并发这不是瞎扯嘛

从这个我感觉就只能说明golang不用多线程并且线程数一样是比不过,但是如果golang用多线程,并且多进程情况下肯定更快

  • 暂无评论
wasoncheung

没有可比性,我用jmeter深度测试过workerman和springboot 纯hello world 前者是快,原因在于 wm的request,session等等组件的封装其实很原生,做的并不多。而主流的框架,对请求要进行很多业务的封装,比如依赖注入,响应类型的自动处理,aop切面等等 一系列的业务。

  • 暂无评论
排骨苏

我有个项目使用go开发的,目前正常使用中,在我本地调试 ,同样的请求同样的逻辑,go是10ms 而webman是25ms左右,我挺想把go改成webman的(已经重构10%),因为go我不太熟悉,开发速度很慢,也受不了不停的err判断,但是说句实在话,速度真的比webman 要快很多,webman已经开启了常驻内存

  • 排骨苏 2023-06-19

    而且go的base64解密有问题,=号如果没处理好变成%3D ,就解不出来,php没问题,写一句一个err,简直一万个卧槽

jonsan

先说结论

都不使用orm的情况下使用sql查询
webman(1.78万QPS)>swoole(1.30万QPS)>gin(0.80万QPS)
结果与第三方techempower压测跑分百分比基本一致
截图

硬件

4核4G阿里云,ubuntu 22.04 64位

压测命令

ab -n100000 -c100 -k http://127.0.0.1:8787/user # webman
ab -n100000 -c100 -k http://127.0.0.1:8080/user # go
ab -n100000 -c100 -k http://127.0.0.1:9090      # swoole

环境

PHP 8.1
swoole 5.0.3
go 1.20.5

webman
进程数 cpu*4,阻塞调用无连接池

swoole
进程数 4,协程+连接池

go(gin)
多线程+协程+连接池

业务

随机从user表查询一条记录json形式返回给客户端。用户表10万条记录。

压测结果

webman
截图

swoole
截图

go(gin)
截图

代码

webman

<?php
namespace app\controller;

use support\Db;
use support\Request;
use app\model\User;
use PDO;

class UserController
{
    public function index()
    {
        $pdo = $this->getPdo();
        $stmt = $pdo->prepare("SELECT * FROM user WHERE id = :id");
        $id = rand(1, 100000);
        $stmt->bindParam(":id", $id, PDO::PARAM_INT);
        $stmt->execute();

        $result = $stmt->fetch(PDO::FETCH_ASSOC);
        return json($result);
    }

    public function table()
    {
        $id = rand(1, 100000);
        return json(Db::table('user')->find($id));
    }

    public function model()
    {
        $id = rand(1, 100000);
        return json(User::find($id));
    }

    protected function getPdo()
    {
        static $pdo;
        if (!$pdo) {
            $db = config('database.connections.mysql.database');
            $username = config('database.connections.mysql.username');
            $password = config('database.connections.mysql.password');
            $host = config('database.connections.mysql.host');
            $dsn = "mysql:host=$host;dbname=$db";
            $pdo = new PDO($dsn, $username, $password);
            $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        }
        return $pdo;
    }

}

swoole

<?php
use Swoole\Database\PDOPool;
use Swoole\Database\PDOConfig;
use Swoole\Coroutine;

//Co::set(['hook_flags'=> SWOOLE_HOOK_ALL]);
\Swoole\Runtime::enableCoroutine();

$http = new Swoole\Http\Server('0.0.0.0', 9090, SWOOLE_BASE);
$http->set([
    'worker_num' => 4
]);
$http->on('Request', function ($request, $response) {
    static $pool;
    if (!$pool) {
        $pool = new PDOPool((new PDOConfig)
            ->withHost('127.0.0.1')
            ->withPort(3306)
            ->withDbName('bench')
            ->withCharset('utf8mb4')
            ->withUsername('root')
            ->withPassword('123456'),
            2
        );
    }

    $pdo = $pool->get();
    $statement = $pdo->prepare('select * from user where id=?');
    $id = rand(1, 100000);
    $statement->execute([$id]);
    $result = $statement->fetchAll();
    $pool->put($pdo);
    $response->end(json_encode($result[0]));
});

$http->start();

go(gin)

import (
        "database/sql"
//      "fmt"
        "log"
        "math/rand"
        "net/http"
        "time"

        "github.com/gin-gonic/gin"
        _ "github.com/go-sql-driver/mysql"
)

func main() {
        // 创建数据库连接池
        db, err := sql.Open("mysql", "root:P@ssword@tcp(localhost:3306)/bench?parseTime=true")
        if err != nil {
                log.Fatal(err)
        }
        defer db.Close()

        // 设置连接池参数
        db.SetMaxOpenConns(100)
        db.SetMaxIdleConns(10)
        db.SetConnMaxIdleTime(20 * time.Second)

        gin.SetMode(gin.ReleaseMode)

        // 创建Gin路由
        r := gin.New()

        // 注册接口
        r.GET("/user", func(c *gin.Context) {
                // 随机生成id
                id := rand.Intn(100000) + 1

                // 查询用户数据
                var name, gender string
                var age int
                err := db.QueryRow("SELECT name, gender, age FROM user WHERE id = ?", id).Scan(&name, &gender, &age)
                if err != nil {
                        c.JSON(http.StatusInternalServerError, gin.H{"error": "查询用户数据失败"})
                        return
                }

                // 构造响应数据
                user := gin.H{
                        "id":     id,
                        "name":   name,
                        "gender": gender,
                        "age":    age,
                }

                // 返回JSON响应
                c.JSON(http.StatusOK, user)
        })

        // 启动HTTP服务器
        err = r.Run(":8080")
        if err != nil {
                log.Fatal(err)
        }
}

生成user表的代码

<?php

// 连接到MySQL数据库
$mysqli = new mysqli("127.0.0.1", "root", "P@ssword");

// 检查连接是否成功
if ($mysqli->connect_error) {
    die("连接数据库失败: " . $mysqli->connect_error);
}

// 创建数据库
$createDbQuery = "CREATE DATABASE IF NOT EXISTS bench";
if ($mysqli->query($createDbQuery) !== TRUE) {
    die("创建数据库失败: " . $mysqli->error);
}

// 选择数据库
$mysqli->select_db("bench");

// 创建user表
$createTableQuery = "CREATE TABLE IF NOT EXISTS user (
    id INT(11) AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    gender VARCHAR(10) NOT NULL,
    age INT(11) NOT NULL,
    avatar VARCHAR(255) NOT NULL,
    phone VARCHAR(20) NOT NULL,
    email VARCHAR(255) NOT NULL
)";
if ($mysqli->query($createTableQuery) !== TRUE) {
    die("创建表失败: " . $mysqli->error);
}

// 填充数据
$insertQuery = "INSERT INTO user (name, gender, age, avatar, phone, email) VALUES (?, ?, ?, ?, ?, ?)";
$statement = $mysqli->prepare($insertQuery);

// 生成随机数据并插入表中
for ($i = 1; $i <= 100000; $i++) {
        $rand = mt_rand(100000000, 999999999);
    $name = "User " . $rand;
    $gender = ($rand % 2 == 0) ? "Male" : "Feml";
    $age = mt_rand(18, 60);
    $avatar = "avatar_" . $rand . ".jpg";
    $phone = "1234567890";
    $email = "user" . $rand . "@example.com";

    $statement->bind_param("ssisss", $name, $gender, $age, $avatar, $phone, $email);
    $statement->execute();
}

// 关闭连接
$statement->close();
$mysqli->close();

echo "数据库bench已创建并填充10万条数据。";

techempower 第三方压测数据

另外附上第三方压测数据,感觉他们压测的更专业
https://www.techempower.com/benchmarks/#section=data-r21&hw=ph&test=db&l=zijnjz-6bj&a=2&f=1ekg-cbcw-2t4w-27wr68-pc0-iv9slc-0-1ekgw-39g-kxs00-o0zk-5jsemh-2x8doc-2

一次数据库查询
截图

多次数据库查询
截图

数据库查询加更新
截图

数据库更新
截图

不带业务的helloworld
截图

其它说明

同时我测试了webman下使用原生PDO和使用orm的性能差别

如果使用原生PDO
webman QPS为1.78万

如果使用laravel的Db::table()
webman QPS降到 0.94万QPS

如果使用laravel的Model
webmanQPS降到 0.72万QPS

可见,orm库对性能影响很大,性能最多相差2倍多。
可能这就是为什么有人测试webman 高于 gin,有人测试webman低于gin,因为没有在同一个层面去压测。
webman 1.78W QPS 对比 go(gin)的0.80W QPS 是合理的对比。

不过不管怎么样,webman性能不输go的框架,甚至一些情况下比go的框架性能好很多。
当然性能不是唯一指标,简单、好用、稳定、社区活跃、性能好这几点要找到一个平衡。
至于框架间性能那点区别,加点硬件就完事了,早点下班它不香吗?

admin

试试go也和workerman一样使用事件库,而不是他内置的

package main

import (
    "fmt"
    "log"
    "net"
    "os"

    "golang.org/x/sys/unix"
)

const (
    EPOLLIN       = 0x001
    EPOLLOUT      = 0x004
    EPOLLET       = 0x80000000
    MaxConnections = 1024
    BufferSize    = 1024
)

func main() {
    listener, err := net.Listen("tcp4", ":8080")
    if err != nil {
        log.Fatal(err)
    }

    fd, err := syscall.EpollCreate1(0)
    if err != nil {
        log.Fatal(err)
    }

    events := make([]syscall.EpollEvent, MaxConnections)
    event := syscall.EpollEvent{
        Events: EPOLLIN | EPOLLET,
        Fd:     int32(listener.(*net.TCPListener).Fd()),
    }

    if err := syscall.EpollCtl(fd, syscall.EPOLL_CTL_ADD, int(listener.(*net.TCPListener).Fd()), &event); err != nil {
        log.Fatal(err)
    }

    buffer := make([]byte, BufferSize)
    for {
        nevents, err := syscall.EpollWait(fd, events, -1)
        if err != nil {
            log.Fatal(err)
        }

        for ev := 0; ev < nevents; ev++ {
            if int(events[ev].Fd) == int(listener.(*net.TCPListener).Fd()) {
                conn, err := listener.Accept()
                if err != nil {
                    log.Fatal(err)
                }

                fmt.Println("Accepted connection from", conn.RemoteAddr())

                event := syscall.EpollEvent{
                    Events: EPOLLIN | EPOLLOUT | EPOLLET,
                    Fd:     int32(conn.(*net.TCPConn).Fd()),
                }

                if err := syscall.EpollCtl(fd, syscall.EPOLL_CTL_ADD, int(conn.(*net.TCPConn).Fd()), &event); err != nil {
                    log.Fatal(err)
                }
            } else {
                conn, err := net.FileConn(os.NewFile(uintptr(events[ev].Fd), ""))
                if err != nil {
                    log.Fatal(err)
                }
                defer conn.Close()

                var n int
                n, err = conn.Read(buffer)
                if err != nil {
                    log.Fatal(err)
                }

                response := []byte("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 12\r\n\r\nHello World\n")
                _, err = conn.Write(response)
                if err != nil {
                    log.Fatal(err)
                }

                syscall.EpollCtl(fd, syscall.EPOLL_CTL_DEL, int(events[ev].Fd), nil)
            }
        }
    }
}
  • 暂无评论
年代过于久远,无法发表评论

songsong

220
积分
0
获赞数
0
粉丝数
2022-05-10 加入
×
🔝