就在上周,生产上出了一个问题,问题的大致情况就是连接数达到了最大的数值,我心想,最大值有1万吧,怎么会那么多链接呢
我火速上到服务器上查看问题的详情
发现大多数的连接存活时间(age)比较长,(idle)空闲时间到时不长,说明了,链接都在忙,我一看最近的执行命令cmd都是ping之类的。我就想是谁在一直ping服务器呢,并且连接数在重启服务器后,掉了大几千。
我重新检查了项目的代码,在redis连接这块,并没有重复连接的情况,那为什么连接数就是不释放呢,问题陷入了无解的境况,一时没有思路。
于是,我就反思,什么样的场景下,会存在连接服务器呢,如果单纯执行查询缓存的命令,相信是不太会执行重连的逻辑的,此时,映入我眼帘的是,关于redis的发布和订阅功能,我想,从这里获取能找到一些蛛丝马迹,我迅速的看了一下源码,当我们订阅某一个channel时,
pubsub := rdb.Subscribe(context.Background(), "asynq:cancel")
会实例化一个PubSub,这个对象实现了redis中的Pub/Sub命令,该对象在出现网络问题时会自动重连redis服务器和重新订阅指定的channel。
func (c *Client) Subscribe(ctx context.Context, channels ...string) *PubSub {
pubsub := c.pubSub()
if len(channels) > 0 {
_ = pubsub.Subscribe(ctx, channels...)
}
return pubsub
}
// Subscribe the client to the specified channels. It returns
// empty subscription if there are no channels.
func (c *PubSub) Subscribe(ctx context.Context, channels ...string) error {
c.mu.Lock()
defer c.mu.Unlock()
err := c.subscribe(ctx, "subscribe", channels...)
if c.channels == nil {
c.channels = make(map[string]struct{})
}
for _, s := range channels {
c.channels[s] = struct{}{}
}
return err
}
问题似乎有点头绪了,我们继续看,当我们读取订阅的消息时,一般会
ch := pubsub.Channel()
for msg := range ch {
fmt.Println(msg.Channel, msg.Payload)
}
// Channel returns a Go channel for concurrently receiving messages.
// The channel is closed together with the PubSub. If the Go channel
// is blocked full for 30 seconds the message is dropped.
// Receive* APIs can not be used after channel is created.
//
// go-redis periodically sends ping messages to test connection health
// and re-subscribes if ping can not not received for 30 seconds.
func (c *PubSub) Channel(opts ...ChannelOption) <-chan *Message {
c.chOnce.Do(func() {
c.msgCh = newChannel(c, opts...)
c.msgCh.initMsgChan()
})
if c.msgCh == nil {
err := fmt.Errorf("redis: Channel can't be called after ChannelWithSubscriptions")
panic(err)
}
return c.msgCh.msgCh
}
这也就印证了,为什么我们的连接一直存活,空闲时间较短,那问题该如何解决呢,
pubsub := rdb.Subscribe(context.Background(), "asynq:cancel")
defer pubsub.Close()
在订阅后,业务处理完成后,记得进行关闭,这样,重新在其他地方进行订阅的时候,链接数不至于一直增加,用完所有的链接。重新发布代码后,生产的redis客户端连接数迅速降下来了,保持在正常的数值。