Closed Clownsw closed 5 months ago
之前的版本做过连续压测,压测停止后上涨的内存不会完全降到启动前水平,但也是会降到一个稳定水位。当时观察下来,这种不是无限增长,应该不是内存泄露。
这个版本我没有专门测过。
你可以把时间拉长,应用多更新多次,看看内存是不是每次都增长。如果不是每次都增长那应该就没这个问题。
目前来看是无限增长,但是时间较短我再观察观察
heqingpan @.***> 于 2024年5月23日周四 11:45写道:
之前的版本做过连续压测,压测停止后上涨的内存不会完全降到启动前水平,但也是会降到一个稳定水位。当时观察下来,这种不是无限增长,应该不是内存泄露。
这个版本我没有专门测过。
你可以把时间拉长,应用多更新多次,看看内存是不是每次都增长。如果不是每次都增长那应该就没这个问题。
— Reply to this email directly, view it on GitHub https://github.com/nacos-group/r-nacos/issues/94#issuecomment-2126176190, or unsubscribe https://github.com/notifications/unsubscribe-auth/AGYUJ5VEPXHUOFWE2MKJ7HLZDVQ4FAVCNFSM6AAAAABIEXGTUOVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCMRWGE3TMMJZGA . You are receiving this because you authored the thread.Message ID: @.***>
我后面抽空也测试验证一下。
另外docker的内存计数与在系统中直接运行的内容计数有一些差异。
我之前的压测内存数据是直接在系统运行的。
我后面抽空也测试验证一下。
另外docker的内存计数与在系统中直接运行的内容计数有一些差异。
我之前的压测内存数据是直接在系统运行的。
1
我想起一个差异点,v0.5.9开启鉴权后,鉴权的数据是放到缓存中,缓存过期时间是1个小时。也就是如果开启鉴权,需要等一小时后再观察内存。
后面考虑支持通过jwt鉴权。
我想起一个差异点,v0.5.9开启鉴权后,鉴权的数据是放到缓存中,缓存过期时间是1个小时。也就是如果开启鉴权,需要等一小时后再观察内存。
后面考虑支持通过jwt鉴权。
好的, 我再观察观察
我想起一个差异点,v0.5.9开启鉴权后,鉴权的数据是放到缓存中,缓存过期时间是1个小时。也就是如果开启鉴权,需要等一小时后再观察内存。
后面考虑支持通过jwt鉴权。
似乎就是某个地方有资源未删除, 每次都加很少但是每次重启程序都会增加, 反复注册应该可以复现
可以确定的是, 如果没有服务变动内存没有变化(一夜测试)
MemCache
这个没给map获取方法
我想起一个差异点,v0.5.9开启鉴权后,鉴权的数据是放到缓存中,缓存过期时间是1个小时。也就是如果开启鉴权,需要等一小时后再观察内存。
后面考虑支持通过jwt鉴权。
diff --git a/src/raft/cache/mod.rs b/src/raft/cache/mod.rs
index efdac9e..dae3e7f 100644
--- a/src/raft/cache/mod.rs
+++ b/src/raft/cache/mod.rs
@@ -1,27 +1,47 @@
// raft缓存数据
-use std::{convert::TryInto, sync::Arc};
+use std::{convert::TryInto, mem, ptr, sync::Arc};
+use std::collections::HashMap;
+use std::hash::Hash;
use actix::prelude::*;
use bean_factory::{bean, Inject};
-use inner_mem_cache::MemCache;
+use inner_mem_cache::{MemCache, MemCacheMode, TimeoutSet};
use ratelimiter_rs::RateLimiter;
use serde::{Deserialize, Serialize};
-use crate::common::constant::CACHE_TREE_NAME;
use crate::{common::limiter_utils::LimiterData, now_millis_i64, now_second_i32};
-
-use self::model::{CacheItemDo, CacheKey, CacheValue};
+use crate::common::constant::CACHE_TREE_NAME;
+use crate::config::core::{ConfigKey, ConfigValue};
use super::db::{
route::TableRoute,
table::{TableManager, TableManagerQueryReq, TableManagerReq, TableManagerResult},
};
+use self::model::{CacheItemDo, CacheKey, CacheValue};
+
pub mod api;
pub mod model;
pub mod route;
+pub trait PrintMap {
+ fn print_map(&self);
+}
+
+#[derive(Default)]
+pub struct MemCache2<K, T>
+ where
+ K: Eq + Hash,
+{
+ pub map: HashMap<K, (u64, T)>,
+ time_set: TimeoutSet<K>,
+ last_clear_time: u64,
+ try_clear_interval: u64,
+ pub mode: MemCacheMode,
+ pub time_out_fn: Option<Arc<dyn Fn(K, T) + Send + Sync>>,
+}
+
#[bean(inject)]
pub struct CacheManager {
cache: MemCache<CacheKey, CacheValue>,
@@ -67,14 +87,14 @@ impl CacheManager {
Ok(None)
}
}
- .into_actor(self)
- .map(
- |result: anyhow::Result<Option<Vec<KvPair>>>, act, _ctx| match act.do_load(result) {
- Ok(_) => {}
- Err(e) => log::error!("load cache info error,{}", e.to_string()),
- },
- )
- .wait(ctx);
+ .into_actor(self)
+ .map(
+ |result: anyhow::Result<Option<Vec<KvPair>>>, act, _ctx| match act.do_load(result) {
+ Ok(_) => {}
+ Err(e) => log::error!("load cache info error,{}", e.to_string()),
+ },
+ )
+ .wait(ctx);
Ok(())
}
@@ -250,35 +270,44 @@ impl Handler<CacheManagerReq> for CacheManager {
}
}
}
- .into_actor(self)
- .map(
- |inner_ctx: anyhow::Result<CacheManagerInnerCtx>, act, _| match inner_ctx? {
- CacheManagerInnerCtx::Get(key) => match act.cache.get(&key) {
- Ok(v) => Ok(CacheManagerResult::Value(v)),
- Err(_) => Ok(CacheManagerResult::None),
+ .into_actor(self)
+ .map(
+ |inner_ctx: anyhow::Result<CacheManagerInnerCtx>, act, _| match inner_ctx? {
+ CacheManagerInnerCtx::Get(key) => match act.cache.get(&key) {
+ Ok(v) => {
+ unsafe {
+ let a = &act.cache;
+ let a = a as *const MemCache<CacheKey, CacheValue> as * mut MemCache<CacheKey, CacheValue> as * mut MemCache2<CacheKey, CacheValue>;
+ let a = &*a;
+ dbg!(&a.map);
+ }
+
+ Ok(CacheManagerResult::Value(v))
+ }
+ Err(_) => Ok(CacheManagerResult::None),
+ },
+ CacheManagerInnerCtx::Remove(key) => {
+ act.cache.remove(&key);
+ Ok(CacheManagerResult::None)
+ }
+ CacheManagerInnerCtx::Set { key, value, ttl } => {
+ act.cache.set(key, value, ttl);
+ Ok(CacheManagerResult::None)
+ }
+ CacheManagerInnerCtx::NotifyChange { key, value } => {
+ match act.do_load(Ok(Some(vec![(key, value)]))) {
+ Ok(_) => {}
+ Err(err) => log::error!("do_load error :{}", err.to_string()),
+ };
+ Ok(CacheManagerResult::None)
+ }
+ CacheManagerInnerCtx::NotifyRemove { key } => {
+ let key = CacheKey::from_db_key(key)?;
+ act.cache.remove(&key);
+ Ok(CacheManagerResult::None)
+ }
},
- CacheManagerInnerCtx::Remove(key) => {
- act.cache.remove(&key);
- Ok(CacheManagerResult::None)
- }
- CacheManagerInnerCtx::Set { key, value, ttl } => {
- act.cache.set(key, value, ttl);
- Ok(CacheManagerResult::None)
- }
- CacheManagerInnerCtx::NotifyChange { key, value } => {
- match act.do_load(Ok(Some(vec![(key, value)]))) {
- Ok(_) => {}
- Err(err) => log::error!("do_load error :{}", err.to_string()),
- };
- Ok(CacheManagerResult::None)
- }
- CacheManagerInnerCtx::NotifyRemove { key } => {
- let key = CacheKey::from_db_key(key)?;
- act.cache.remove(&key);
- Ok(CacheManagerResult::None)
- }
- },
- );
+ );
Box::pin(fut)
}
}
我这两天观察一下缓存
[src\raft\cache\mod.rs:282:33] &a.map = {
CacheKey {
cache_type: UserSession,
key: "59377fb8cd404764b9e8e6a0468967345d7ab511fd8f4b5eaaa40aef2279e761",
}: (
1716545576317,
UserSession(
UserSession {
username: "admin",
nickname: Some(
"admin",
),
roles: [
"0",
],
extend_infos: {},
},
),
),
CacheKey {
cache_type: UserSession,
key: "28ef4b9442a04b1bb4a8753c9cd6a25264239b021d33484fa1cb6d49c6c43f54",
}: (
1716543156624,
UserSession(
UserSession {
username: "admin",
nickname: Some(
"admin",
),
roles: [
"0",
],
extend_infos: {},
},
),
),
CacheKey {
cache_type: UserSession,
key: "28a5a589328f4474b700773d891a56e32118109cac254ddfb4a6f382ace12ee5",
}: (
1716543086624,
UserSession(
UserSession {
username: "admin",
nickname: Some(
"admin",
),
roles: [
"0",
],
extend_infos: {},
},
),
),
CacheKey {
cache_type: String,
key: "Captcha_9111bfba230d4390b47814ce8db3f14a",
}: (
1716459471016,
String(
"MKVX",
),
),
CacheKey {
cache_type: UserSession,
key: "046d702237c4489693952b94d47c735d9756d29b76954b7fa5d4fc37a32129e2",
}: (
1716543061625,
UserSession(
UserSession {
username: "admin",
nickname: Some(
"admin",
),
roles: [
"0",
],
extend_infos: {},
},
),
),
CacheKey {
cache_type: UserSession,
key: "3e5aa3448d6e40adbf0afdb16083de2c3109be75d47240dc93da08be97ae695f",
}: (
1716543043624,
UserSession(
UserSession {
username: "admin",
nickname: Some(
"admin",
),
roles: [
"0",
],
extend_infos: {},
},
),
),
}
目前缓存只有在操作时尝试清除过期数据,没有加定时清理兜底(或者没生效,需要确认)。
正常使用过程至少每一个小时触发清理一次过期数据,这样就不会有一直没被清理的数据,缓存内存会保存在一定水位。
我这几天手头没电脑,后面我再加上定时间隔的兜底清理策略,应该就能避免这个现象。
目前缓存只有在操作时尝试清除过期数据,没有加定时清理兜底(或者没生效,需要确认)。
正常使用过程至少每一个小时触发清理一次过期数据,这样就不会有一直没被清理的数据,缓存内存会保存在一定水位。
我这几天手头没电脑,后面我再加上定时间隔的兜底清理策略,应该就能避免这个现象。
我明天部署一下上面代码的, 我再测试看看
目前缓存只有在操作时尝试清除过期数据,没有加定时清理兜底(或者没生效,需要确认)。 正常使用过程至少每一个小时触发清理一次过期数据,这样就不会有一直没被清理的数据,缓存内存会保存在一定水位。 我这几天手头没电脑,后面我再加上定时间隔的兜底清理策略,应该就能避免这个现象。
我明天部署一下上面代码的, 我再测试看看
打日志看了下, 不是 src/raft/cache/mod.rs
的缓存问题, 缓存一直都是三个没变化
[src/raft/cache/mod.rs:43:9] &(*p).map = {
CacheKey {
cache_type: UserSession,
key: "0e949368262c497295bad0ce34005b6f3bccdb2c0f18451bbf2c58618bfd7803",
}: (
1716603477672,
UserSession(
UserSession {
username: "admin",
nickname: Some(
"admin",
),
roles: [
"0",
],
extend_infos: {},
},
),
),
CacheKey {
cache_type: UserSession,
key: "604e65dae82a4ca7999b9347ef018474d66ab4bc74464a21af301afff5973287",
}: (
1716602976672,
UserSession(
UserSession {
username: "admin",
nickname: Some(
"admin",
),
roles: [
"0",
],
extend_infos: {},
},
),
),
CacheKey {
cache_type: UserSession,
key: "ace14193bca84717beb0690dd82429b0dfbb6ec98882481584554e870dbe3ec8",
}: (
1716602970672,
UserSession(
UserSession {
username: "admin",
nickname: Some(
"admin",
),
roles: [
"0",
],
extend_infos: {},
},
),
),
}
收到,这个我过两天回家之后再看看。
你有用v0.5.7做相同的操作对比过吗? 想确认是不是这两个版本引入的问题。
没有的话我回去再做对比好了
收到,这个我过两天回家之后再看看。
你有用v0.5.7做相同的操作对比过吗? 想确认是不是这两个版本引入的问题。
没有的话我回去再做对比好了
v0.5.7一样有这个问题
那可能就不是这个原因了。
我回去之后再看看。
对了,你是用什么客户端版本?
对了,你是用什么客户端版本?
r-nacos: v0.5.9
nacos-client: 2.2.4
周末outing回来太晚来不及复现确认,这几天我再抽空复现确认问题。
周末outing回来太晚来不及复现确认,这几天我再抽空复现确认问题。
好的, 老哥
周末outing回来太晚来不及复现确认,这几天我再抽空复现确认问题。
可以确定的是一定有地方泄露了, 因为今天我回来查看服务器内存已经增长到18mb
周末outing回来太晚来不及复现确认,这几天我再抽空复现确认问题。
我通过jemalloc的heap dump看了下, 可惜对rust async基本不支持, 只能看到poll 1.pdf
我的想法先检查应用自身的问题。
计划增加一个dump metrics接口,支持获取r-nacos运行时引用集合(HashMap ,Vec)中的元素数量,看看是否是应用中的那个地方没有释放内存。
已确认存在这个问题,并通过批量链接工具可以稳定复现问题。
初步确认和grpc长链接关闭后对应的资源没能自动释放有关,后面几天会优先处理这个 bug。
每个链接占用的内存都不大,内存会缓慢增加,一般场景运行几天可能变化不明显。
正在使用的用户,如果发现问题可以通过重启临时解决,也可以设置定时重启(每天或每周重启一次即可)。
已确认存在这个问题,并通过批量链接工具可以稳定复现问题。
初步确认和grpc长链接关闭后对应的资源没能自动释放有关,后面几天会优先处理这个 bug。
每个链接占用的内存都不大,内存会缓慢增加,一般场景运行几天可能变化不明显。
正在使用的用户,如果发现问题可以通过重启临时解决,也可以设置定时重启(每天或每周重启一次即可)。
好的, 麻烦了老哥
对r-nacos做多批次重联测试,发起前几次内存增长比较明显,后面内存收敛趋于稳定,基本不增长。
最新的结论为: grpc重复链接不会导致内存无限增长。
测试版本:v0.5.10,操作系统:MacOS x86 , 每批次并发100个链接。 测试结果如下:
专用内存(M) | 实际内存(M) | 重联批次 |
---|---|---|
5.9 | 10.8 | 0 |
9.4 | 15.4 | 1 |
10.8 | 16.8 | 2 |
12.2 | 18.1 | 3 |
13.0 | 19.0 | 4 |
13.5 | 19.5 | 5 |
13.9 | 19.8 | 6 |
14.1 | 20.0 | 7 |
14.4 | 20.3 | 8 |
14.4 | 20.4 | 9 |
14.5 | 20.4 | 10 |
15.3 | 19.0 | 11 |
15.5 | 19.1 | 12 |
15.8 | 19.5 | 13 |
16.0 | 19.7 | 14 |
16.0 | 19.7 | 15 |
16.0 | 19.7 | 16 |
16.0 | 19.8 | 17 |
16.1 | 19.8 | 18 |
16.2 | 19.8 | 19 |
16.2 | 19.9 | 20 |
16.3 | 19.9 | 21 |
16.3 | 19.9 | 22 |
16.3 | 19.9 | 23 |
同时在本地对验证微调后的版本做同样的测试,测试结果如下:
专用内存(M) | 实际内存(M) | 重联批次 |
---|---|---|
5.9 | 10.7 | 0 |
10.1 | 16.0 | 1 |
11.9 | 17.9 | 2 |
13 | 19 | 3 |
13.5 | 19.5 | 4 |
14.2 | 20.2 | 5 |
14.9 | 20.8 | 6 |
14.9 | 20.9 | 7 |
15.2 | 21.2 | 8 |
15.3 | 21.3 | 9 |
15.3 | 21.3 | 10 |
15.4 | 21.4 | 11 |
15.4 | 21.4 | 12 |
15.4 | 21.4 | 13 |
从测试结果可以推测: r-nacos应用运行过程中,部分集合(HashMap,Vec 等)会因数据量增加而自动扩容(占用的内存变大),而元素移除时不会自动缩容,集合内存保持给后继的数据使用。
当集合容量到大一定量级时,基本足够业务数据的循环使用,这时容量基本也就不需要再扩容,内存趋于稳定。
上面的测试只是单纯的测试 grpc 批量链接与重联,没有涉及业务。
还需要对实际业务场景做多次重启看看内存是否也会收敛,如果能收敛那么就没有问题。
上面的测试只是单纯的测试 grpc 批量链接与重联,没有涉及业务。
还需要对实际业务场景做多次重启看看内存是否也会收敛,如果能收敛那么就没有问题。
好的, 老哥, 我这边在跑一段时间看看
从测试结果可以推测: r-nacos应用运行过程中,部分集合(HashMap,Vec 等)会因数据量增加而自动扩容(占用的内存变大),而元素移除时不会自动缩容,集合内存保持给后继的数据使用。
当集合容量到大一定量级时,基本足够业务数据的循环使用,这时容量基本也就不需要再扩容,内存趋于稳定。
言之有理, 仔细一想确实是r-nacos
的连接断开也不会立即删除, 如果此时再来新的连接可能会触发扩容, 这个扩容不会缩容所以可能是我看到的内存增长的原因。我这边再观察观察。
r-nacos + mimalloc + musl
, 一个小时的正常开发环境(与之前一样 四个服务, 四个配置, 一百个服务配置), 我的内存稳定在13.6
- 13.8
这里是想说明切换到mimalloc后内存稳定水位更低吗?
关于提换底层的内存分配器影响面比较大,需要通过充分测试比较之后才好确定是否应该切换。
这里是想说明切换到mimalloc后内存稳定水位更低吗?
关于提换底层的内存分配器影响面比较大,需要通过充分测试比较之后才好确定是否应该切换。
我这里简单切换了一下mimalloc
确实比glibc
更稳定, 波动更小, 我就是想跟老哥提一下有时间可以测试一下考虑切换到mimalloc
收到,我后面有空时会试试,不过时间上应该会晚一点。
收到,我后面有空时会试试,不过时间上应该会晚一点。
好的, 麻烦了老哥
目前来看这是个误解
r-nacos: v0.5.9 两个程序(两个配置), 二十个服务左右 反复注册(开发需要来回重启服务, 有个几十次), 从最开始6mb左右到12mb, 中间即使停止服务内存也不会释放 疑似有资源泄露未释放