Go GC 调优

发现生产环境 Go 的 GC 次数特别频繁,平均每分钟 12 次左右,偶尔来个流量尖刺,每分钟 GC 次数超过了 20 次,同时发现,服务器内存是 1G,但是实际只使用了 0.05G,只使用了 5%,这就非常不合理了。

先看一下优化后的结果,GC 次数从原来的 12 次减少到 2 次,内存占用从原来的 0.05G 增加到 0.2G 左右,GC 次数减少,内存占用增加,这是符合预期的

GC次数
内存使用

主要是通过 2 个参数来进行优化:SetGCPercentSetMemoryLimit

1、SetGCPercent

比较常见的是通过调整 GC 的步调,以调整 GC 的触发频率。可以通过设置 GOGC 或者在代码中设置 debug.SetGCPercent() 来达到,效果是相同的。

这个参数控制的是触发 GC 的阈值,是一个百分比。当 Go 新创建对象占用内存大小,除以,上次 GC 结束后保留下来的对象占用内存大小,所得到的比值大于设置的阈值时,就会触发 GC,默认值是100。也就是说,默认情况下,当目前占用内存是上次 GC 结束后占用内存的一倍时触发 GC。

举个例子,假设上次 GC 后,驻留内存是 100MB,若该值设置的是 100,那么下次内存达到 200MB 时候就会触发 GC,若设置 200,那么下次内存达到 300MB 的时候就会触发 GC。

下面通过代码来说明。消费者从队列中读取序列化的数据,将其反序列化到一个临时的中间数据结构中,将变形后的数据写入map中保存。

package main

import (
    "encoding/json"
    "fmt"
    "runtime"
    "sync"
)

// 用于解码数据的临时结构
type QMessage struct {
    ID   uint64       `json:"id,omitempty"`
    Body QMessageBody `json:"body,omitempty"`
}
type QMessageBody struct {
    Field1 string `json:"field_1,omitempty"`
    Field2 int    `json:"field_2,omitempty"`
}

// 常驻于内存的数据结构
type Message struct {
    id     uint64
    field1 string
    field2 int
}

// 内存数据缓存,里面存放了千万级的词条信息
var buffer = make(map[int]Message)

func main() {
    wg := &sync.WaitGroup{}
    q := make(chan string)
    wg.Add(2)
    go producer(q, wg)
    go consumer(q, wg)
    wg.Wait()
    PrintMemUsage()
}

// 模拟生产者,产生两千万词条数据
func producer(q chan string, wg *sync.WaitGroup) {
    for i := 0; i < 20000000; i++ {
        q <- `{"id":123456, "body":{"field1": "123", "field2": 456}}`
    }
    close(q)
    wg.Done()
}

// 模拟消费者,消费并反序列化词条数据,使用中间临时数据结构进行数据变形,并将最后的结果存储
func consumer(q chan string, wg *sync.WaitGroup) {
    idx := 0
    for data := range q {
        idx++
        qtmp := QMessage{}
        json.Unmarshal([]byte(data), &qtmp)

        tmp := Message{
            id:     qtmp.ID,
            field1: qtmp.Body.Field1,
            field2: qtmp.Body.Field2,
        }
        buffer[idx] = tmp
    }
    wg.Done()
}

// 以下是打印内存监控数据的工具函数,与业务逻辑无关
func PrintMemUsage() {
    var m runtime.MemStats
    runtime.ReadMemStats(&m)
    // For info on each, see: https://golang.org/pkg/runtime/#MemStats
    fmt.Printf("Alloc = %v MiB", bToMb(m.Alloc))
    fmt.Printf("\tTotalAlloc = %v MiB", bToMb(m.TotalAlloc))
    fmt.Printf("\tSys = %v MiB", bToMb(m.Sys))
    fmt.Printf("\tNumGC = %v", m.NumGC)
    fmt.Printf("\tSTW = %v\n", m.PauseTotalNs / 1000)
}

func bToMb(b uint64) uint64 {
    return b / 1024 / 1024
}

把打印结果转为表格:

GOGC Alloc (MiB) TotalAlloc (MiB) Sys (MiB) NumGC STW
-1 9318 9318 9598 0 0
12 1508 9317 2899 251 11320
25 1537 9318 2909 131 5980
50 1655 9318 3397 66 3242
100 4237 9318 4348 31 1503
200 3987 9318 4808 17 709

可以看出当把 GOGC 的值分别设置为 12、25、50、100、200 时,占用的内存逐步增加,GC 次数逐步减少,STW 时间也逐步减少。

很好理解,因为 GC 频次减少了,很多对象回收推迟了,占用内存就增多了,优点是 GC 次数和 STW 时间都减少了,以空间换时间。

从上面可以看出,SetGCPercent 控制着GC的运行频率。当 SetGCPercent 值设置的较小时,GC 运行就频繁一些;当 SetGCPercent 的值设置的较大时,GC运行就不那么频繁,但要承担内存分配接近资源上限的风险。

2、SetMemoryLimit

为了解决内存分配超过资源上限的风险,需要配合使用另一个参数 SetMemoryLimit

一旦设定了 SetMemoryLimit,当 Go 堆大小达到 MemoryLimit 减去非堆内存后的值时,GC 就会被触发,即使手动关闭了GC(GOGC=off),也会被触发。这个特性最直接解决的就是 oom-killed 这个问题。

但如果 Go 应用的 live heap object 超过了 soft memory limit 但还尚未被 kill,那么此时 GC 会被持续触发,但为了保证在这种情况下业务依然能继续进行,GC 最多只会使用 50% 的 CPU,以保证业务处理依然能够得到 CPU 资源。

3、总结

本文介绍了通过 SetGCPercentSetMemoryLimit 对 Go 应用 GC 调优:

  1. SetGCPercent 设置的是 GC 触发的百分比,而不是一个具体值;
  2. SetMemoryLimit 设置了 soft memory limit 的最大值,达到该值时会触发 GC;
  3. 若同时设置这俩值,即使 soft memory 没有达到 MemoryLimit,也有可能触发 GC,因为在没达到 MemoryLimit 阈值的情况下,还是遵循 GOGC 决定要不要进行垃圾回收。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容

  • 1、北京预计25日夜间至26日有小到中雪,随后气温骤降并伴有大风。 北京市气象台预报显示,1月24日天气晴朗,气温...
    cnxhsy阅读 18评论 0 0
  • 每次出门在外总少不了朋友的帮衬,我又是个很怕麻烦别人的人,每每接受好友的深意,总觉温暖与感动。独自驾车行驶在悠长的...
    super7777777阅读 11评论 0 0
  • 名师爱读书 案例一: 华应龙透露,从中师毕业后,他到一所农村小学教体育,数学只是他的“副业”。他也坦言,做了两三年...
    汐雨桐花阅读 30评论 0 1
  • 腊月二十五,周五。 万宁天气,雾,18~26度,空气指数38。 家里的天气,雾霾,2~9度。空气指数117。 昨晚...
    竹影起舞正听风阅读 60评论 0 5
  • 1、 [endif]网页布局的本质 用CSS来摆放盒子,把盒子摆放到相应的位置 2、传统网页布局的三种方式 普通流...
    许文雅阅读 13评论 0 0