看到嗶哩嗶哩有個視頻對比golang和webman helloword壓力測試性能,webman比goloang低了很多,低我理解,golang畢竟多線程自帶協(xié)程,而且webman是框架,golang是http標(biāo)準(zhǔn)庫,不在一個層次,但是感覺不會低這么多才對。還有我覺得應(yīng)該是golang的http標(biāo)準(zhǔn)庫和workerman對比才公平。所以我特地買了臺aliyun服務(wù)器測試下,順便把swoole workerman也壓測了下,結(jié)果也發(fā)到了群里,既然測試了就記錄下來給大家參考下,代碼都有大家可以自行測試。
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怎么開多進(jìn)程,為了公平起見我默認(rèn)workerman webman swoole 全部1個進(jìn)程,go就是教程里的helloword代碼,應(yīng)該也是一個進(jìn)程,是不是多線程我不知道。
沒開keepalive | 開了keepalive | |
---|---|---|
golang | 19995 | 98546 |
workerman | 30120 | 125986 |
webman | 29301 | 85938 |
swoole | 25836 | 73304 |
swoole+協(xié)程 | 27093 | 54596 |
結(jié)果是workeman壓測性能高于golang,webman短連接高于golang,keepalive長連接略低于golang。
swoole短連接高于golang,keepalive低于golang。workerman和webman不管是短連接還是keepliave都高于swoole。
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)
}
require_once __DIR__ . '/vendor/autoload.php';
use Workerman\Worker;
$worker = new Worker('http://0.0.0.0:12345');
$worker->onMessage = function($connection, $request)
{
// 不加-k參數(shù)時(shí)要用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時(shí)用send
$connection->send('hello');
};
Worker::runAll();
workerman這里不太好,ab測試時(shí)-k 參數(shù)需要自己區(qū)別處理,所以需要兩個腳本
<?php
namespace app\controller;
use support\Request;
class Index
{
public function index(Request $request)
{
return 'hello';
}
}
<?php
$http = new Swoole\Http\Server('0.0.0.0', 12346, SWOOLE_BASE);
$http->on('Request', function ($request, $response) {
$response->end('hello');
});
$http->start();
<?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();
});
?
?
?
?
?
?
?
?
?
?
我知道肯定會有人說helloword壓測沒有意義,但是我覺得還有一定意義的,畢竟代表了框架的極限性能啊。
https://learnku.com/laravel/t/63523
另外這里也有一個golang框架與webman的比較,帶簡單業(yè)務(wù)的,大家也可以參考的
群主:我這里只開一個進(jìn)程 helloworld 壓測QPS達(dá)到10萬左右,其它框架包括c寫的擴(kuò)展,只開一個進(jìn)程QPS最多6萬多。
Intel Core i7 處理器,php7.3。http://wtbis.cn/q/5328#reply_11278
這里我有必要說一下,PHP好在開發(fā)效率上,綜合下來,性能夠用、開發(fā)迅速;
golang的http庫更像php-fpm,fast-http在地位上有點(diǎn)類似workerman,golang的web開發(fā)框架里利用了http-fast且支持fork多進(jìn)程的框架fiber,還有個evio庫和gnet庫是類似于workerman但更低層,更像一個libevent、libuv、libev
比來比去沒用,用適合的語言干適合的事,高效完成任務(wù)工作,才是應(yīng)該干的事
你這個測試不具備實(shí)際的參考意義,真實(shí)的web應(yīng)用業(yè)務(wù)場景是io密集型的,io操作是影響性能的關(guān)鍵,我在真實(shí)業(yè)務(wù)的測試結(jié)果是golang(gin)>swoole(hyperf)>workerman(webman),不信你可以試試,在一個接口中查幾次數(shù)據(jù)庫,插入或修改數(shù)據(jù),然后寫入一條日志,協(xié)程在處理io機(jī)密性操作還是有優(yōu)勢的,阻塞了會自動切換,要不然swoole花這么多精力搞個協(xié)程有什么意義呢
協(xié)程不具備并發(fā)能力,協(xié)程只是調(diào)度,協(xié)程需要結(jié)合線程,swoole是單線程,golang是多線程;swoole和libco應(yīng)該有淵源關(guān)系;
另外您所謂的真實(shí)業(yè)務(wù)也需要建立在這幾次查詢數(shù)據(jù)庫和插入及修改是否是原子性、是否有關(guān)聯(lián)等前提下,順帶一提,gin+gorm的性能其實(shí)不如webman和hyperf;在重查詢的實(shí)際項(xiàng)目中g(shù)orm+gin的組合的結(jié)果大約是hyperf、webman的80%,在重更新的實(shí)際項(xiàng)目中大約是hyperf、webman的105%左右,前提是webman使用常駐單例的數(shù)據(jù)庫連接。
這個實(shí)際項(xiàng)目中還需要考慮是否有過重的json序列化需求,如果結(jié)合json序列化,golang所謂的性能及開發(fā)效率還要大打折扣
如果喜歡golang,我比較推薦一個基于fasthttp的框架fiber,這個框架包含了高性能orm及周邊插件;如果是涉及到游戲服務(wù)開發(fā),我推薦gnet網(wǎng)絡(luò)框架。
如果請求中有需要訪問第三方接口的情況,比如2s才返回結(jié)果,那么webman的性能應(yīng)該就不如自帶協(xié)程的golang了吧,不阻塞的情況下webman性能確實(shí)不錯
類似的,golang用的線程池,和worker用多進(jìn)程差不太多,線程更方便管理和彈性,但開多了一樣的,區(qū)別不大;實(shí)際上整個生命周期屬于阻塞隊(duì)列
chaz6chez 說的很對,golang在重業(yè)務(wù)下很糟糕,我們將php項(xiàng)目轉(zhuǎn)成了go,邏輯照搬,跑起來很差,fiber比gin要強(qiáng)很多,兩個框架我們都上了,最后放棄go了
@Tinywan 初步斷定為json的問題,起初用自帶的庫能干到CPU80%,換成sonic后好了很多,但還是不理想,還不如phpfpm, 其實(shí)qps500不算大,但是go表現(xiàn)真的不佳,不帶業(yè)務(wù)或者業(yè)務(wù)小確實(shí)很流弊。
沒有可比性,我用jmeter深度測試過workerman和springboot 純hello world 前者是快,原因在于 wm的request,session等等組件的封裝其實(shí)很原生,做的并不多。而主流的框架,對請求要進(jìn)行很多業(yè)務(wù)的封裝,比如依賴注入,響應(yīng)類型的自動處理,aop切面等等 一系列的業(yè)務(wù)。
我有個項(xiàng)目使用go開發(fā)的,目前正常使用中,在我本地調(diào)試 ,同樣的請求同樣的邏輯,go是10ms 而webman是25ms左右,我挺想把go改成webman的(已經(jīng)重構(gòu)10%),因?yàn)間o我不太熟悉,開發(fā)速度很慢,也受不了不停的err判斷,但是說句實(shí)在話,速度真的比webman 要快很多,webman已經(jīng)開啟了常駐內(nèi)存
都不使用orm的情況下使用sql查詢
webman(1.78萬QPS)>swoole(1.30萬QPS)>gin(0.80萬QPS)
結(jié)果與第三方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
進(jìn)程數(shù) cpu*4
,阻塞調(diào)用無連接池
swoole
進(jìn)程數(shù) 4,協(xié)程+連接池
go(gin)
多線程+協(xié)程+連接池
隨機(jī)從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() {
// 創(chuàng)建數(shù)據(jù)庫連接池
db, err := sql.Open("mysql", "root:P@ssword@tcp(localhost:3306)/bench?parseTime=true")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// 設(shè)置連接池參數(shù)
db.SetMaxOpenConns(100)
db.SetMaxIdleConns(10)
db.SetConnMaxIdleTime(20 * time.Second)
gin.SetMode(gin.ReleaseMode)
// 創(chuàng)建Gin路由
r := gin.New()
// 注冊接口
r.GET("/user", func(c *gin.Context) {
// 隨機(jī)生成id
id := rand.Intn(100000) + 1
// 查詢用戶數(shù)據(jù)
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": "查詢用戶數(shù)據(jù)失敗"})
return
}
// 構(gòu)造響應(yīng)數(shù)據(jù)
user := gin.H{
"id": id,
"name": name,
"gender": gender,
"age": age,
}
// 返回JSON響應(yīng)
c.JSON(http.StatusOK, user)
})
// 啟動HTTP服務(wù)器
err = r.Run(":8080")
if err != nil {
log.Fatal(err)
}
}
<?php
// 連接到MySQL數(shù)據(jù)庫
$mysqli = new mysqli("127.0.0.1", "root", "P@ssword");
// 檢查連接是否成功
if ($mysqli->connect_error) {
die("連接數(shù)據(jù)庫失敗: " . $mysqli->connect_error);
}
// 創(chuàng)建數(shù)據(jù)庫
$createDbQuery = "CREATE DATABASE IF NOT EXISTS bench";
if ($mysqli->query($createDbQuery) !== TRUE) {
die("創(chuàng)建數(shù)據(jù)庫失敗: " . $mysqli->error);
}
// 選擇數(shù)據(jù)庫
$mysqli->select_db("bench");
// 創(chuàng)建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("創(chuàng)建表失敗: " . $mysqli->error);
}
// 填充數(shù)據(jù)
$insertQuery = "INSERT INTO user (name, gender, age, avatar, phone, email) VALUES (?, ?, ?, ?, ?, ?)";
$statement = $mysqli->prepare($insertQuery);
// 生成隨機(jī)數(shù)據(jù)并插入表中
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();
}
// 關(guān)閉連接
$statement->close();
$mysqli->close();
echo "數(shù)據(jù)庫bench已創(chuàng)建并填充10萬條數(shù)據(jù)。";
另外附上第三方壓測數(shù)據(jù),感覺他們壓測的更專業(yè)
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
一次數(shù)據(jù)庫查詢
多次數(shù)據(jù)庫查詢
數(shù)據(jù)庫查詢加更新
數(shù)據(jù)庫更新
不帶業(yè)務(wù)的helloworld
同時(shí)我測試了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,因?yàn)闆]有在同一個層面去壓測。
webman 1.78W QPS 對比 go(gin)的0.80W QPS 是合理的對比。
不過不管怎么樣,webman性能不輸go的框架,甚至一些情況下比go的框架性能好很多。
當(dāng)然性能不是唯一指標(biāo),簡單、好用、穩(wěn)定、社區(qū)活躍、性能好這幾點(diǎn)要找到一個平衡。
至于框架間性能那點(diǎn)區(qū)別,加點(diǎn)硬件就完事了,早點(diǎn)下班它不香嗎?
試試go也和workerman一樣使用事件庫,而不是他內(nèi)置的
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)
}
}
}
}
支持一下