nacos-group / r-nacos

Nacos server re-implemented in Rust.
https://r-nacos.github.io/docs/
Apache License 2.0
973 stars 104 forks source link

疑似资源不释放, 导致内存只涨不降 #94

Closed Clownsw closed 5 months ago

Clownsw commented 5 months ago

r-nacos: v0.5.9 两个程序(两个配置), 二十个服务左右 反复注册(开发需要来回重启服务, 有个几十次), 从最开始6mb左右到12mb, 中间即使停止服务内存也不会释放 疑似有资源泄露未释放

image

heqingpan commented 5 months ago

之前的版本做过连续压测,压测停止后上涨的内存不会完全降到启动前水平,但也是会降到一个稳定水位。当时观察下来,这种不是无限增长,应该不是内存泄露。

这个版本我没有专门测过。

你可以把时间拉长,应用多更新多次,看看内存是不是每次都增长。如果不是每次都增长那应该就没这个问题。

Clownsw commented 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: @.***>

heqingpan commented 5 months ago

我后面抽空也测试验证一下。

另外docker的内存计数与在系统中直接运行的内容计数有一些差异。

我之前的压测内存数据是直接在系统运行的。

Clownsw commented 5 months ago

我后面抽空也测试验证一下。

另外docker的内存计数与在系统中直接运行的内容计数有一些差异。

我之前的压测内存数据是直接在系统运行的。

1

heqingpan commented 5 months ago

我想起一个差异点,v0.5.9开启鉴权后,鉴权的数据是放到缓存中,缓存过期时间是1个小时。也就是如果开启鉴权,需要等一小时后再观察内存。

后面考虑支持通过jwt鉴权。

Clownsw commented 5 months ago

我想起一个差异点,v0.5.9开启鉴权后,鉴权的数据是放到缓存中,缓存过期时间是1个小时。也就是如果开启鉴权,需要等一小时后再观察内存。

后面考虑支持通过jwt鉴权。

好的, 我再观察观察

Clownsw commented 5 months ago

我想起一个差异点,v0.5.9开启鉴权后,鉴权的数据是放到缓存中,缓存过期时间是1个小时。也就是如果开启鉴权,需要等一小时后再观察内存。

后面考虑支持通过jwt鉴权。

image

似乎就是某个地方有资源未删除, 每次都加很少但是每次重启程序都会增加, 反复注册应该可以复现

Clownsw commented 5 months ago

image

Clownsw commented 5 months ago

可以确定的是, 如果没有服务变动内存没有变化(一夜测试)

Clownsw commented 5 months ago

MemCache这个没给map获取方法

Clownsw commented 5 months ago

我想起一个差异点,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)
     }
 }

我这两天观察一下缓存

Clownsw commented 5 months ago
[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: {},
            },
        ),
    ),
}
heqingpan commented 5 months ago

目前缓存只有在操作时尝试清除过期数据,没有加定时清理兜底(或者没生效,需要确认)。

正常使用过程至少每一个小时触发清理一次过期数据,这样就不会有一直没被清理的数据,缓存内存会保存在一定水位。

我这几天手头没电脑,后面我再加上定时间隔的兜底清理策略,应该就能避免这个现象。

Clownsw commented 5 months ago

目前缓存只有在操作时尝试清除过期数据,没有加定时清理兜底(或者没生效,需要确认)。

正常使用过程至少每一个小时触发清理一次过期数据,这样就不会有一直没被清理的数据,缓存内存会保存在一定水位。

我这几天手头没电脑,后面我再加上定时间隔的兜底清理策略,应该就能避免这个现象。

我明天部署一下上面代码的, 我再测试看看

Clownsw commented 5 months ago

目前缓存只有在操作时尝试清除过期数据,没有加定时清理兜底(或者没生效,需要确认)。 正常使用过程至少每一个小时触发清理一次过期数据,这样就不会有一直没被清理的数据,缓存内存会保存在一定水位。 我这几天手头没电脑,后面我再加上定时间隔的兜底清理策略,应该就能避免这个现象。

我明天部署一下上面代码的, 我再测试看看

打日志看了下, 不是 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: {},
            },
        ),
    ),
}
heqingpan commented 5 months ago

收到,这个我过两天回家之后再看看。

你有用v0.5.7做相同的操作对比过吗? 想确认是不是这两个版本引入的问题。

没有的话我回去再做对比好了

Clownsw commented 5 months ago

收到,这个我过两天回家之后再看看。

你有用v0.5.7做相同的操作对比过吗? 想确认是不是这两个版本引入的问题。

没有的话我回去再做对比好了

v0.5.7一样有这个问题

heqingpan commented 5 months ago

那可能就不是这个原因了。

我回去之后再看看。

heqingpan commented 5 months ago

对了,你是用什么客户端版本?

Clownsw commented 5 months ago

