介绍
用 gin 和 fastapi 分别写了一个发送短信的接口,调用 UCloud API 的过程用time.Sleep
进行模拟。
为保证公平,接口中的操作尽可能保持了一致。
程序运行在一个 CPU 核数为 1 的主机上。
本地使用 jmeter 进行压测,线程数 1000,测试两分钟。
代码
gin:
package main
import (
"github.com/gin-gonic/gin"
"github.com/ucloud/ucloud-sdk-go/services/usms"
"github.com/ucloud/ucloud-sdk-go/ucloud"
"github.com/ucloud/ucloud-sdk-go/ucloud/auth"
"github.com/ucloud/ucloud-sdk-go/ucloud/config"
"github.com/ucloud/ucloud-sdk-go/ucloud/log"
"net/http"
"time"
)
type SmsSendForm struct {
PhoneNumbers []string `json:"phone_numbers"`
Content string `json:"content"`
}
var (
USMSClient *usms.USMSClient
)
func InitUCloudSdk() {
cfg := config.NewConfig()
cfg.LogLevel = log.InfoLevel
cfg.ProjectId = "ProjectId"
credential := auth.NewCredential()
credential.PublicKey = "PublicKey"
credential.PrivateKey = "PrivateKey"
USMSClient = usms.NewClient(&cfg, &credential)
}
func SendSms(c *gin.Context) {
smsSendForm := SmsSendForm{}
if err := c.BindJSON(&smsSendForm); err != nil {
c.JSON(http.StatusOK, gin.H{
"code": -2,
"message": "请求参数错误",
})
return
}
req := USMSClient.NewSendUSMSMessageRequest()
req.SigContent = ucloud.String("SigContent")
req.TemplateId = ucloud.String("TemplateId")
req.PhoneNumbers = smsSendForm.PhoneNumbers
req.TemplateParams = []string{smsSendForm.Content}
time.Sleep(time.Nanosecond * 33245898) // 模拟调用 UCloud API 的过程
c.JSON(http.StatusOK, gin.H{
"code": 12345,
"message": "测试",
})
}
func main() {
InitUCloudSdk()
r := gin.Default()
r.POST("/sms", SendSms)
_ = r.Run(":8080")
}
fastapi:
import time
from typing import List
from fastapi import FastAPI
from ucloud.core import exc
from ucloud.client import Client
from pydantic import BaseModel
client = Client({
"public_key": "public_key",
"private_key": "private_key",
"project_id": "project_id",
})
app = FastAPI()
class SmsSendForm(BaseModel):
phone_numbers: List[str]
content: str
@app.post("/sms")
def send_sms(form: SmsSendForm):
try:
_ = client.usms()
time.sleep(0.033245898)
except exc.UCloudException as e:
print(e)
return {
"code": 12345,
"message": "测试",
}
测试结果
gin:
fastapi + uvicorn(1 worker):
启动命令:uvicorn main:app --host 0.0.0.0 --port 8080
结果:图丢了,只记得 Transactions/s 是 七百多。
fastapi + uvicorn(4 workers):
启动命令:uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4
结果:Transactions/s 为 八百多
fastapi + uvicorn(8 workers):
启动命令:uvicorn main:app --host 0.0.0.0 --port 8080 --workers 8
结果:Transactions/s 为 九百多
可以看出 gin 的 TPS 差不多是 fastapi 的 10 倍。
进一步测试 fastapi
为了进一步排除其他可能,将 fastapi 接口进行简化:
from fastapi import FastAPI
app = FastAPI()
@app.post("/sms")
def send_sms():
return {
"code": 12345,
"message": "测试",
}
fastapi + uvicorn(8 workers)运行,测试结果:
从这儿可以看出 fastapi 和 gin 的差距应该主要是并发量,因为接口内已经没有任何代码了,fastapi 的 Transactions/s 仍然没有提升多少。
总结
本次测试,只使用了 uvicorn 来运行 fastapi,uvicorn 只能设置线程数(上面写到的 worker),而没有启动多个进程的能力。因此,如果本次使用的主机是多核的,那么结果不可信,因为 Python 单进程无法利用多核,不过本次使用的主机是单核的,因此应该没有这方面的问题。
虽然单核环境下 Python 单进程没毛病,但启动多个进程性能或许还是会稍稍高一点。为了拥有多进程的能力,可以加一个 gunicorn,即 fastapi + uvicorn + gunicorn,使用 gunicorn 启动多进程,也许可以将并发能力再提高一点。当然,如果是多核环境,这个 gunicorn 是一定要加的,否则无法利用多核。
网上说 fastapi 性能媲美 nodejs,直追 go,但经过本次测试发现性能和 go 差距甚远,不知是网传错误,还是我本次测试方式错误。如果是测试方式错误,等以后有足够的知识面,知道怎么测之后再回来重测。
测试是要分场景的,本次测试只是对最简单的场景进行了简单的测试,不能一刀切说 go 的性能是 fastapi 的 10 倍,不同场景下测试结果是完全不同的,不能一概而论。