使用 go-redis 出现 connection pool timeout

534 阅读3分钟
💡 开发语言是 Golang,使用的库是 go-redis

线上服务的日志出现了 conn pool timeout 的错误,刚开始以为是只有我新写的接口出现了这个问题,后面发现是所有有依赖于 redis 的都出现了这个问题,后面又发现 db 操作和接口请求都有出现同样的报错,翻了一下错误日志,报错的原因都是因为 socket: too many open files。

google 上搜索了这个问题 ,找到了这篇文章。

如何解决 Linux 上的“打开文件过多”错误 --- How to Solve the "Too Many Open Files" Error on Linux (howtogeek.com)

最后让运维使用了 uimit -n 去修改限制的上限,解决了这个问题。

但是刚开始的思路是有误的,一直以为是 redis 的问题,用的库是 go-redis,所以过程中也对 go-redis 什么情况会出现 connection pool time 进行了模拟。

首先看官网明确说的,可能导致 conn pool timeout 的情况有两种

  1. 当连接池中没有空闲连接时,可能因为超过了 Options.PoolTimeout 时间而收到此错误,如果你在使用 Pub/Sub 或 redis.Conn,请确保不再使用它们时正确释放 PubSub/Conn 占用的网络资源。
  2. 当 Redis 处理命令的速度太慢并且池中的所有连接被阻塞的时间超过 PoolTimeout 持续时间时,也可能会遇到该错误。

我写了两个例子去模拟会导致 conn pool timeout/too many concurrent operations on a single file or socket 的情况

  1. 测试不断申请连接的情况,但不释放
package main

import (
	"context"
	"fmt"
	"log"
	"runtime"
	"testing"
	"time"

	"github.com/go-redis/redis/v8"
)

func main() {
	fmt.Println(runtime.GOMAXPROCS(runtime.NumCPU()))
	// 创建连接
	client := redis.NewClient(&redis.Options{
		Addr: ":6379",
	})

	//测试不断申请连接的情况,但不释放
	TestContinueApplyConn(client)
}

func TestContinueApplyConn(client *redis.Client) {
	for {
		// 申请连接
		conn := client.Conn(context.Background())
		//打印当前 redis 连接池中的连接数量
		fmt.Println(client.PoolStats().TotalConns)
		// 执行操作
		_, err := conn.Ping(client.Context()).Result()
		if err != nil {
			log.Fatal(err)
			break
		}

		// 释放连接
		// conn.Close()
	}
}

使用 go run main.go 运行,控制台输出内容如下

$ go run main.go 
16
0
1
2
3
...
160
2024/02/05 10:50:41 redis: connection pool timeout
exit status 1

第一行输出为 16,当连接数到 160 个后,出现 connection pool timeout 的错误,程序退出,这个同时也验证了 go-redis 连接数的连接池大小为 runtime.GOMAXPROCS * 10

  1. 测试使用 NewClient 出来的 redis client 并发不间断地去执行 redis command,这个出现的是 too many concurrent operations on a single file or socket (max 1048575) 的错误
// 执行这个报错的信息为: panic: too many concurrent operations on a single file or socket (max 1048575)
func TestContinueUseClientWithNewGoRoutine(client *redis.Client) {
	for {
		//打印当前 redis 连接池中的连接数量
		go func() {
			fmt.Println(client.PoolStats().TotalConns)
			err := client.SetNX(context.Background(), "test-key", 1, 10*time.Second).Err()
			if err != nil {
				log.Fatal(err)
			}
		}()
	}
}

.....
62
160
160
160
160
160
160
160
62
160
160
160
62
panic: too many concurrent operations on a single file or socket (max 1048575)

goroutine 1099917 [running]:
internal/poll.(*fdMutex).rwlock(0xc000128280, 0x0?)
        C:/Program Files/Go/src/internal/poll/fd_mutex.go:147 +0x11b
internal/poll.(*FD).writeLock(...)

这个报错信息时,可以在文件描述符上执行的最大并发操作数是 1048575,该操作太多了。解决方案是重构您的代码,以便您在该 UDP 连接上不会有 1048576 个并发操作