美文网首页GOLANG后端架构Go语言
database/sql一个bug引发的事故

database/sql一个bug引发的事故

作者: imnx | 来源:发表于2017-01-02 13:00 被阅读124次

    某次做checklist,当看到机器的监控时,发现下午某时刻某台机器突发大量CLOSE_WAIT。一下子就怀疑是服务端处理超时了,client主动断开连接造成的。然后定位到某个服务下午上线,由于db大量阻塞导致的问题。数据库不是有超时么,连接和读写都有啊??

    其实这个问题网上也有很多网友反馈,只不过我们这次切切实实的踩到坑了。go1.8也基本修复了这个问题。下面来看看到底是怎么回事。我们看database/sql这个包就行了。

    先看1.7 怎么获取连接的:
    // Out of free connections or we were asked not to use one. If we're not // allowed to open any more connections, make a request and wait. if db.maxOpen > 0 && db.numOpen >= db.maxOpen { // Make the connRequest channel. It's buffered so that the // connectionOpener doesn't block while waiting for the req to be read. req := make(chan connRequest, 1) db.connRequests = append(db.connRequests, req) db.mu.Unlock() ret, ok := <-req if !ok { return nil, errDBClosed } if ret.err == nil && ret.conn.expired(lifetime) { ret.conn.Close() return nil, driver.ErrBadConn } return ret.conn, ret.err }
    在看1.8 获取连接的实现:
    // Out of free connections or we were asked not to use one. If we're not // allowed to open any more connections, make a request and wait. if db.maxOpen > 0 && db.numOpen >= db.maxOpen { // Make the connRequest channel. It's buffered so that the // connectionOpener doesn't block while waiting for the req to be read. req := make(chan connRequest, 1) db.connRequests = append(db.connRequests, req) db.mu.Unlock() · // Timeout the connection request with the context. select { case <-ctx.Done(): return nil, ctx.Err() case ret, ok := <-req: if !ok { return nil, errDBClosed } if ret.err == nil && ret.conn.expired(lifetime) { ret.conn.Close() return nil, driver.ErrBadConn } return ret.conn, ret.err } }
    很明显看到在1.7里,一旦connRequest的“消费者”过多,肯定有很多goroutine会一直等待的,no timeout,no cancel。connRequest的实现也是很有趣的,从命名上看是个conn的request,等待响应和拿到conn。connRequests是个chan的数组,而chan里面塞了conn。当某goroutine消费完conn后,变成生产者(参考putConnDBLocked方法),从而既能实现goroutine间的通知,同时又能直接传递数据库连接。但是问题来了,channel本身是阻塞的,也不支持超时。一旦db出现慢查询啥的,很可能就会导致大量数据库请求阻塞,蛋疼。
    golang的1.8很大程度上支持了context,从sql这个包里也能发现,新的大量的xxxContext函数出现了。连接啊、执行sql啊、事务啊。context.WithTimeout()直接就搞定超时功能了,烦恼不再。不但是database,在1.7里net/http也早已大量支持了context。看来golang本身对context越来越倚重了,相信以后会看到越来越多的func(ctx context...)。

    相关文章

      网友评论

        本文标题:database/sql一个bug引发的事故

        本文链接:https://www.haomeiwen.com/subject/xvswvttx.html