iqiyi / dpvs

DPVS is a high performance Layer-4 load balancer based on DPDK.
Other
3.01k stars 724 forks source link

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

Open yuexiaocai opened 1 month ago

yuexiaocai commented 1 month ago

问题描述

在使用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 commented 1 month ago

作为参考:

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:https://github.com/puma/puma/issues/1449

yuexiaocai commented 1 month ago

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

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

yuexiaocai commented 3 weeks ago

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

ywc689 commented 3 weeks ago

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