由於在POST /v1/users/
時我們會需要透過smtp寄出通知信再回response,
導致執行時間如下,有過長的傾向
ironman-2021 | [GIN-debug] redirecting request 307: /v1/users/ --> /v1/users/
ironman-2021 | [GIN] 2021/10/13 - 15:07:51 | 200 | 2.804489s | 192.168.176.1 | POST "/v1/users/"
我們可以發現加入smtp寄信功能後,response time竟然已經長達2.8秒了,也因此我們將加入Async來讓smtp寄信變成非同步任務,以此來降低Client等待的時間,提升使用者體驗。
其實非常的簡單,想要第一時間啟動goroutine
的話,其實只要在原本task function前面加上關鍵字go
就行了
// CreateUser @Summary
// @Tags user
// @version 1.0
// @produce application/json
// @param language header string true "language"
// @param register body Register true "register"
// @Success 200 string successful return value
// @Router /v1/users [post]
func (u UsersController) CreateUser(c *gin.Context) {
t := gi18n.New()
var form Register
bindErr := c.BindJSON(&form)
lan := c.Request.Header.Get("language")
if lan == "" {
lan = "en"
}
t.SetLanguage(lan)
if bindErr == nil {
err := service.RegisterOneUser(form.Account, form.Password, form.Email)
if err == nil {
go service.Send("Register Notification", "Welcome to become our membership", form.Email)
c.JSON(http.StatusOK, gin.H{
"status": 1,
"msg": t.Translate(c, "Response_Success"),
"data": nil,
})
} else {
c.JSON(http.StatusInternalServerError, gin.H{
"status": -1,
"msg": "Register Failed" + err.Error(),
"data": nil,
})
}
} else {
c.JSON(http.StatusBadRequest, gin.H{
"status": -1,
"msg": "Failed to parse register data" + bindErr.Error(),
"data": nil,
})
}
}
那完成後我們來測試一下response time吧!
ironman-2021 | [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
ironman-2021 |
ironman-2021 | [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
ironman-2021 | - using env: export GIN_MODE=release
ironman-2021 | - using code: gin.SetMode(gin.ReleaseMode)
ironman-2021 |
ironman-2021 | [GIN-debug] GET /hc --> main.SetRouter.func1 (5 handlers)
ironman-2021 | [GIN-debug] GET /crawler --> main.SetRouter.func2 (5 handlers)
ironman-2021 | [GIN-debug] POST /v1/users/ --> ironman-2021/app/controller.UsersController.CreateUser-fm (5 handlers)
ironman-2021 | [GIN-debug] GET /v1/users/:id --> ironman-2021/app/controller.UsersController.GetUser-fm (7 handlers)
ironman-2021 | [GIN-debug] POST /v1/users/login --> ironman-2021/app/controller.UsersController.AuthHandler-fm (5 handlers)
ironman-2021 | [GIN-debug] GET /swagger/*any --> github.com/swaggo/gin-swagger.CustomWrapHandler.func1 (5 handlers)
ironman-2021 | [GIN-debug] Listening and serving HTTP on :8080
ironman-2021 | [GIN-debug] redirecting request 307: /v1/users/ --> /v1/users/
ironman-2021 | [GIN] 2021/10/13 - 15:50:39 | 200 | 12.7741ms | 192.168.192.1 | POST "/v1/users/"
這邊我們可以發現response time從原本的2.8秒s 變成了 12.8ms了,這也證實我們的smtp寄信功能變成了個異步的任務!
但假設有多個異步任務呢? 如果我們除了通知信以外還要同時再寄出一份我們的規則表給使用者呢? 我們要如何確保每次都有將信件都寄出並且異步處理呢?
此時之前說過的sync.WaitGroup
就能登場了
app/service/smtp.go
package service
import (
"github.com/joho/godotenv"
"github.com/sirupsen/logrus"
"ironman-2021/app/middleware"
"net/smtp"
"os"
"sync"
)
var wg sync.WaitGroup
func Send(title string, body string, to string) {
envErr := godotenv.Load()
if envErr != nil {
panic(envErr)
}
from := os.Getenv("MAIL_USERNAME")
pass := os.Getenv("MAIL_PASSWORD")
port := os.Getenv("MAIL_PORT")
server := os.Getenv("MAIL_SERVER")
msg := "From: " + from + "\n" +
"To: " + to + "\n" +
"Subject: " + title + "\n" +
body
err := smtp.SendMail(server+":"+port,
smtp.PlainAuth("", from, pass, server),
from, []string{to}, []byte(msg))
if err != nil {
middleware.Logger().WithFields(logrus.Fields{
"name": "Smtp",
}).Error("error: ", err)
}
middleware.Logger().WithFields(logrus.Fields{
"name": "Smtp",
}).Info("Send from: ", from+", To: ", to)
wg.Done()
}
func MultiSend(email string) {
wg.Add(2)
go Send("Register Notification", "Welcome to become our membership", email)
go Send("Please review the rules", "Rules1:..........", email)
wg.Wait()
middleware.Logger().WithFields(logrus.Fields{
"name": "Smtp",
}).Info("Finished all tasks")
}
MultiSend()
來讓API觸發MultiSend()
裡頭透過WaitGroup
來等待兩個goroutine tasks都確定完成才寫Log/app/controller/user.go
最後一樣在觸發處使用關鍵字go
來呼叫MultiSend()
// CreateUser @Summary
// @Tags user
// @version 1.0
// @produce application/json
// @param language header string true "language"
// @param register body Register true "register"
// @Success 200 string successful return value
// @Router /v1/users [post]
func (u UsersController) CreateUser(c *gin.Context) {
t := gi18n.New()
var form Register
bindErr := c.BindJSON(&form)
lan := c.Request.Header.Get("language")
if lan == "" {
lan = "en"
}
t.SetLanguage(lan)
if bindErr == nil {
err := service.RegisterOneUser(form.Account, form.Password, form.Email)
if err == nil {
go service.MultiSend(form.Email)
c.JSON(http.StatusOK, gin.H{
"status": 1,
"msg": t.Translate(c, "Response_Success"),
"data": nil,
})
} else {
c.JSON(http.StatusInternalServerError, gin.H{
"status": -1,
"msg": "Register Failed" + err.Error(),
"data": nil,
})
}
} else {
c.JSON(http.StatusBadRequest, gin.H{
"status": -1,
"msg": "Failed to parse register data" + bindErr.Error(),
"data": nil,
})
}
}
這章節我們成功的結合goroutine來讓Gin執行異步任務執行,也確實地改善了使用者體驗。
這次的程式碼我也會放在下方連結提供參考!
https://github.com/Neskem/Ironman-2021/tree/Day-29