【发布时间】:2019-11-06 07:22:39
【问题描述】:
当我尝试使用 Echo(以及 Gin)发送一个大型数组时出现内存问题。 请求后,内存不是空闲的。
package main
import (
"net/http"
"strconv"
"github.com/labstack/echo"
)
type User struct {
Username string
Password string
Lastname string
Firstname string
}
func main() {
e := echo.New()
e.GET("/", func(c echo.Context) error {
var user User
users := make([]User, 0)
for i := 0; i < 100000; i++ {
user = User{
Username: "ffgfgfghhfghfhgfgfhgfghfghfhgfhgfh" + strconv.Itoa(i),
Password: "gjgjghjgjhgjhghjfrserhkhjhklljjkbhjvftxersgdghjjkhkljkbhftd",
Lastname: "njuftydfhgjkjlkjlkjlkhjkhu",
Firstname: "jkggkjkl,,lm,kljkvgf"}
users = append(users, user)
}
defer func() {
users = nil
}()
return c.JSON(http.StatusOK, users)
})
e.Logger.Fatal(e.Start(":1323"))
}
为了测试,我并行运行请求,我得到了这些结果:
- 1 个请求:300 个月
- 5 个请求:1.5Go
- 10 个请求:3.1Go
- 更多:我的电脑死机了 :)
如何减少内存消耗?
编辑
如果我不必处理数据,它会很好地工作。
例如,如果我从数据库中获得 100,000 行。
然后我需要处理它们以返回具有多个级别的 JSON。在这种情况下,我必须创建一个数组或映射。
但问题是内存永远不会被释放。并且随着每个请求的增加而增加,在并行请求的情况下更糟。
这里是一个例子:
import (
"strconv"
"time"
)
type sqlDataType struct {
ApplicationID int
ApplicationName string
ApplicationCreatedAt time.Time
ApplicationUpdatedAt time.Time
ModuleID int
ModuleName string
ModuleCreatedAt time.Time
ModuleUpdatedAt time.Time
ActionID int
ActionName string
ActionCreatedAt time.Time
ActionUpdatedAt time.Time
}
type DataApplicationType struct {
Name string
CreatedAt time.Time
UpdatedAt time.Time
Modules map[int]dataModuleType
}
type dataModuleType struct {
Name string
CreatedAt time.Time
UpdatedAt time.Time
Actions map[int]dataActionType
}
type dataActionType struct {
Name string
CreatedAt time.Time
UpdatedAt time.Time
}
// InitData inits data for test
func InitData() map[int]DataApplicationType {
data := make(map[int]DataApplicationType)
const nbApplications = 10
const nbModules = 1000
const nbActions = 100000
sqlData := make([]sqlDataType, 0)
for i := 0; i < nbActions; i++ {
line := sqlDataType{
ApplicationID: (i % nbApplications) + 1,
ApplicationName: "Application " + strconv.Itoa((i%nbApplications)+1),
ApplicationCreatedAt: time.Now(),
ApplicationUpdatedAt: time.Now(),
ModuleID: (i % nbModules) + 1,
ModuleName: "Module " + strconv.Itoa((i%nbModules)+1),
ModuleCreatedAt: time.Now(),
ModuleUpdatedAt: time.Now(),
ActionID: i + 1,
ActionName: "Action " + strconv.Itoa(i+1),
ActionCreatedAt: time.Now(),
ActionUpdatedAt: time.Now(),
}
sqlData = append(sqlData, line)
}
nbData := len(sqlData)
for i := 0; i < nbData; i++ {
if _, ok := data[sqlData[i].ApplicationID]; !ok {
dac := new(dataActionType)
dac.Name = sqlData[i].ActionName
dac.CreatedAt = sqlData[i].ActionCreatedAt
dac.UpdatedAt = sqlData[i].ActionUpdatedAt
dmo := new(dataModuleType)
dmo.Name = sqlData[i].ModuleName
dmo.CreatedAt = sqlData[i].ModuleCreatedAt
dmo.UpdatedAt = sqlData[i].ModuleUpdatedAt
dmo.Actions = make(map[int]dataActionType)
dmo.Actions[sqlData[i].ActionID] = *dac
dap := new(DataApplicationType)
dap.Name = sqlData[i].ApplicationName
dap.CreatedAt = sqlData[i].ApplicationCreatedAt
dap.UpdatedAt = sqlData[i].ApplicationUpdatedAt
dap.Modules = make(map[int]dataModuleType)
dap.Modules[sqlData[i].ModuleID] = *dmo
data[sqlData[i].ApplicationID] = *dap
}
if _, ok := data[sqlData[i].ApplicationID].Modules[sqlData[i].ModuleID]; !ok {
dac := new(dataActionType)
dac.Name = sqlData[i].ActionName
dac.CreatedAt = sqlData[i].ActionCreatedAt
dac.UpdatedAt = sqlData[i].ActionUpdatedAt
dmo := new(dataModuleType)
dmo.Name = sqlData[i].ModuleName
dmo.CreatedAt = sqlData[i].ModuleCreatedAt
dmo.UpdatedAt = sqlData[i].ModuleUpdatedAt
dmo.Actions = make(map[int]dataActionType)
dmo.Actions[sqlData[i].ActionID] = *dac
data[sqlData[i].ApplicationID].Modules[sqlData[i].ModuleID] = *dmo
}
if _, ok := data[sqlData[i].ApplicationID].Modules[sqlData[i].ModuleID].Actions[sqlData[i].ActionID]; !ok {
dac := new(dataActionType)
dac.Name = sqlData[i].ActionName
dac.CreatedAt = sqlData[i].ActionCreatedAt
dac.UpdatedAt = sqlData[i].ActionUpdatedAt
data[sqlData[i].ApplicationID].Modules[sqlData[i].ModuleID].Actions[sqlData[i].ActionID] = *dac
}
}
return data
}
在 main.go 中:
func main() {
// Lancement de Cobra
// commands.Execute()
go issues.InitData()
go issues.InitData()
go issues.InitData()
go issues.InitData()
go issues.InitData()
time.Sleep(60 * time.Second)
}
此脚本需要大约 500 Mo 内存并且不会释放它,而地图甚至还没有转换为 JSON。
如何减少内存消耗和/或在多次调用时保持稳定的内存消耗状态?
感谢您的帮助
【问题讨论】:
-
不要在内存中构建一个包含十万个对象的 JSON 数组。使用
json.NewEncoder(),并将结果流式传输到HTTP 响应(请参阅example)。有时自己做一些事情而不依赖于框架会很有帮助。 -
您为什么希望这段代码不会使用大量内存?您可以通过 append 为 0-len 切片创建 1e5 个元素的切片。每次您的后备阵列需要增加您的副本时。
users = nil只是蛇油。只是不要使用内存。从 icza 的建议开始。 -
@icza 非常感谢,我会试试你的解决方案。
标签: json go memory-management slice httpserver