国语诗 发表于 2026-1-4 07:20:07

Redis 7.0 新特性之maxmemory-clients:限制客户端内存总使用量

背景

之前分享个 case(Redis 内存突增时,如何定量分析其内存使用情况),一个 Redis 实例的内存突增,used_memory最大时达到了 78.9G,而该实例的maxmemory配置却只有 16G,最终导致实例中的数据被大量驱逐。
导致这个问题的一个常见原因是客户端占用的内存过多。
Redis 中,客户端内存主要包括三部分:输入缓冲区(暂存客户端命令)、输出缓冲区(缓存发送给客户端的数据),以及客户端对象本身的开销。
其中,输入缓冲区可通过client-query-buffer-limit限制,输出缓冲区可通过client-output-buffer-limit限制。
但这两个参数只能限制单个客户端。
在客户端数量较多的情况下,即使单个客户端占用不大,客户端内存的总量仍可能失控。
为了解决这一问题,Redis 7.0 引入了maxmemory-clients,用于限制所有客户端可使用的内存总量。
下面看看具体的实现细节。
配置

standardConfig static_configs[] = {
...
    createSSizeTConfig("maxmemory-clients", NULL, MODIFIABLE_CONFIG, -100, SSIZE_MAX, server.maxmemory_clients, 0, MEMORY_CONFIG | PERCENT_CONFIG, NULL, applyClientMaxMemoryUsage),
...
};maxmemory-clients 的默认值为 0,最小值为 -100,对应的内部变量是server.maxmemory_clients。
该参数既可以设置为正数,也可以设置为负数:

[*]正数:表示客户端内存总使用量的上限。因为该参数的类型是 MEMORY_CONFIG,所以可以指定 kb/mb/gb 之类的单位。不指定,则默认是字节。
[*]负数:表示按 maxmemory  的百分比限制客户端内存。例如:maxmemory-clients = -50表示客户端内存总量不得超过 maxmemory 的 50%。
这一点是在getClientEvictionLimit函数中实现的。
size_t getClientEvictionLimit(void) {
    size_t maxmemory_clients_actual = SIZE_MAX;

    if (server.maxmemory_clients < 0 && server.maxmemory > 0) {
        unsignedlonglong maxmemory_clients_bytes = (unsignedlonglong)((double)server.maxmemory * -(double) server.maxmemory_clients / 100);
        if (maxmemory_clients_bytes <= SIZE_MAX)
            maxmemory_clients_actual = maxmemory_clients_bytes;
    }
    elseif (server.maxmemory_clients > 0)
        maxmemory_clients_actual = server.maxmemory_clients;
    else
        return0;

    /* Don't allow a too small maxmemory-clients to avoid cases where we can't communicate
     * at all with the server because of bad configuration */
    if (maxmemory_clients_actual < 1024*128)
        maxmemory_clients_actual = 1024*128;

    return maxmemory_clients_actual;
}可以看到,只有满足以下条件的客户端才会被统计内存并参与驱逐:

[*]maxmemory_clients不为 0。
[*]客户端未设置 CLIENT_NO_EVICT,在 Redis 7.0 中,支持通过CLIENT NO-EVICT ON命令显式关闭驱逐。
[*]客户端类型为 NORMAL 或 PUBSUB。也就是说,复制相关客户端不会被驱逐。
客户端驱逐细节

客户端驱逐是在evictClients函数中实现的。
void evictClients(void) {
    // 如果 client_mem_usage_buckets 没被初始化,则直接返回
    if (!server.client_mem_usage_buckets)
        return;
    // 从最大客户端内存桶开始驱逐
    int curr_bucket = CLIENT_MEM_USAGE_BUCKETS-1;
    listIter bucket_iter;
    listRewind(server.client_mem_usage_buckets.clients, &bucket_iter);
    // 获取客户端允许使用的最大内存
    size_t client_eviction_limit = getClientEvictionLimit();
    if (client_eviction_limit == 0)
        return;
    // 循环驱逐,直到客户端总内存降到阈值以下或所有可驱逐客户端已释放
    while (server.stat_clients_type_memory +
           server.stat_clients_type_memory >= client_eviction_limit) {
        // 获取当前桶的下一个客户端
        listNode *ln = listNext(&bucket_iter);
        if (ln) {
            client *c = ln->value;
            // 生成客户端信息字符串,用于日志
            sds ci = catClientInfoString(sdsempty(),c);
            serverLog(LL_NOTICE, "Evicting client: %s", ci);
            // 释放客户端占用的资源
            freeClient(c);
            sdsfree(ci);
            // stat_evictedclients对应的是info stats中的evicted_clients
            server.stat_evictedclients++;
        } else {
            // 当前桶已空,切换到下一个较小客户端桶
            curr_bucket--;
            // 所有桶都已经遍历完,但内存仍超过阈值,记录警告
            if (curr_bucket

娥搽裙 发表于 2026-1-23 09:44:40

感谢分享,学习下。

赖琳芳 发表于 2026-1-24 03:23:14

yyds。多谢分享

褐洌 发表于 2026-1-28 08:48:32

感谢分享

闹忧踫 发表于 2026-1-31 21:16:40

谢谢分享,试用一下

涣爹卮 发表于 2026-2-1 05:53:18

鼓励转贴优秀软件安全工具和文档!

琴丁辰 发表于 2026-2-3 03:53:40

谢谢楼主提供!

颖顿庐 发表于 2026-2-8 13:17:01

过来提前占个楼

艺轫 发表于 2026-2-8 17:33:54

感谢发布原创作品,程序园因你更精彩

注思 发表于 2026-2-9 08:12:11

懂技术并乐意极积无私分享的人越来越少。珍惜

僭墙覆 发表于 2026-2-10 00:18:00

这个好,看起来很实用

茅断卉 发表于 2026-2-10 12:24:47

收藏一下   不知道什么时候能用到

寥唏 发表于 2026-2-11 13:06:08

感谢,下载保存了

祉遛吾 发表于 2026-2-12 16:00:42

收藏一下   不知道什么时候能用到

醋辛 发表于 2026-2-13 12:12:58

谢谢分享,辛苦了

啪炽 发表于 2026-2-14 03:02:46

东西不错很实用谢谢分享

利怡悦 发表于 2026-2-21 11:24:33

谢谢分享,辛苦了

撒阗奕 发表于 2026-2-22 21:55:45

很好很强大我过来先占个楼 待编辑

杠氯 发表于 2026-2-24 16:35:55

感谢分享

圣罩 发表于 2026-3-1 01:52:06

东西不错很实用谢谢分享
页: [1] 2
查看完整版本: Redis 7.0 新特性之maxmemory-clients:限制客户端内存总使用量