首页 新闻 会员 周边

EFcore多线程查询,导致内存飙高,线程结束后内存没有释放

1
[待解决问题]

前几天发现公司一个服务占用了大量的内存,有时候达到7-8G,服务直接就挂了。后面发现这个服务里一张7-8w数据的表用efcore查询,随着高并发的访问内存飙高,访问结束后一定量的内存没有得到释放,后面直接就飚到了3个G。后面我本地用控制台做了多线程的efcore的查询,发现线程结束后GC并没有回收完数据,有时候剩下还几个G的数据都在。有哪位大佬碰到过这个问题吗?

Mrs_ZMx的主页 Mrs_ZMx | 菜鸟二级 | 园豆:280
提问于:2019-12-11 14:52
< >
分享
所有回答(5)
0

建议检查一下有没有将使用 DbContext 的类注册为 Singleton ?

dudu | 园豆:30994 (高人七级) | 2019-12-11 14:55

没,直接用的using。

支持(0) 反对(0) Mrs_ZMx | 园豆:280 (菜鸟二级) | 2019-12-11 15:01

@Mrs_ZMx: 建议看一下 EF Core 的告警日志,可能是某些 LINQ 造成了加载全表数据在内存中查询

支持(0) 反对(0) dudu | 园豆:30994 (高人七级) | 2019-12-11 15:05

@dudu: 我控制台在本地循环用线程池开10个线程,每个线程来拖一张1W的表,内存直接飚到4G,最后有2G的内存一直没被GC,找了很久,找不到问题。

支持(0) 反对(0) Mrs_ZMx | 园豆:280 (菜鸟二级) | 2019-12-11 15:05

@dudu: 不开线程,一切正常。

支持(0) 反对(0) Mrs_ZMx | 园豆:280 (菜鸟二级) | 2019-12-11 15:08

@Mrs_ZMx: 建议最好能提供重现这个问题的实例代码

支持(0) 反对(0) dudu | 园豆:30994 (高人七级) | 2019-12-11 18:17

@dudu: static void Main(string[] args)
{
for (int i = 0; i < 400; i++)
{
var task = new Task(() => { var d = efoscoreContext.Project.ToList(); });
task.Start();
}
while (true)
{
Console.WriteLine("主线程的id:" + Thread.CurrentThread.ManagedThreadId + "--现在存在的线程数;" + Process.GetCurrentProcess().Threads.Count);
Thread.Sleep(TimeSpan.FromSeconds(1));
}
}

支持(0) 反对(0) Mrs_ZMx | 园豆:280 (菜鸟二级) | 2019-12-11 18:45

@dudu: static void Main(string[] args)
{
for (int i = 0; i < 400; i++)
{
var task = new Task(() =>
{
using (efoscoreContext efoscoreContext = new efoscoreContext(new DbContextOptionsBuilder<efoscoreContext>().UseSqlServer("******;").Options))
{
var d = efoscoreContext.Project.ToList();
}
});
task.Start();
}
while (true)
{
Console.WriteLine("主线程的id:" + Thread.CurrentThread.ManagedThreadId + "--现在存在的线程数;" + Process.GetCurrentProcess().Threads.Count);
Thread.Sleep(TimeSpan.FromSeconds(1));
}
}

支持(0) 反对(0) Mrs_ZMx | 园豆:280 (菜鸟二级) | 2019-12-11 18:52

@dudu: 测试代码就是这样,我拉project这张表,执行过程会GC,但是线程结束后,一部分内存回收不了

支持(0) 反对(0) Mrs_ZMx | 园豆:280 (菜鸟二级) | 2019-12-11 18:53

@Mrs_ZMx: 建议试试
1)使用 WeakReference

WeakReference d = efoscoreContext.Project.ToList();

2)使用 GC.Collect()

GC.Collect();
while (true)
{
    //..
}
支持(0) 反对(0) dudu | 园豆:30994 (高人七级) | 2019-12-12 10:12

@dudu: WeakReference好像不能使用在efoscoreContext.Project.ToList()上,GC.Collect是可以的,但这影响性能.我等下准备写篇博客,把详细的问题的过程陈述过来。现在我只有重启服务来解决问题了

支持(0) 反对(0) Mrs_ZMx | 园豆:280 (菜鸟二级) | 2019-12-12 10:30

@Mrs_ZMx: 等你的博文

