解决 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

相关推荐
心想好事成1 小时前
尚硅谷redis7 47-48 redis事务之理论简介
java·数据库·redis
会敲键盘的猕猴桃很大胆2 小时前
Redis实战-缓存篇(万字总结)
java·数据库·spring boot·redis·缓存
熙曦Sakura10 小时前
【Redis】Redis安装
数据库·redis·缓存
MonKingWD12 小时前
【redis原理篇】底层数据结构
数据结构·数据库·redis
渡梦酒13 小时前
Redis批量删除Key的三种方式
数据库·redis·junit
小Mie不吃饭15 小时前
Redis | 缓存技术对后端的重要性
数据库·redis·缓存
泽韦德16 小时前
【Redis】第1节|Redis服务搭建
数据库·redis·缓存
凌~风18 小时前
Windows环境下Redis的安装使用与报错解决
数据库·redis·缓存
冼紫菜1 天前
Spring Cloud项目登录认证从JWT切换到Redis + UUID Token方案
java·redis·后端·spring cloud
Absinthe_苦艾酒1 天前
Redis学习专题(六)分布式锁
redis·分布式