找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2579

积分

0

好友

361

主题
发表于 4 天前 | 查看: 16| 回复: 0

在一台未经过任何调优的 Linux 服务器上部署 Redis,启动时你可能会遇到下面这些警告信息。

1363410:M 15 Jan 2026 13:07:34.879 # WARNING: The TCP backlog setting of 512 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
1363410:M 15 Jan 2026 13:07:34.879 # Server initialized
1363410:M 15 Jan 2026 13:07:34.879 # WARNING Memory overcommit must be enabled! Without it, a background save or replication may fail under low memory condition. Being disabled, it can can also cause failures without low memory condition, see https://github.com/jemalloc/jemalloc/issues/1328. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command ‘sysctl vm.overcommit_memory=1' for this to take effect.
1363410:M 15 Jan 2026 13:07:34.879 # WARNING You have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command ‘echo madvise > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled (set to 'madvise' or ‘never').

这些警告并非无关紧要,它们实际上是在提醒你,当前操作系统的某些内核参数设置不够合理,如果不进行调整,将会直接影响 Redis 的性能和长期运行的稳定性。

除了这些需要在操作系统层面解决的警告,Redis 自身也提供了多个参数,用于在进程或连接级别进行更精细化的内核参数优化。这主要包括:

  • tcp-backlog:设置 TCP 服务器中 listen() 的 backlog 参数。
  • disable-thp:在进程级别关闭透明大页(THP)。
  • tcp-keepalive:在连接级别设置 TCP Keepalive 参数。
  • server_cpulistbio_cpulist:可将 Redis 进程或后台 I/O 线程绑定到指定的 CPU 核心。
  • oom-score-adjoom-score-adj-values:调整进程的 oom_score。该值是 Linux 内核为每个进程计算的一个整数值(位于 /proc/[pid]/oom_score),分数越高,意味着在系统内存不足时,该进程越容易被 OOM Killer 优先终止。

下面,我们就来深入探讨这些关键参数的实现原理、作用以及最佳配置建议。

tcp-backlog

我们先从配置定义看起:

createIntConfig(“tcp-backlog”, NULL, IMMUTABLE_CONFIG, 0, INT_MAX, server.tcp_backlog, 511, INTEGER_CONFIG, NULL, NULL), /* TCP listen backlog. */

tcp-backlog 参数用于设置 TCP 服务器在调用 listen() 函数时使用的 backlog 参数。这个参数决定了已完成三次握手、但尚未被应用层 accept() 处理的连接队列的最大长度。

一个典型的 TCP 服务器端流程如下:

// 1. 创建 socket
int fd = socket(AF_INET, SOCK_STREAM, 0);
// 2. 绑定 IP + 端口
bind(fd, ...);
// 3. 开始监听
listen(fd, backlog);
// 4. 接受连接
int connfd = accept(fd, ...);

当客户端发起连接时,会发生什么呢?

  1. TCP 三次握手完成后,这个连接会被内核放入一个名为“已完成连接队列”(accept queue)的地方,这个队列的长度正是由 listen(backlog) 中的 backlog 值控制的。
  2. 服务器程序通过调用 accept() 从该队列中取出连接,交给业务逻辑处理。
  3. 如果已完成连接队列已满,后续完成三次握手的连接将无法进入队列。此时,内核通常会丢弃客户端发来的 ACK 或进行其他处理,导致客户端出现连接超时、重传等现象。

在 Redis 源码中,tcp-backlog 的默认值为 511,对应的内存变量是 server.tcp_backlog。在建立监听时,会调用 anetListen 函数:

static int anetListen(char *err, int s, struct sockaddr *sa, socklen_t len, int backlog) {
    // 将 socket 绑定到指定的 IP 和端口
    if (bind(s,sa,len) == -1) {
        anetSetError(err, “bind: %s”, strerror(errno));
        close(s);
        return ANET_ERR;
    }
    // 将 socket 设置为监听状态,开始接收客户端连接,backlog 指定内核允许排队的最大未处理连接数
    if (listen(s, backlog) == -1) {
        anetSetError(err, “listen: %s”, strerror(errno));
        close(s);
        return ANET_ERR;
    }
    return ANET_OK;
}