支持(0) 反对(0) dudu | 园豆:30994 (高人七级) | 2019-12-12 10:44

@dudu: 简单写了一篇随笔

支持(0) 反对(0) Mrs_ZMx | 园豆:280 (菜鸟二级) | 2019-12-12 11:27

@dudu: https://www.cnblogs.com/tcdbk/p/12028106.html

支持(0) 反对(0) Mrs_ZMx | 园豆:280 (菜鸟二级) | 2019-12-12 11:29

@Mrs_ZMx: 下面的代码测试没这个问题

while (true)
{
    for (int i = 1; i < 100; i++)
    {
        var t = new Task(() =>
        {
            var loh = new byte[1024 * 1024 * 1024];
        });

        t.Start();
    }

    Thread.Sleep(TimeSpan.FromSeconds(5));
}
支持(0) 反对(0) dudu | 园豆:30994 (高人七级) | 2019-12-12 13:22

@dudu: 我知道一般线程结束后,内存是会回收了的,我是在线程里面进行Efcore查询发现内存释放不完,肯定是什么原因导致的。。你试下在Task里面同efcore来拖一张表

支持(0) 反对(0) Mrs_ZMx | 园豆:280 (菜鸟二级) | 2019-12-12 13:51

@Mrs_ZMx: 我这里能重现这个问题了。

支持(0) 反对(0) dudu | 园豆:30994 (高人七级) | 2019-12-13 22:24

@Mrs_ZMx: 可能与线程池的线程数不够用有关,建议通过下面的代码给线程池预热试试

if (ThreadPool.SetMinThreads(200, 20))
{
    Parallel.For(0, 200, a => System.Threading.Thread.Sleep(1000));
}
支持(0) 反对(0) dudu | 园豆:30994 (高人七级) | 2019-12-13 23:21

@Mrs_ZMx: 我这里的测试代码在内存占用达到1.2G左右的时候就会被回收

class Program
{
    static void Main(string[] args)
    {
        if (ThreadPool.SetMinThreads(200, 20))
        {
            Parallel.For(0, 200, a => System.Threading.Thread.Sleep(1000));
        }

        for (int i = 0; i < 400; i++)
        {
            Task.Run(async () =>
            {
                var options = new DbContextOptionsBuilder<BlogDbContext>()
                    .UseSqlServer("server=.;database=cnblogs;integrated security=true;").Options;

                using (var db = new BlogDbContext(options))
                {
                    var d = await db.Set<BlogPost>().AsNoTracking().ToListAsync();
                }
            });
        }

        while (true)
        {
            Console.WriteLine("主线程的id:" + Thread.CurrentThread.ManagedThreadId + "--现在存在的线程数;" + Process.GetCurrentProcess().Threads.Count);
            Thread.Sleep(TimeSpan.FromSeconds(1));
        }
    }
}
支持(0) 反对(0) dudu | 园豆:30994 (高人七级) | 2019-12-13 23:24

@Mrs_ZMx: "我知道一般线程结束后,内存是会回收了的",如果线程不够用呢?

支持(0) 反对(0) dudu | 园豆:30994 (高人七级) | 2019-12-13 23:33

@dudu:没看到这段消息。。你不觉得到了1.2G其实也是很高了吗?而且即使你结束了,1.2G还在高居不下 。。

支持(0) 反对(0) Mrs_ZMx | 园豆:280 (菜鸟二级) | 2019-12-17 19:41

@dudu: 这种线程预热是什么原理,ThreadPool.SetMinThreads(200, 20)这个设置了,系统不会默认内置200个工作线程吗

支持(0) 反对(0) DHclly | 园豆:206 (菜鸟二级) | 2023-07-05 09:20

@DHclly: 建议专门发个提问

支持(0) 反对(0) dudu | 园豆:30994 (高人七级) | 2023-07-05 09:54
0

你可以试试 GC.Collect() 强制垃圾回收

左眼水星 | 园豆:113 (初学一级) | 2019-12-11 15:10

GC.Collect()试过确实有点用,但是这个强制回收会停止线程,强制GC。感觉没有找到问题的源头。刚刚开会,又花掉了下午的时间

支持(0) 反对(0) Mrs_ZMx | 园豆:280 (菜鸟二级) | 2019-12-11 16:45

