Golang踩坑分析之-Gorm

55次阅读

共计 2335 个字符,预计需要花费 6 分钟才能阅读完成。

案例 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.

正文完
 0