Redis 实例在启动时,会主动调用 checkTcpBacklogSettings() 函数来检查 server.tcp_backlog 的配置是否合理。

void checkTcpBacklogSettings(void) {
#if defined(HAVE_PROC_SOMAXCONN)
    FILE *fp = fopen(“/proc/sys/net/core/somaxconn”, “r”);
    char buf[1024];
    if (!fp) return;
    if (fgets(buf,sizeof(buf),fp) != NULL) {
        int somaxconn = atoi(buf);
        if (somaxconn > 0 && somaxconn < server.tcp_backlog) {
            serverLog(LL_WARNING,“WARNING: The TCP backlog setting of %d cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of %d.”, server.tcp_backlog, somaxconn);
        }
    }
    fclose(fp);
...
#endif
}

这里的关键在于 somaxconn 这个系统参数,它位于 /proc/sys/net/core/somaxconnsomaxconn 定义了 Linux 内核允许的单个 TCP socket 的最大 backlog 队列长度,也就是 listen() 参数在内核层面的硬性上限。

如果 somaxconn 的值小于 Redis 配置的 tcp-backlog,那么在实际调用 listen() 时,生效的将是更小的 somaxconn 值。此时,Redis 就会打印出我们开头看到的那个警告:

# WARNING: The TCP backlog setting of 512 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.

设置建议:对于大多数普通的 Web 服务或并发连接数在几百量级的场景,默认值 511 通常足够。但如果你部署的是一个高并发服务,预期会有成千上万的并发 TCP 连接,建议将 backlog 调整为 4096 甚至更高,并相应地将系统的 /proc/sys/net/core/somaxconn 值也调大。

disable-thp

其配置定义如下:

createBoolConfig(“disable-thp”, NULL, IMMUTABLE_CONFIG, server.disable_thp, 1, NULL, NULL),

disable-thp 参数控制是否在进程级别关闭透明大页(Transparent Huge Pages)。默认值为 1(开启),对应的内部变量是 server.disable_thp

Redis 在启动时,会调用 linuxMemoryWarnings 函数来检查与 Linux 系统内存相关的配置。

void linuxMemoryWarnings(void) {
    sds err_msg = NULL;
    if (checkOvercommit(&err_msg) < 0) {
        serverLog(LL_WARNING,“WARNING %s”, err_msg);
        sdsfree(err_msg);
    }
    if (checkTHPEnabled(&err_msg) < 0) {
        server.thp_enabled = 1;
        if (THPDisable() == 0) {
            server.thp_enabled = 0;
        } else {
            serverLog(LL_WARNING, “WARNING %s”, err_msg);
        }
        sdsfree(err_msg);
    }
}

首先,它会检查 overcommit_memory 设置(通过读取 /proc/sys/vm/overcommit_memory)。如果其值不为 1,Redis 会发出严厉警告,因为内存超配被禁用时,在低内存条件下执行 fork() 操作(如 RDB 持久化、AOF 重写或全量复制)很可能失败。

# WARNING Memory overcommit must be enabled! Without it, a background save or replication may fail under low memory condition...

接着,它会检查透明大页是否被全局启用(通过读取 /sys/kernel/mm/transparent_hugepage/enabled)。如果发现其值为 always,并且 server.disable_thp 为 1,则会调用 THPDisable() 函数。

#include<sys/prctl.h>
/* since linux-3.5, kernel supports to set the state of the “THP disable” flag
 * for the calling thread. PR_SET_THP_DISABLE is defined in linux/prctl.h */
static int THPDisable(void) {
    int ret = -EINVAL;

    if (!server.disable_thp)
        return ret;

#ifdef PR_SET_THP_DISABLE
    ret = prctl(PR_SET_THP_DISABLE, 1, 0, 0, 0);
#endif

    return ret;
}

