使用Echo或Gin框架的大型阵列的内存消耗

时间:2019-06-24 13:12:33

标签: json go memory-management slice httpserver

当我尝试使用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个请求:300Mo
  • 5个请求:1.5Go
  • 10个请求:3.1Go
  • 更多:我的电脑死机了:)

如何减少内存消耗?

2 个答案:

答案 0 :(得分:1)

分配的内存不会立即返回操作系统,请参见Cannot free memory once occupied by bytes.Buffer;和Freeing unused memory?

您的答案没有什么改善,因为您仍在内存中构建(Go)数组(或更确切地说是切片),一旦完成,就只能将其编组到响应中。您还为每个项目创建一个新的编码器,将一个项目编组,然后将其丢弃。您可以使用json.Encoder封送多个项目。您还需要在每个项目之后刷新响应,这也非常低效。这违反了所有内部缓冲的目的...

相反,您可以在项目(User)准备好后立即封送它们,因此您不必将所有内容都保留在内存中。并且不要在每个用户之后都刷新一次,这足以在最后执行一次,这是不必要的,因为从处理程序返回后,服务器将刷新所有缓冲的数据。

执行以下操作:

e.GET("/", func(c echo.Context) error {
    c.Response().WriteHeader(http.StatusOK)

    enc := json.NewEncoder(c.Response())
    for i := 0; i < 100000; i++ {
        user := User{
            Username:  "ffgfgfghhfghfhgfgfhgfghfghfhgfhgfh" + strconv.Itoa(i),
            Password:  "gjgjghjgjhgjhghjfrserhkhjhklljjkbhjvftxersgdghjjkhkljkbhfd",
            Lastname:  "njuftydfhgjkjlkjlkjlkhjkhu",
            Firstname: "jkggkjkl,,lm,kljkvgf",
        }
        if err := enc.Encode(user); err != nil {
            return err
        }
    }

    return nil
})

这里要注意的一件事:上面的代码没有将JSON数组发送到输出,而是发送了一系列JSON对象。如果这不适合您,并且您确实需要发送单个JSON数组,则只需对数据进行“框架化”,然后在项目之间插入逗号:

e.GET("/", func(c echo.Context) error {
    resp := c.Response()
    resp.WriteHeader(http.StatusOK)

    if _, err := io.WriteString(resp, "["); err != nil {
        return err
    }
    enc := json.NewEncoder(resp)
    for i := 0; i < 100000; i++ {
        if i > 0 {
            if _, err := io.WriteString(resp, ","); err != nil {
                return err
            }
        }
        user := User{
            Username:  "ffgfgfghhfghfhgfgfhgfghfghfhgfhgfh" + strconv.Itoa(i),
            Password:  "gjgjghjgjhgjhghjfrserhkhjhklljjkbhjvftxersgdghjjkhkljkbhft",
            Lastname:  "njuftydfhgjkjlkjlkjlkhjkhu",
            Firstname: "jkggkjkl,,lm,kljkvgf",
        }
        if err := enc.Encode(user); err != nil {
            return err
        }
    }
    if _, err := io.WriteString(resp, "]"); err != nil {
        return err
    }

    return nil
})

答案 1 :(得分:0)

我更改为使用@icza的解决方案。

代码在这里:

package main

import (
    "encoding/json"
    "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)
        }

        c.Response().WriteHeader(http.StatusOK)
        for _, user := range users {
            if err := json.NewEncoder(c.Response()).Encode(user); err != nil {
                return err
            }
            c.Response().Flush()
        }
        return nil
    })

    e.Logger.Fatal(e.Start(":1323"))
}

这样做,我的进步很小。我从3.5GB转到2GB。但是每次发送请求时,使用的内存都会增加。

为什么在请求处理后GC无法释放内存?