介绍

用 gin 和 fastapi 分别写了一个发送短信的接口,调用 UCloud API 的过程用time.Sleep进行模拟。
为保证公平,接口中的操作尽可能保持了一致。
程序运行在一个 CPU 核数为 1 的主机上。
本地使用 jmeter 进行压测,线程数 1000,测试两分钟。

代码

gin:

  1. package main
  2. import (
  3. "github.com/gin-gonic/gin"
  4. "github.com/ucloud/ucloud-sdk-go/services/usms"
  5. "github.com/ucloud/ucloud-sdk-go/ucloud"
  6. "github.com/ucloud/ucloud-sdk-go/ucloud/auth"
  7. "github.com/ucloud/ucloud-sdk-go/ucloud/config"
  8. "github.com/ucloud/ucloud-sdk-go/ucloud/log"
  9. "net/http"
  10. "time"
  11. )
  12. type SmsSendForm struct {
  13. PhoneNumbers []string `json:"phone_numbers"`
  14. Content string `json:"content"`
  15. }
  16. var (
  17. USMSClient *usms.USMSClient
  18. )
  19. func InitUCloudSdk() {
  20. cfg := config.NewConfig()
  21. cfg.LogLevel = log.InfoLevel
  22. cfg.ProjectId = "ProjectId"
  23. credential := auth.NewCredential()
  24. credential.PublicKey = "PublicKey"
  25. credential.PrivateKey = "PrivateKey"
  26. USMSClient = usms.NewClient(&cfg, &credential)
  27. }
  28. func SendSms(c *gin.Context) {
  29. smsSendForm := SmsSendForm{}
  30. if err := c.BindJSON(&smsSendForm); err != nil {
  31. c.JSON(http.StatusOK, gin.H{
  32. "code": -2,
  33. "message": "请求参数错误",
  34. })
  35. return
  36. }
  37. req := USMSClient.NewSendUSMSMessageRequest()
  38. req.SigContent = ucloud.String("SigContent")
  39. req.TemplateId = ucloud.String("TemplateId")
  40. req.PhoneNumbers = smsSendForm.PhoneNumbers
  41. req.TemplateParams = []string{smsSendForm.Content}
  42. time.Sleep(time.Nanosecond * 33245898) // 模拟调用 UCloud API 的过程
  43. c.JSON(http.StatusOK, gin.H{
  44. "code": 12345,
  45. "message": "测试",
  46. })
  47. }
  48. func main() {
  49. InitUCloudSdk()
  50. r := gin.Default()
  51. r.POST("/sms", SendSms)
  52. _ = r.Run(":8080")
  53. }

fastapi:

  1. import time
  2. from typing import List
  3. from fastapi import FastAPI
  4. from ucloud.core import exc
  5. from ucloud.client import Client
  6. from pydantic import BaseModel
  7. client = Client({
  8. "public_key": "public_key",
  9. "private_key": "private_key",
  10. "project_id": "project_id",
  11. })
  12. app = FastAPI()
  13. class SmsSendForm(BaseModel):
  14. phone_numbers: List[str]
  15. content: str
  16. @app.post("/sms")
  17. def send_sms(form: SmsSendForm):
  18. try:
  19. _ = client.usms()
  20. time.sleep(0.033245898)
  21. except exc.UCloudException as e:
  22. print(e)
  23. return {
  24. "code": 12345,
  25. "message": "测试",
  26. }

测试结果

gin:
image.png

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 接口进行简化:

  1. from fastapi import FastAPI
  2. app = FastAPI()
  3. @app.post("/sms")
  4. def send_sms():
  5. return {
  6. "code": 12345,
  7. "message": "测试",
  8. }

fastapi + uvicorn(8 workers)运行,测试结果:
image.png

从这儿可以看出 fastapi 和 gin 的差距应该主要是并发量,因为接口内已经没有任何代码了,fastapi 的 Transactions/s 仍然没有提升多少。

总结

  1. 本次测试,只使用了 uvicorn 来运行 fastapi,uvicorn 只能设置线程数(上面写到的 worker),而没有启动多个进程的能力。因此,如果本次使用的主机是多核的,那么结果不可信,因为 Python 单进程无法利用多核,不过本次使用的主机是单核的,因此应该没有这方面的问题。

  2. 虽然单核环境下 Python 单进程没毛病,但启动多个进程性能或许还是会稍稍高一点。为了拥有多进程的能力,可以加一个 gunicorn,即 fastapi + uvicorn + gunicorn,使用 gunicorn 启动多进程,也许可以将并发能力再提高一点。当然,如果是多核环境,这个 gunicorn 是一定要加的,否则无法利用多核。

  3. 网上说 fastapi 性能媲美 nodejs,直追 go,但经过本次测试发现性能和 go 差距甚远,不知是网传错误,还是我本次测试方式错误。如果是测试方式错误,等以后有足够的知识面,知道怎么测之后再回来重测。

  4. 测试是要分场景的,本次测试只是对最简单的场景进行了简单的测试,不能一刀切说 go 的性能是 fastapi 的 10 倍,不同场景下测试结果是完全不同的,不能一概而论。