这个函数通过 prctl(PR_SET_THP_DISABLE, 1, 0, 0, 0) 系统调用,仅针对当前 Redis 进程禁用透明大页。如果操作系统开启了透明大页,而 Redis 未能成功关闭它,就会在日志中打印警告:

# WARNING You have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis...

为何 Redis 如此“反感”透明大页?主要原因是 THP 会导致 fork() 操作(用于生成 RDB 或 AOF 重写子进程)变得异常缓慢,并且在内存紧张时,内核合并或拆分大页的操作会引入不可预测的延迟,严重影响 Redis 的响应性能。因此,Linux系统 上运行 Redis,强烈建议关闭透明大页。

设置建议:保持默认值 disable-thp yes 即可。更彻底的做法是在操作系统层面全局禁用 THP。

tcp-keepalive

其配置定义如下:

createIntConfig(“tcp-keepalive”, NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.tcpkeepalive, 300, INTEGER_CONFIG, NULL, NULL),

tcp-keepalive 的默认值为 300(秒),对应的内部变量是 server.tcpkeepalive。它作用于连接级别。

Redis 会在每个 TCP 连接建立之后,调用 anetKeepAlive 函数,通过 setsockopt 来配置底层 socket 的保活参数:

int anetKeepAlive(char *err, int fd, int interval) {
    int val = 1;
    // 启用 TCP keepalive 机制
    if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1) {
        anetSetError(err, “setsockopt SO_KEEPALIVE: %s”, strerror(errno));
        return ANET_ERR;
    }
    // 设置首次探测时间:连接空闲 interval 秒后开始发送第一个探测包
    val = interval;
    if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) {
        anetSetError(err, “setsockopt TCP_KEEPIDLE: %s\n”, strerror(errno));
        return ANET_ERR;
    }
    // 设置探测间隔:每 interval/3 秒发送一次探测包
    val = interval/3;
    if (val == 0) val = 1;
    if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &val, sizeof(val)) < 0) {
        anetSetError(err, “setsockopt TCP_KEEPINTVL: %s\n”, strerror(errno));
        return ANET_ERR;
    }
    // 设置最大探测次数:连续 3 次探测失败后判定连接已死亡
    val = 3;
    if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &val, sizeof(val)) < 0) {
        anetSetError(err, “setsockopt TCP_KEEPCNT: %s\n”, strerror(errno));
        return ANET_ERR;
    }
    ...
    return ANET_OK;
}

我们可以将 Redis 的设置与 Linux 系统默认的 TCP/IP 内核参数做一个对比:

选项 对应内核参数 作用 Redis 代码逻辑与默认值对比
TCP_KEEPIDLE net.ipv4.tcp_keepalive_time 首次探测的等待时间。连接空闲多久后发送第一个探测包。 设为 tcp-keepalive 值(默认300秒)。系统默认是7200秒
TCP_KEEPINTVL net.ipv4.tcp_keepalive_intvl 探测间隔。两个探测包之间的时间间隔。 设为 tcp-keepalive/3(最小为1秒)。系统默认是75秒
TCP_KEEPCNT net.ipv4.tcp_keepalive_probes 探测次数。达到次数后仍无响应则判定连接死亡。 固定设为3次。系统默认是9次。

通过以上对比可以看出,Redis 主动配置了比系统默认值更短、更积极的 TCP Keepalive 参数。这样做的好处非常明显:可以更快地发现已经僵死或发生异常的客户端连接,及时释放其占用的文件描述符等资源,从而提升 高并发 长连接场景下 Redis 服务的整体稳定性和资源利用率。

设置建议:通常保持默认值 300 即可。如果你的网络环境非常不稳定,可以考虑将其设置为更小的值(如 60),以便更快地清理无效连接。

xxx_cpulist

Redis 支持通过一组参数,将不同的执行单元绑定到指定的 CPU 核心上,这组参数包括:

