由於在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
<<: JS Library 学习笔记:Three.js 初见面,在2D画面创造三维世界 (四)
>>: Day 30 - 游艇网页专案完成後的优化方向 - ASP.NET Web Forms C#
推荐的Vim、VSCodeVim的参考资源 [系列文目录] 这篇文章推荐几个Vim与VSCodeVi...
此篇延续 Bootstrap 客制化 Sass utilities(上)最後尚未介绍的 gener...
关於讯息伫列怎麽去储存呢?大致分成下列两种: 1.系统池(system pool):如果能确定讯息伫...
今天一样来讲解一星的Fibonaccimal Base 附上程序码 import static ja...
前言 Vuex 并不会限制我们的代码结构,只是有三大原则需要遵守: 应用层级的状态应该集中到单个 s...