博客 / 詳情

返回

Redis 調優:必須關注的幾個參數

背景

在一台未經過任何調優的 Linux 服務器上部署 Redis,在 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_cpulist、bio_cpulist:可將 Redis 進程或後台 I/O 線程綁定到指定 CPU。

  • oom-score-adjoom-score-adj-values:調整進程的 oom_score。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)控制。
  2. 服務器通過accept()從隊列中取走連接,交給應用層處理。
  3. 當已完成連接隊列已滿時,新完成三次握手的連接無法進入 accept queue,內核通常會丟棄或延遲 ACK,導致客户端出現連接超時、重傳。

tcp-backlog 的默認值為 511,對應的內存變量為 server.tcp_backlog。

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;
}

實例在啓動時,會調用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
}

具體實現上,它會獲取/proc/sys/net/core/somaxconn的值。

somaxconn 決定了 Linux 內核允許的單個 TCP socket 的最大 backlog 隊列長度,也就是listen()的 backlog 參數在內核層面的上限。

如果 somaxconn 的值小於 tcp-backlog,Redis 會打印如下信息,此時,在listen()函數中實際生效的就是 somaxconn 的值。

# 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 甚至更高。

disable-thp

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

disable-thp 用來控制是否在進程級別關閉透明大頁。默認值是 1,對應的內部變量是 server.disable_thp。

實例在啓動時,會調用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);
    }
}

在具體實現上,該函數首先會調用checkOvercommit獲取/proc/sys/vm/overcommit_memory的值,如果 overcommit_memory 值不為 1,則會提示以下警告信息。

# 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.

在 Redis 中,建議將 vm.overcommit_memory 設置為 1,否則fork()子進程(如 RDB、AOF rewrite、全量複製)操作在低內存時會失敗。

接着,會調用checkTHPEnabled獲取/sys/kernel/mm/transparent_hugepage/enabled的值,如果它的值為 always,則會調用THPDisable()

該函數會基於 server.disable_thp 的值來決定是否禁用當前進程的透明大頁。

#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;
}

如果該變量的值為 0,則不關閉,如果為 1,則會調用prctl(PR_SET_THP_DISABLE, 1, 0, 0, 0)禁用當前進程的透明大頁。

如果操作系統開啓了透明大頁,且 Redis 沒有關閉,則會在日誌中打印以下信息。

# 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 中,建議關閉透明大頁,否則fork() 操作可能因管理大頁而顯著變慢,而且內存緊張時,合併/拆分大頁操作也會增加  Redis 響應耗時。

設置建議:默認值即可。

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 的 keepalive 參數:

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_KEEPIDLE net.ipv4.tcp_keepalive_time 首次探測的等待時間。連接空閒多久後發送第一個探測包。 設為 tcp-keepalive。默認是 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 在長連接和高併發場景下的穩定性。

設置建議:默認值即可。如果網絡質量特別差,可設置更小的值。

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、bio_cpulist、bgsave_cpulist、aof_rewrite_cpulist 分別用於綁定 Redis 主線程、BIO 後台線程、RDB 子進程和 AOF rewrite 子進程。

在實現上,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
#ifdef __FreeBSD__
    cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID, -1, sizeof(cpuset), &cpuset);
#endif
#ifdef __DragonFly__
    pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
#endif
#ifdef __NetBSD__
    pthread_setaffinity_np(pthread_self(), cpuset_size(cpuset), cpuset);
    cpuset_destroy(cpuset);
#endif
}

對於 Redis 這種單線程應用(主線程只能在一個 CPU 核心上處理請求)來説,進行 CPU 綁定的好處顯而易見:減少 CPU 上下文切換、提高 CPU Cache 命中率、避免受到其他高負載任務的干擾。唯一的不足是增加了運維的配置和管理成本。

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

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:禁用。默認值。
    • yes / relative:啓用相對模式(在系統原始 OOM 值的基礎上疊加)。
    • absolute:啓用絕對模式(直接使用配置值,不考慮系統原始值)。
  • oom-score-adj-values:針對不同角色設置 OOM 權重。該參數需要配置三個值,分別對應:主節點、從節點、後台子進程(BGCHILD,用於 RDB / AOF / 複製)。默認值為0 200 800。數值越大,表示在系統內存緊張時越容易被 OOM Killer 殺死。

無論是修改 oom-score-adj 還是 oom-score-adj-values,都會調用updateOOMScoreAdj 。

該函數會根據進程角色(主節點、從節點或後台子進程)為當前進程設置對應的 oom_score_adj 值。

static int updateOOMScoreAdj(const char **err) {
    if (setOOMScoreAdj(-1) == C_ERR) {
        *err = "Failed to set current oom_score_adj. Check server logs.";
        return0;
    }

    return1;
}

// 根據進程類別設置 Redis 的 oom_score_adj,process_class: 進程類別,-1 表示自動選擇
int setOOMScoreAdj(int process_class) {
    // 如果傳入 -1,自動根據角色選擇 master 或 replica
    if (process_class == -1)
        process_class = (server.masterhost ? CONFIG_OOM_REPLICA : CONFIG_OOM_MASTER);

    serverAssert(process_class >= 0 && process_class < CONFIG_OOM_COUNT);

#ifdef HAVE_PROC_OOM_SCORE_ADJ
    // oom_score_adjusted_by_redis:標記 Redis 是否已修改過 OOM 分數。
    // oom_score_adj_base:保存原始 OOM 分數,以便回滾或禁用時恢復。
    staticint oom_score_adjusted_by_redis = 0;
    staticint 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;
    } elseif (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;
#else
    /* Unsupported */
    return C_ERR;
#endif
}

具體實現上,系統的原始 OOM 值通過讀取/proc/self/oom_score_adj獲取。

在第一次啓用 OOM 調整時,Redis 會將該原始值保存到 oom_score_adj_base 中,以便在後續禁用配置或發生配置回滾時,能夠將進程的 OOM 值恢復為調整前的狀態。

無論是相對模式還是絕對模式,最終生效的 OOM 值都會寫回到/proc/self/oom_score_adj

需要注意的是,oom-score-adj-values 允許配置的取值範圍為 [-2000, 2000],而 Linux 內核實際支持的 oom_score_adj 範圍僅為[-1000, 1000]。

之所以放寬配置範圍,是為了更好地支持相對模式(relative):在相對模式下,配置值會與系統原始 OOM 值進行疊加,疊加後的結果再被裁剪到內核允許的範圍內,從而在不同系統初始 OOM 設置下仍能保持合理、可控的調整效果。 

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.