Golang踩坑分析之 Gorm

案例1:Golang Gorm踩坑

## 影響情況##
服務A 是定時調度系統,利用github.com/robfig/cron 來實現,發現服務運行3小時左右就不打印sql 日誌,但是API 服務響應正常. 導致調度服務不執行、

##分析思路##
Golang 編寫調度的服務遇到不調度的情況如何分析處理那?首先我們利用golang 自帶的pprof來分析。在main.go中 增加`
    go func() {
        if err := http.ListenAndServe("0.0.0.0:12345", nil); err != nil {
            log.Println(err)
        }
    }()`

然後再結合火焰圖 去分析。可惜這次,pprof 並沒有幫上我們的忙,因爲調度服務很輕量,內存方面沒超過1MB,profile cpu等也很正常、那麼接下來怎麼辦?

由於是服務調度每次都會打印sql,那麼我們就去查看mysql 的資源吧、
首先 通過show full process list; 如下

mysql> show full processlist;
+---------+-----------+------------------+---------+---------+------+----------+-----------------------+-----------+---------------+
| Id      | User      | Host             | db      | Command | Time | State    | Info                  | Rows_sent | Rows_examined |
+---------+-----------+------------------+---------+---------+------+----------+-----------------------+-----------+---------------+
| 2855999 | A | *:52898 | my | Sleep   |   37 |          | NULL                  |         0 |             0 |
| 2880711 | A | *:26280 | my | Sleep   |   10 |          | NULL                  |         0 |             0 |
| 2904663 | A | *:50519 | my | Sleep   | 2544 |          | NULL                  |         0 |             0 |
| 2905293 | A | *:6492  | my | Sleep   |  567 |          | NULL                  |         0 |             0 |
| 2905341 | A | *:2777  | my | Query   |    0 | starting | show full processlist |         0 |             0 |
| 2905425 | A | *:4081  | my | Sleep   |   57 |          | NULL                  |         0 |             0 |
| 2905444 | A | *:15888 | my | Sleep   |   92 |          | NULL                  |         0 |             0 |
| 2905523 | A | *:13524 | my | Sleep   |   10 |          | NULL                  |         0 |             0 |
+---------+-----------+------------------+---------+---------+------+----------+-----------------------+-----------+———————+

測試環境服務 發現會有大量的sleep,那麼我們再看下my.conf 配置。一般最大連接數就設置200左右。所所以1分鐘調度一次,200分鐘我們的服務就被把數據庫連接數佔滿。那麼接下來去分析下源碼。

//以下是調度的問題代碼
for rowsInner.Next() {
            dataMap[supportInt] = true
            break
}
我們發現 直接做了break、但是爲什麼會導致連接被佔用 不能釋放那?
func (rs *Rows) Next() bool {
    var doClose, ok bool
    withLock(rs.closemu.RLocker(), func() {
        doClose, ok = rs.nextLocked()
    })
    if doClose {
        rs.Close()
    }
    return ok
}


func (rs *Rows) Scan(dest ...interface{}) error {
    rs.closemu.RLock()

    if rs.lasterr != nil && rs.lasterr != io.EOF {
        rs.closemu.RUnlock()
        return rs.lasterr
    }
    if rs.closed {
        err := rs.lasterrOrErrLocked(errRowsClosed)
        rs.closemu.RUnlock()
        return err
    }
    rs.closemu.RUnlock()

    if rs.lastcols == nil {
        return errors.New("sql: Scan called without calling Next")
    }
    if len(dest) != len(rs.lastcols) {
        return fmt.Errorf("sql: expected %d destination arguments in Scan, not %d", len(rs.lastcols), len(dest))
    }
    for i, sv := range rs.lastcols {
        err := convertAssignRows(dest[i], sv, rs)
        if err != nil {
            return fmt.Errorf(`sql: Scan error on column index %d, name %q: %v`, i, rs.rowsi.Columns()[i], err)
        }
    }
    return nil
}

我們通過源碼可以發現,Next()方法判斷下一個元素有沒有,但是我們沒有用scan,因爲scan 每次返回都會釋放掉一行數據庫的緩存,所以 我們 手動rows.Close(),這種情況也很難遇到,因爲一般情況下,我們都是 scan到底,最後底層代碼會 執行 d.close(rows.lasterr)、所以我們一般不需要手動close rows.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章