首页 > 数字化转型 > 移动办公 > 表示按 maxmemory 的百分比限制客户端内存
这一点是在
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)。 每个桶维护两类信息:
当客户端内存发生变化时,Redis 会通过
updateClientMemUsageAndBucket
更新该客户端的内存使用情况(客户端使用的内存,对应
client list
输出中的
tot-mem
),并根据内存大小将客户端分配到对应的桶中:
此外,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);
}
可以看到,只有满足以下条件的客户端才会被统计内存并参与驱逐:
maxmemory_clients
不为 0。
CLIENT NO-EVICT ON
命令显式关闭驱逐。
客户端驱逐是在
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
该函数的调用场景主要有两个:
beforeSleep
:在处理完本轮所有命令、即将进入下一轮事件循环阻塞前执行。该阶段会处理客户端读写与阻塞状态、集群与复制维护、Key 过期、AOF 刷盘、异步释放客户端,以及客户端内存驱逐等操作。
processCommand(client *c)
:在完整读取并解析一条客户端命令后调用,是所有命令的必经路径,用于执行命令合法性校验、ACL 权限检查、集群重定向判断、客户端内存限制、服务器内存淘汰、只读从库校验等一系列前置检查。不通过,直接拦截。
4️⃣ Skill Server 执行真实逻辑
比如:
5️⃣ 返回结构化结果
{ "id": "u_123", "name": "Alice", "email": "test@example.com", "level": "VIP"}
6️⃣ Claude 把结果“吃”回上下文
注意这里不是“展示给用户”,而是: 作为新上下文继续推理。Claude 可能会接着分析、对比、总结,最后才生成自然语言回复。
下面这个示例非常简单,但已经具备 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()
你会发现:
但 Claude 会自动理解:
如果你做过 Agent,一定踩过这些坑:
MCP 的好处在于:
一个典型 Agent 行为:
用户需求: “帮我分析这个用户最近的消费情况,并给出建议。”
Claude 可能会:
很多人会问:
简单说一句:
既然定位到了问题所在,解决起来就比较简单了。我决定升级相关组件到更稳定的版本。首先在项目的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/ ,如需转载,请注明出处,否则将追究法律责任。