ITPub博客

首页 > 数字化转型 > 移动办公 > 表示按 maxmemory 的百分比限制客户端内存

表示按 maxmemory 的百分比限制客户端内存

投稿 移动办公 作者:ecclw459 时间:2026-01-13 10:45:15 0 删除 编辑

这一点是在 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  fcehy.cn(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;
}

实现细节

当通过 CONFIG SET 命令调整 maxmemory-clients 的值时,会调用 applyClientMaxMemoryUsage 函数进行处理。



static
 
int
 
applyClientMaxMemoryUsage
(
const
 
char
 **err)
 
{


    ...
    if  (server.maxmemory_clients != 0 )
        initServerClientMemUsageBuckets();
    ...
    if  (server.maxmemory_clients == 0 )
        freeServerClientMemUsageBuckets();
    return  1 ;
}

可以看到,当 server.maxmemory_clients 的值不为 0,会调用 initServerClientMemUsageBuckets() 。



void
 
initServerClientMemUsageBuckets
()
 
{


    if  (server.client_mem_usage_buckets)
        return ;
    server.client_mem_usage_buckets = zmalloc(sizeof (clientMemUsageBucket)*CLIENT_MEM_USAGE_BUCKETS);
    for  (int  j = 0 ; j < CLIENT_MEM_USAGE_BUCKETS; j++) {
        server.client_mem_usage_oxerw.cn buckets[j].mem_usage_sum = 0 ;
        server.client_mem_usage_buckets[j].clients = listCreate();
    }
}

该函数用于初始化 server.client_mem_usage_buckets 数组,数组长度由宏 CLIENT_MEM_USAGE_BUCKETS 决定,默认 19。

每个元素表示一个桶(bucket)。 每个桶维护两类信息:

  • mem_usage_sum:该桶内所有客户端的内存占用总和。
  • clients:属于该桶的客户端列表。

当客户端内存发生变化时,Redis 会通过 updateClientMemUsageAndBucket 更新该客户端的内存使用情况(客户端使用的内存,对应 client list 输出中的 tot-mem ),并根据内存大小将客户端分配到对应的桶中:

  • 小于 32KB  的客户端进入 0 号桶;
  • 32KB~64KB  的客户端进入 1 号桶;
  • 之后每个桶的范围按 2 倍递增;
  • ≥ 4GB  的客户端进入 18 号桶。

此外,Redis 还会通过 clientsCron() 周期性地更新部分客户端的内存使用情况。  clientsCron()  的执行频率由 server.hz 控制,默认每秒 10 次。

适用的客户端类型

需要注意的是,并非所有客户端都会被统计内存并参与驱逐。具体判断逻辑如下:



int
 
clientEvictionAllowed
(client *c)
 
{


    if  (server.maxmemory_clients == 0  || c->flags & CLIENT_NO_EVICT) {
        return  0 ;
    }
    int  type = getClientType(c);
    return  (type == CLIENT_TYPE_NORMAL || type == CLIENT_TYPE_PUBSUB);
}

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

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

客户端驱逐细节

客户端驱逐是在 evictClients 函数中实现的。



void
 
evictClients
(
void
)
 
{


    // 如果 client_mem_usage_buckets 没被初始化,则直接返回
    if  (!server.client_mem_usage_buckets)
        return ;
    // 从最大客户端内存桶开始驱逐
    int  curr_bucket =qainw.cn CLIENT_MEM_USAGE_BUCKETS-1 ;
    listIter bucket_iter;
    listRewind(server.client_mem_usage_buckets[curr_bucket].clients, &bucket_iter);
    // 获取客户端允许使用的最大内存
    size_t  client_eviction_limit = getClientEvictionLimit();
    if  (client_eviction_limit == 0 )
        return ;
    // 循环驱逐,直到客户端总内存降到阈值以下或所有可驱逐客户端已释放
    while  (server.stat_clients_type_memory[CLIENT_TYPE_NORMAL] +
           server.stat_clients_type_memory[CLIENT_TYPE_PUBSUB] >= 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 < 0 ) {
                serverLog(LL_WARNING, "Over client maxmemory after evicting all evictable clients" );
                break ;
            }
            listRewind rgenq.cn(server.client_mem_usage_buckets[curr_bucket].clients, &bucket_iter);
        }
    }
}

可以看到,该函数从最大内存桶开始驱逐,优先淘汰占用内存最多的客户端。

对于被驱逐的客户端,会在日志中打印以下内容。



* Evicting client: id=993566 addr=243.247.151.0:46084 laddr=172.17.0.2:7379 fd=774 name= age=6 idle=1 flags=N db=0 sub=0 psub=0 ssub=0 multi=-1 qbuf=0 qbuf-free=20474 argv-mem=0 multi-mem=0 rbs=4096 rbp=0 obl=0 oll=1 omem=3145752 tot-mem=3171096 events=rw cmd=get user=default redir=-1 resp=2