GC.Collect()会解决问题,这也反面证明了确实是内存没有回收,但不能使用GC.Collect()来强制执行垃圾回收,因为这个方法会停掉当前线程来执行GC。明天就要演示了,有点急啊。。。

支持(0) 反对(0) Mrs_ZMx | 园豆:280 (菜鸟二级) | 2019-12-11 16:56

@Mrs_ZMx: 看你上面贴的代码如果一直查询的是同一张表,那最好先调用一下之前对象的Dispose(你代码中的变量d)然后再赋新的值应该会好点;

还有一个原因你这样会在短时间内创建大量线程,会占用很多内存。如果是上面的代码,你要的只是Project表的最新数据吧。这方面觉得也可以优化下。

支持(0) 反对(0) 左眼水星 | 园豆:113 (初学一级) | 2019-12-11 19:49

@左眼水星:我可以(试过)休眠1秒(这一秒已经完全可以完成一个线程)开一个线程,还是会造成很多内容没GC,高达2g

支持(0) 反对(0) Mrs_ZMx | 园豆:280 (菜鸟二级) | 2019-12-11 19:52

顺序执行是没有任何问题的,就是多线程造成了问题

支持(1) 反对(0) Mrs_ZMx | 园豆:280 (菜鸟二级) | 2019-12-11 20:03

@Mrs_ZMx: 

2G的话可能是缓存的查询结果,线程的内存开销不至于此

有可能是变成大对象了要不试试GC.Collect(2, GCCollectionMode.Forced, false, false);

支持(0) 反对(0) 左眼水星 | 园豆:113 (初学一级) | 2019-12-11 20:06

@左眼水星:GC.Collect是能解决问题的,但这个方法是强制停掉线程来进行垃圾回收,影响性能的

支持(0) 反对(1) Mrs_ZMx | 园豆:280 (菜鸟二级) | 2019-12-11 20:15
0

仅查询数据 用下Select

通信的搞程序 | 园豆:1747 (小虾三级) | 2019-12-11 17:03

就是用select

支持(0) 反对(0) Mrs_ZMx | 园豆:280 (菜鸟二级) | 2019-12-11 17:23

@Mrs_ZMx: 贴下实际代码噻

支持(0) 反对(0) 通信的搞程序 | 园豆:1747 (小虾三级) | 2019-12-11 17:23

@通信的搞程序: 好的,实际业务是访问GRPC服务,在服务里有一个方法用databaseContext.Table.Select().ToList();随着访问量的增加内存也一直增,主要存在部分内存释放不了,后面直接堆积到了5,6个g。我后面通过测试发现是使用多线程进行efcore查询,存在一部分内存释放不了,随着线程一次一次查询,内存逐渐在堆积。具体代码是for里面开线程 new Task.StartNew(() => var t = databaseContext.Table.Select().ToList()),就这么简单

支持(0) 反对(0) Mrs_ZMx | 园豆:280 (菜鸟二级) | 2019-12-11 17:31

@Mrs_ZMx: https://www.cnblogs.com/weapon/p/9121143.html 看看是不是博客园发的这种问题

支持(0) 反对(0) 通信的搞程序 | 园豆:1747 (小虾三级) | 2019-12-11 17:36

@通信的搞程序: 我感觉是netcore垃圾回收机制,和EFcore查询在多线程中,存在了某些问题,导致了内存泄漏。。

支持(0) 反对(0) Mrs_ZMx | 园豆:280 (菜鸟二级) | 2019-12-11 17:37

@通信的搞程序: 要找到源头在哪里,然后处理。。现在什么也没蹲到。欲哭无泪。。

支持(0) 反对(0) Mrs_ZMx | 园豆:280 (菜鸟二级) | 2019-12-11 17:39

@通信的搞程序: 我看过那篇博客,不是注入方式的问题。我直接用的using

支持(0) 反对(0) Mrs_ZMx | 园豆:280 (菜鸟二级) | 2019-12-11 17:49

@Mrs_ZMx: 数据库查询最好不要并发去查询同一张表吧,不然对数据库也不友好

支持(0) 反对(0) DHclly | 园豆:206 (菜鸟二级) | 2023-07-05 09:24
0

楼主,最后怎么解决的?重启程序之后好像就可以了

t800 | 园豆:202 (菜鸟二级) | 2022-07-19 15:25
0

楼主,最后怎么解决的?

DHclly | 园豆:206 (菜鸟二级) | 2023-07-05 09:42
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册