对了,你是用什么客户端版本?

r-nacos: v0.5.9 nacos-client: 2.2.4

heqingpan commented 5 months ago

周末outing回来太晚来不及复现确认,这几天我再抽空复现确认问题。

Clownsw commented 5 months ago

周末outing回来太晚来不及复现确认,这几天我再抽空复现确认问题。

好的, 老哥

Clownsw commented 5 months ago

周末outing回来太晚来不及复现确认,这几天我再抽空复现确认问题。

可以确定的是一定有地方泄露了, 因为今天我回来查看服务器内存已经增长到18mb

Clownsw commented 5 months ago

周末outing回来太晚来不及复现确认,这几天我再抽空复现确认问题。

我通过jemalloc的heap dump看了下, 可惜对rust async基本不支持, 只能看到poll 1.pdf

heqingpan commented 5 months ago

我的想法先检查应用自身的问题。

计划增加一个dump metrics接口,支持获取r-nacos运行时引用集合(HashMap ,Vec)中的元素数量,看看是否是应用中的那个地方没有释放内存。

heqingpan commented 5 months ago

已确认存在这个问题,并通过批量链接工具可以稳定复现问题。

初步确认和grpc长链接关闭后对应的资源没能自动释放有关,后面几天会优先处理这个 bug。

每个链接占用的内存都不大,内存会缓慢增加,一般场景运行几天可能变化不明显。

正在使用的用户,如果发现问题可以通过重启临时解决,也可以设置定时重启(每天或每周重启一次即可)。

Clownsw commented 5 months ago

已确认存在这个问题,并通过批量链接工具可以稳定复现问题。

初步确认和grpc长链接关闭后对应的资源没能自动释放有关,后面几天会优先处理这个 bug。

每个链接占用的内存都不大,内存会缓慢增加,一般场景运行几天可能变化不明显。

正在使用的用户,如果发现问题可以通过重启临时解决,也可以设置定时重启(每天或每周重启一次即可)。

好的, 麻烦了老哥

heqingpan commented 5 months ago

对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

image


同时在本地对验证微调后的版本做同样的测试,测试结果如下:

专用内存(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

image

heqingpan commented 5 months ago

从测试结果可以推测: r-nacos应用运行过程中,部分集合(HashMap,Vec 等)会因数据量增加而自动扩容(占用的内存变大),而元素移除时不会自动缩容,集合内存保持给后继的数据使用。

当集合容量到大一定量级时,基本足够业务数据的循环使用,这时容量基本也就不需要再扩容,内存趋于稳定。

heqingpan commented 5 months ago

上面的测试只是单纯的测试 grpc 批量链接与重联,没有涉及业务。

还需要对实际业务场景做多次重启看看内存是否也会收敛,如果能收敛那么就没有问题。

Clownsw commented 5 months ago

上面的测试只是单纯的测试 grpc 批量链接与重联,没有涉及业务。

还需要对实际业务场景做多次重启看看内存是否也会收敛,如果能收敛那么就没有问题。

好的, 老哥, 我这边在跑一段时间看看

Clownsw commented 5 months ago

从测试结果可以推测: r-nacos应用运行过程中,部分集合(HashMap,Vec 等)会因数据量增加而自动扩容(占用的内存变大),而元素移除时不会自动缩容,集合内存保持给后继的数据使用。

当集合容量到大一定量级时,基本足够业务数据的循环使用,这时容量基本也就不需要再扩容,内存趋于稳定。

言之有理, 仔细一想确实是r-nacos的连接断开也不会立即删除, 如果此时再来新的连接可能会触发扩容, 这个扩容不会缩容所以可能是我看到的内存增长的原因。我这边再观察观察。

Clownsw commented 5 months ago

r-nacos + mimalloc + musl, 一个小时的正常开发环境(与之前一样 四个服务, 四个配置, 一百个服务配置), 我的内存稳定在13.6 - 13.8

image

heqingpan commented 5 months ago

这里是想说明切换到mimalloc后内存稳定水位更低吗?

关于提换底层的内存分配器影响面比较大,需要通过充分测试比较之后才好确定是否应该切换。

Clownsw commented 5 months ago

这里是想说明切换到mimalloc后内存稳定水位更低吗?

关于提换底层的内存分配器影响面比较大,需要通过充分测试比较之后才好确定是否应该切换。

我这里简单切换了一下mimalloc确实比glibc更稳定, 波动更小, 我就是想跟老哥提一下有时间可以测试一下考虑切换到mimalloc

heqingpan commented 5 months ago

收到,我后面有空时会试试,不过时间上应该会晚一点。

Clownsw commented 5 months ago

收到,我后面有空时会试试,不过时间上应该会晚一点。

好的, 麻烦了老哥

Clownsw commented 5 months ago

目前来看这是个误解