解决 Redis 启动 Creating Server TCP listening socket *:6379: unable to bind socket

redis-3.2.1 版本中,注释掉 bind,启动发现日志报 Creating Server TCP listening socket *:6379: unable to bind socket,但在 redis-5.0.0 中,同样的配置却能启动起来,本着刨根问底的精神,下载了两个版本的源码,对比下发现了原因。

先看 redis-3.2.1

C 复制代码
/* Initialize a set of file descriptors to listen to the specified 'port'
 * binding the addresses specified in the Redis server configuration.
 *
 * The listening file descriptors are stored in the integer array 'fds'
 * and their number is set in '*count'.
 *
 * The addresses to bind are specified in the global server.bindaddr array
 * and their number is server.bindaddr_count. If the server configuration
 * contains no specific addresses to bind, this function will try to
 * bind * (all addresses) for both the IPv4 and IPv6 protocols.
 *
 * On success the function returns C_OK.
 *
 * On error the function returns C_ERR. For the function to be on
 * error, at least one of the server.bindaddr addresses was
 * impossible to bind, or no bind addresses were specified in the server
 * configuration but the function is not able to bind * for at least
 * one of the IPv4 or IPv6 protocols. */
int listenToPort(int port, int *fds, int *count) {
    int j;

    /* Force binding of 0.0.0.0 if no bind address is specified, always
     * entering the loop if j == 0. */
    if (server.bindaddr_count == 0) server.bindaddr[0] = NULL;
    for (j = 0; j < server.bindaddr_count || j == 0; j++) {
        if (server.bindaddr[j] == NULL) {
            /* Bind * for both IPv6 and IPv4, we enter here only if
             * server.bindaddr_count == 0. */
            fds[*count] = anetTcp6Server(server.neterr,port,NULL,
                server.tcp_backlog);
            if (fds[*count] != ANET_ERR) {
                anetNonBlock(NULL,fds[*count]);
                (*count)++;

                /* Bind the IPv4 address as well. */
                fds[*count] = anetTcpServer(server.neterr,port,NULL,
                    server.tcp_backlog);
                if (fds[*count] != ANET_ERR) {
                    anetNonBlock(NULL,fds[*count]);
                    (*count)++;
                }
            }
            /* Exit the loop if we were able to bind * on IPv4 and IPv6,
             * otherwise fds[*count] will be ANET_ERR and we'll print an
             * error and return to the caller with an error. */
            if (*count == 2) break;
        } else if (strchr(server.bindaddr[j],':')) {
            /* Bind IPv6 address. */
            fds[*count] = anetTcp6Server(server.neterr,port,server.bindaddr[j],
                server.tcp_backlog);
        } else {
            /* Bind IPv4 address. */
            fds[*count] = anetTcpServer(server.neterr,port,server.bindaddr[j],
                server.tcp_backlog);
        }
        if (fds[*count] == ANET_ERR) {
            serverLog(LL_WARNING,
                "Creating Server TCP listening socket %s:%d: %s",
                server.bindaddr[j] ? server.bindaddr[j] : "*",
                port, server.neterr);
            return C_ERR;
        }
        anetNonBlock(NULL,fds[*count]);
        (*count)++;
    }
    return C_OK;
}

由于 bind 都被注释了,因此 if (server.bindaddr_count == 0) server.bindaddr[0] = NULL; 被执行了。

