Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

是否可以通过增大监听的backlog解决并发连接unix domain socket失败问题 #994

Open
yuexiaocai opened this issue Sep 2, 2024 · 4 comments
Assignees
Labels
issue/to-solve issues await answers tobe solved

Comments

@yuexiaocai
Copy link

yuexiaocai commented Sep 2, 2024

问题描述

在使用dpvs-agent批量下发配置(例如并发设置几十条转发路由,并发量并不大)时经常出现Get conn from pool failed: Error="dial unix /var/run/dpvs.ipc: connect: resource temporarily unavailable"的报错。

dpvs-agent的解决方法(未奏效)

注意到dpvs-agent的代码中也有提到相关问题,解决方法是进行10次重试(但在我的测试中并未奏效,重试10次后仍有很多路由下发失败):

func unixDialer(ctx context.Context) (net.Conn, error) {
	retry := 0
	d := net.Dialer{Timeout: 10 * time.Second}
	raddr := net.UnixAddr{Name: IpcSocket, Net: "unix"}
	for {
		conn, err := d.DialContext(ctx, "unix", raddr.String())
		if err == nil {
			return conn, nil
		}
		// FIXME: A weird fact was observed in tests of large concurrency that the previous
		// "DailContext" returned the error "resource temporarily unavailable" occasionally.
		// No solution to it has found yet, thus just retry as a compromise.
		if strings.Contains(err.Error(), "resource temporarily unavailable") != true {
			return nil, err
		}
		retry++
		if retry > 10 {
			return nil, err
		}
	}
	return nil, errors.New("unknown error")
}

疑点

连接池参数

此时我尝试输出中有多少连接可以复用,结果调试发现代码中的ConnPool似乎并未起到作用:我在route.go执行conn, err := cp.Get(ctx)之后打印了cp.Stats(),发现在每次查找连接时都是Miss,从未Hit连接,TotalConns的数量也是0。

我发现dpvs-agent代码中,初始化时未设置MinIdleConns字段,因而缺省为0,进而连接池中并无idle连接可以复用,每次与数据面交互似乎都需要重新创建连接

cp := pool.NewConnPool(&pool.Options{
		Dialer:   unixDialer,
		PoolSize: 1024,
		// PoolTimeout:        -1,
		// IdleTimeout:        -1,
		// IdleCheckFrequency: -1,
	})

然而设置了MinIdleConns字段后,所有访问dpvs-agent的curl请求都block住了,无法正常运行。

socket监听参数

同时,数据面的ctrl.c中 Server socket 监听的 backlog 设置成了1,这样的做法似乎并不常见:

if (-1 == listen(srv_fd, 1)) {
        RTE_LOG(ERR, MSGMGR, "%s: Server socket listen failed\n", __func__);
        close(srv_fd);
        unlink(ipc_unix_domain);
        return EDPVS_IO;
    }

当我把 backlog 调大后,connect: resource temporarily unavailable 的问题解决了,所有路由都可以批量下发成功。

问题

想请教一下 DPVS 的开发者们:

  1. listen(srv_fd, 1)backlog设置为1 有什么其他方面的特殊考虑吗?这会不会是导致下发配置并发问题的根因?
  2. 是我的配置出了问题,导致在dpvs-agent中连接池没有生效吗?如果连接池中没有可以复用的连接,为什么要设置ConnPool机制?
@yuexiaocai yuexiaocai changed the title 是否可以通过增大监听backlog解决Unix domain socket连接失败的问题? 是否可以通过增大监听的backlog解决并发连接unix domain socket失败问题 Sep 2, 2024
@yuexiaocai
Copy link
Author

作为参考:

redis中的backlog默认值是511:https://github.com/redis/redis/blob/unstable/redis.conf#L148

PostgreSQL中通过 postgresql.conf 配置文件中的 max_connections 参数间接影响 backlog,默认值是100:https://github.com/postgres/postgres/blob/master/src/backend/utils/misc/postgresql.conf.sample#L65

puma中的backlog默认值是1024:puma/puma#1449

@yuexiaocai
Copy link
Author

另外,是否可以在retry时使用指数退避的策略,而不是无间隔地进行retry?

由于 d.DialContext() 直接返回了错误,所以Dialer 的Timeout参数(10秒)其实是不生效的,所有的retry会再次并发执行,导致最终全部失败。

@yuexiaocai
Copy link
Author

再次补充:dpvs数据面用的是accept,所以无法同时和多个client通信,不知此处为什么要模仿go-pg的连接池。是否考虑把数据面改成epoll呢?

@ywc689
Copy link
Collaborator

ywc689 commented Sep 6, 2024

dpvs-agent底层通信的连接池设计上有缺陷,现在只是通过简单重试缓解该问题,后面我们会详细看看,非常感谢提供的问题线索和解决思路。

@ywc689 ywc689 added the issue/to-solve issues await answers tobe solved label Sep 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
issue/to-solve issues await answers tobe solved
Projects
None yet
Development

No branches or pull requests

3 participants