createStringConfig(“server_cpulist”, NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.server_cpulist, NULL, NULL, NULL),
createStringConfig(“bio_cpulist”, NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.bio_cpulist, NULL, NULL, NULL),
createStringConfig(“aof_rewrite_cpulist”, NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.aof_rewrite_cpulist, NULL, NULL, NULL),
createStringConfig(“bgsave_cpulist”, NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.bgsave_cpulist, NULL, NULL, NULL),

它们分别用于绑定:

  • server_cpulist:Redis 主事件循环线程(处理所有命令请求)。
  • bio_cpulist:BIO(Background I/O)后台线程(负责异步关闭文件、AOF 刷盘等任务)。
  • bgsave_cpulist:执行 RDB 持久化的 bgsave 子进程。
  • aof_rewrite_cpulist:执行 AOF 重写的子进程。

在实现上,Redis 提供了 setcpuaffinity 函数来封装 CPU 亲和性设置操作。例如在 Linux 系统上,它使用 sched_setaffinity 系统调用来完成绑定。

void redisSetCpuAffinity(const char *cpulist) {
#ifdef USE_SETCPUAFFINITY
    setcpuaffinity(cpulist);
#else
    UNUSED(cpulist);
#endif
}

/* set current thread cpu affinity to cpu list, this function works like
 * taskset command (actually cpulist parsing logic reference to util-linux).
 * example of this function: “0,2,3”, “0,2-3”, “0-20:2”. */
void setcpuaffinity(const char *cpulist) {
...
#ifdef __linux__
    sched_setaffinity(0, sizeof(cpuset), &cpuset);
#endif
...
}

对于 Redis 这种核心处理逻辑为单线程(主线程)的模型,进行 CPU 绑定的益处是直观的:减少不必要的 CPU 上下文切换开销、提高 CPU 缓存命中率、避免主线程被同一服务器上其他高负载任务频繁抢占干扰。当然,这也增加了 运维 部署和管理的复杂性。

oom-score-adj、oom-score-adj-values

这是两个用于管理进程 OOM (Out-Of-Memory) 优先级的参数,它们的配置定义如下:

createEnumConfig(“oom-score-adj”, NULL, MODIFIABLE_CONFIG, oom_score_adj_enum, server.oom_score_adj, OOM_SCORE_ADJ_NO, NULL, updateOOMScoreAdj),
createSpecialConfig(“oom-score-adj-values”, NULL, MODIFIABLE_CONFIG | MULTI_ARG_CONFIG, setConfigOOMScoreAdjValuesOption, getConfigOOMScoreAdjValuesOption, rewriteConfigOOMScoreAdjValuesOption, updateOOMScoreAdj),
  • oom-score-adj:决定是否启用以及以何种模式启用 OOM 分数调整。
    • no:禁用调整(默认值)。
    • yesrelative:启用相对模式。在此模式下,配置的 OOM 值会叠加到系统原有的 oom_score_adj 基础上。
    • absolute:启用绝对模式。直接使用配置值,覆盖系统原始值。
  • oom-score-adj-values:为不同角色的 Redis 进程设置具体的 OOM 权重值。该参数需要三个整数值,分别对应:主节点从节点后台子进程(BGCHILD,即执行 RDB / AOF rewrite / 复制的子进程)。默认值为 0 200 800数值越大,表示在系统内存紧张时,该进程越容易被 OOM Killer 选中并杀死。

无论是修改 oom-score-adj 还是 oom-score-adj-values,最终都会触发调用 updateOOMScoreAdj 函数。这个函数的核心是 setOOMScoreAdj,它会根据当前进程的角色(主、从、后台子进程)来设置相应的 oom_score_adj 值。