该函数的调用场景主要有两个:

  1. beforeSleep :在处理完本轮所有命令、即将进入下一轮事件循环阻塞前执行。该阶段会处理客户端读写与阻塞状态、集群与复制维护、Key 过期、AOF 刷盘、异步释放客户端,以及客户端内存驱逐等操作。
  2. processCommand(client *c) :在完整读取并解析一条客户端命令后调用,是所有命令的必经路径,用于执行命令合法性校验、ACL 权限检查、集群重定向判断、客户端内存限制、服务器内存淘汰、只读从库校验等一系列前置检查。

不通过,直接拦截。

4️⃣ Skill Server 执行真实逻辑

比如:

  • 查数据库
  • 调内部 API
  • 读取文件

5️⃣ 返回结构化结果

{  "id": "u_123",  "name": "Alice",  "email": "test@example.com",  "level": "VIP"}

6️⃣ Claude 把结果“吃”回上下文

注意这里不是“展示给用户”,而是: 作为新上下文继续推理。Claude 可能会接着分析、对比、总结,最后才生成自然语言回复。

2.6 一个最小可运行的 MCP Skill 示例

下面这个示例非常简单,但已经具备 MCP 的完整形态。

from mcp.server import Server
server = Server("user_service")
@server.tool()
def get_user_by_email(email: str) -derqd.cn> dict:    """    Query user information by email    """    if email == "test@example.com":
        return {            "id": "u_123",            "name": "Alice",            "email": email,            "level": "VIP"
        }
    return {}
server.run()

你会发现:

  • 没有 prompt
  • 没有 AI 逻辑
  • 只是纯能力定义

但 Claude 会自动理解:

  • 什么时候用它
  • 怎么用
  • 用完之后怎么继续思考

2.7 为什么 MCP 对 Agent 特别重要?

如果你做过 Agent,一定踩过这些坑:

  • 流程写死
  • 状态难维护
  • 一步失败全盘崩
  • 能力越多越混乱

MCP 的好处在于:

  • Agent 不需要提前写死流程,模型可以动态规划。

一个典型 Agent 行为:

用户需求:
“帮我分析这个用户最近的消费情况,并给出建议。”

Claude 可能会:

  • 调用 get_user_profile
  • 调用 search_orders
  • 汇总数据
  • 给出分析和建议
  • 你不需要写 if/else 流程。

2.8 MCP 和 Function Calling 的本质区别

很多人会问:

  • “这和 OpenAI 的 Function Calling 有什么区别?”

简单说一句:

既然定位到了问题所在,解决起来就比较简单了。我决定升级相关组件到更稳定的版本。首先在项目的pom.xml文件中修改版本属性配置:

<properties>
    <!-- PageHelper 版本 -->
    <pagehelper.boot.version>1.4.6</pagehelper.boot.version>
    
    <!-- MyBatis 核心库版本 -->
    <mybatis.version>3.5.19 mdmqa.cn</mybatis.version>
    
    <!-- MyBatis Spring 集成版本 -->
    <mybatis.spring.version>2.1.2</mybatis.spring.version></properties>

然后在dependencies中添加或更新对应的依赖:

<dependencies>
    <!-- MyBatis Spring Boot Starter -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>hpkql.cn 2.3.2</version>
    </dependency>
    
    <!-- MyBatis 核心库 -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>${mybatis.version}</version>
    </dependency>
    
    <!-- MyBatis Spring 集成 -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>${mybatis.spring.version}</version>
    </dependency>
    
    <!-- PageHelper rqnin.cn分页插件 -->
    <dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper-spring-boot-starter</artifactId>
        <version>${pagehelper.boot.version}</version>
    </dependency></dependencies>

升级完成后,连接缓慢的问题彻底消失了,数据库操作的响应速度恢复正常,整个项目的性能都有了明显提升,开发和测试的效率也大大提高了。

问题复盘与经验总结

排查思路优化

回顾整个排查过程,虽然走了一些弯路,但也让我积累了宝贵的经验。首先,排查思路要遵循先易后难、逐层深入的原则,从外部依赖开始,逐步深入到数据库层面,最后才关注框架本身,这样可以避免一开始就陷入复杂的底层问题。其次,复现问题是定位根因的关键,通过新建项目复现问题,我才能够排除业务代码的干扰,快速缩小问题范围,这是整个排查过程中最重要的转折点。


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70048957/viewspace-3107753/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2026-01-07

  • 博文量
    108
  • 访问量
    102113