一.故障现象
1.influxdb内存使用极高
内存占用达到37.1GiB:
2.influxdb的goroutine很多
goroutine数目达18W之巨,并且持续增长:
二.pprof排查
1.pprof heap
内存占用高的话,一般会pprof 堆内存;
下图可以看到,主要的heap内存在解析point,写入数据;
2. pprof goroutine
17W个goroutine都在执行远程写,并且有dial创建远程连接的操作:
看一下goroutine内部都在干啥:
curl controller24399:8086/debug/pprof/goroutine?debug=1 > goroutine.txt
其中,有17w个goroutine锁在了RLock上,初步猜测RWLock可能出现了deadlock:
三.RWLock不可重入导致死锁
1.RWLock不可冲入
RWLock中的RLock()是不可重入的,这是因为
中间一旦有Lock(),就会导致后面的RLock()被锁住;
- 这是避免Lock()锁饥饿而做出的动作;
最终出现:
- Lock()等第一个RLock()释放读锁;
- 第二个RLock()等待Lock()释放写锁;
- 产生死锁;
2.RWLock避免Lock()写'锁饥饿'的机制
四.故障原因
下面结合influxdb的源码,进行原因分析。
influxdb远程连接的clientPool,goroutine内部出现RLock()的可重入调用,一旦中间其它goroutine有Lock(),就会产生死锁。
// cluster/client_pool.go
func (c *clientPool) conn(nodeID uint64) (net.Conn, error) {
c.mu.RLock()
conn, err := c.pool[nodeID].Get()
c.mu.RUnlock()
return conn, err
}
conn()函数:
- 先拿到RLock();
- 调用c.pool[nodeId].Get()创建链接;
- 最后释放RUnlock();
// cluster/pool.go
func (c *boundedPool) Get() (net.Conn, error) {
conns := c.getConns()
if conns == nil {
return nil, pool.ErrClosed
}
// Try and grab a connection from the pool
select {
case conn := <-conns:
if conn == nil {
return nil, pool.ErrClosed
}
return c.wrapConn(conn), nil
default:
// Could not get connection, can we create a new one?
if atomic.LoadInt32(&c.total) < int32(cap(conns)) {
conn, err := c.factory() // 创建连接的时候,调用clientPool.size()尝试获取RLock()
if err != nil {
return nil, err
}
atomic.AddInt32(&c.total, 1)
return c.wrapConn(conn), nil
}
}
…
}
factory()实际调用的是connFactory.dial()函数:
函数内部又调用了clientPool.size();
func (c *connFactory) dial() (net.Conn, error) { if c.clientPool.size() > maxConnections { return nil, errMaxConnectionsExceeded } ni, err := c.metaClient.DataNode(c.nodeID) if err != nil { return nil, err } ... }
而clientPool.size()函数内部,又尝试申请RLock();
产生可重入的RLock()调用
;
// cluster/client_pool.go
func (c *clientPool) size() int {
c.mu.RLock()
var size int
for _, p := range c.pool {
size += p.Len()
}
c.mu.RUnlock()
return size
}
而中间一旦遇到其它goroutine对clientPool内部的Lock()就会产生死锁:
// cluster/client_pool.go
func (c *clientPool) setPool(nodeID uint64, p pool.Pool) {
c.mu.Lock()
c.pool[nodeID] = p
c.mu.Unlock()
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。