int setOOMScoreAdj(int process_class) {
    // 如果传入 -1,自动根据角色选择 master 或 replica
    if (process_class == -1)
        process_class = (server.masterhost ? CONFIG_OOM_REPLICA : CONFIG_OOM_MASTER);
    ...
#ifdef HAVE_PROC_OOM_SCORE_ADJ
    static int oom_score_adjusted_by_redis = 0;
    static int oom_score_adj_base = 0;

    int fd;
    int val;
    char buf[64];
    // oom_score_adj 不为 no
    if (server.oom_score_adj != OOM_SCORE_ADJ_NO) {
        // 第一次修改时,备份原始 oom_score_adj
        if (!oom_score_adjusted_by_redis) {
            oom_score_adjusted_by_redis = 1;
            /* Backup base value before enabling Redis control over oom score */
            fd = open(“/proc/self/oom_score_adj”, O_RDONLY);
            if (fd < 0 || read(fd, buf, sizeof(buf)) < 0) {
                serverLog(LL_WARNING, “Unable to read oom_score_adj: %s”, strerror(errno));
                if (fd != -1) close(fd);
                return C_ERR;
            }
            oom_score_adj_base = atoi(buf);
            close(fd);
        }
        // 获取配置的 OOM 分数
        val = server.oom_score_adj_values[process_class];
        // 如果是相对模式,累加原始值
        if (server.oom_score_adj == OOM_SCORE_RELATIVE)
            val += oom_score_adj_base;
        // 限制值在 [-1000, 1000] 的内核有效范围内
        if (val > 1000) val = 1000;
        if (val < -1000) val = -1000;
    } else if (oom_score_adjusted_by_redis) {
        // 如果配置禁用 OOM 调整且之前已修改过,恢复原始值
        oom_score_adjusted_by_redis = 0;
        val = oom_score_adj_base;
    } else {
        return C_OK;
    }

    snprintf(buf, sizeof(buf) - 1, “%d\n”, val);
    // 写入 /proc/self/oom_score_adj,使内核生效
    fd = open(“/proc/self/oom_score_adj”, O_WRONLY);
    if (fd < 0 || write(fd, buf, strlen(buf)) < 0) {
        serverLog(LL_WARNING, “Unable to write oom_score_adj: %s”, strerror(errno));
        if (fd != -1) close(fd);
        return C_ERR;
    }
    close(fd);
    return C_OK;
...
}

实现上有几个关键点:

  1. 系统原始的 OOM 值通过读取 /proc/self/oom_score_adj 获得,并在第一次启用调整时备份到 oom_score_adj_base 变量中。
  2. 无论是相对模式还是绝对模式,计算出的最终值都会写回 /proc/self/oom_score_adj 文件,从而立即影响内核的 OOM 评估。
  3. 一个有趣的细节是:oom-score-adj-values 允许的配置范围是 [-2000, 2000],而 Linux 内核实际支持的 oom_score_adj 有效范围是 [-1000, 1000]。设计更宽的配置范围主要是为了优雅地支持“相对模式”。在相对模式下,配置值会与系统原始值叠加,叠加后的结果再被裁剪到 [-1000, 1000] 的合法区间。这样,无论系统初始 OOM 设置如何,你都能通过配置实现有意义的、方向正确的调整。

默认配置 0 200 800 体现了一种明智的策略:保护主节点(分数最低),让从节点承担一定风险,而让后台子进程(尤其是可能消耗大量内存的 bgsaverewrite)成为最优先的牺牲品。这符合 高可用 和业务连续性的普遍诉求——优先保证服务本身可用。

希望通过本文对 Redis 这些内核级调优参数的深入解析,能帮助你在实际部署中更好地配置 Redis,使其性能与稳定性得到充分保障。如果你想了解更多关于数据库或系统优化的实战经验,欢迎关注云栈社区,与更多开发者一同交流成长。




上一篇:PVE集群网络规划实战:从单机部署到企业级三节点高可用架构
下一篇:元象开源XVERSE-Ent大模型:专为泛娱乐设计的4.2B/5.7B中英双语底座
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-24 02:48 , Processed in 0.245093 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表