同时呢,代码注释中也写的很清楚,如果没有打开 bind,那么默认会连到 0.0.0.0,那么接下来走到 for 循环的 if 判断中,仔细看 fds[*count] = anetTcp6Server(server.neterr,port,NULL, server.tcp_backlog); 这段代码,会去连接 ipv6,如果本机没有开启 ipv6,那么便不会连接 ipv4,而是走到 if (fds[*count] == ANET_ERR) { 中打印日志便退出。

再来看看 redis-5.0.0

c 复制代码
/* Initialize a set of file descriptors to listen to the specified 'port'
 * binding the addresses specified in the Redis server configuration.
 *
 * The listening file descriptors are stored in the integer array 'fds'
 * and their number is set in '*count'.
 *
 * The addresses to bind are specified in the global server.bindaddr array
 * and their number is server.bindaddr_count. If the server configuration
 * contains no specific addresses to bind, this function will try to
 * bind * (all addresses) for both the IPv4 and IPv6 protocols.
 *
 * On success the function returns C_OK.
 *
 * On error the function returns C_ERR. For the function to be on
 * error, at least one of the server.bindaddr addresses was
 * impossible to bind, or no bind addresses were specified in the server
 * configuration but the function is not able to bind * for at least
 * one of the IPv4 or IPv6 protocols. */
int listenToPort(int port, int *fds, int *count) {
    int j;

    /* Force binding of 0.0.0.0 if no bind address is specified, always
     * entering the loop if j == 0. */
    if (server.bindaddr_count == 0) server.bindaddr[0] = NULL;
    for (j = 0; j < server.bindaddr_count || j == 0; j++) {
        if (server.bindaddr[j] == NULL) {
            int unsupported = 0;
            /* Bind * for both IPv6 and IPv4, we enter here only if
             * server.bindaddr_count == 0. */
            fds[*count] = anetTcp6Server(server.neterr,port,NULL,
                server.tcp_backlog);
            if (fds[*count] != ANET_ERR) {
                anetNonBlock(NULL,fds[*count]);
                (*count)++;
            } else if (errno == EAFNOSUPPORT) {
                unsupported++;
                serverLog(LL_WARNING,"Not listening to IPv6: unsupproted");
            }

            if (*count == 1 || unsupported) {
                /* Bind the IPv4 address as well. */
                fds[*count] = anetTcpServer(server.neterr,port,NULL,
                    server.tcp_backlog);
                if (fds[*count] != ANET_ERR) {
                    anetNonBlock(NULL,fds[*count]);
                    (*count)++;
                } else if (errno == EAFNOSUPPORT) {
                    unsupported++;
                    serverLog(LL_WARNING,"Not listening to IPv4: unsupproted");
                }
            }
            /* Exit the loop if we were able to bind * on IPv4 and IPv6,
             * otherwise fds[*count] will be ANET_ERR and we'll print an
             * error and return to the caller with an error. */
            if (*count + unsupported == 2) break;
        } else if (strchr(server.bindaddr[j],':')) {
            /* Bind IPv6 address. */
            fds[*count] = anetTcp6Server(server.neterr,port,server.bindaddr[j],
                server.tcp_backlog);
        } else {
            /* Bind IPv4 address. */
            fds[*count] = anetTcpServer(server.neterr,port,server.bindaddr[j],
                server.tcp_backlog);
        }
        if (fds[*count] == ANET_ERR) {
            serverLog(LL_WARNING,
                "Creating Server TCP listening socket %s:%d: %s",
                server.bindaddr[j] ? server.bindaddr[j] : "*",
                port, server.neterr);
            return C_ERR;
        }
        anetNonBlock(NULL,fds[*count]);
        (*count)++;
    }
    return C_OK;
}

看了下代码才发现,更改了连接的判断,先尝试连接 ipv6,再尝试连接 ipv4,这样即使本机没有开启 ipv6 也能成功。

总结下:

1、redis 3 那个版本,如果 bind 没开启,先绑定 ipv6,ipv6 绑定失败就报错了(开发机不支持或关闭了),绑定成功了,再绑定 ipv4;

2、redis 5 ,如果 bind 没开启,则先尝试绑定 ipv6,ipv6 绑定失败就打印个日志,再去尝试绑定 ipv4,成功了就继续往下走。

参考: https://github.com/redis/redis/issues/3241

相关推荐
Clarence Liu1 小时前
redis学习 (1) 基础入门
数据库·redis·学习
天生励志1232 小时前
Redis 安装部署
数据库·redis·缓存
爬山算法4 小时前
Redis(169)如何使用Redis实现数据同步?
前端·redis·bootstrap
武帝为此4 小时前
【Redis 数据库介绍】
数据库·redis·缓存
铁锚5 小时前
Redis中KEYS命令的潜在风险与遍历建议
数据库·redis·缓存
爬山算法6 小时前
Redis(168) 如何使用Redis实现会话管理?
java·数据库·redis
可爱の小公举6 小时前
Redis技术体系全面解析
数据库·redis·缓存
Geoking.9 小时前
Redis 中 ziplist 与 quicklist 解析与对比
数据库·redis·缓存
Eren7Y琳10 小时前
开箱即用构建应用环境:openEuler易获得性深度验证
redis·设计模式·架构
萝卜青今天也要开心10 小时前
2025年下半年系统架构设计师考后分享
java·数据库·redis·笔记·学习·系统架构