# 《并发设计模式》第25章-生产者消费者模式-个人文库系统资源耗尽问题分析
作者:冰河
星球:http://m6z.cn/6aeFbs (opens new window)
博客:https://binghe.gitcode.host (opens new window)
文章汇总:https://binghe.gitcode.host/md/all/all.html (opens new window)
源码获取地址:https://t.zsxq.com/0dhvFs5oR (opens new window)
沉淀,成长,突破,帮助他人,成就自我。
- 本章难度:★★☆☆☆
- 本章重点:了解生产者消费者模式的使用场景,掌握生产者消费者模式与多线程异步方式的区别,重点掌握生产者消费者模式在实际项目场景中的应用,并能够结合自身项目实际场景思考如何将生产者消费者模式灵活应用到自身实际项目中。
大家好,我是冰河~~
生产者消费者模式异步处理任务,与多线程异步处理任务还是有所区别的。在多线程模式下,每个任务可单独分配一个线程执行,如果每个任务都新创建一个线程执行的话,可能会导致在某段请求并发量比较高的时间里,系统中存在大量的活跃线程正在执行任务,此时这些线程会大量的占用服务器的资源。而生产者消费者模式,在某些场景下,尽管并发请求量高,只要消费者的消费速度大于生产者的生产速度,则只需要少量的消费者线程即可处理对应的任务,不会过度的占用服务器资源。
# 一、故事背景
公司生产环境面向C端的个人文库系统由于大量用户上传文档保存数据而崩溃宕机,小菜虽说花了不少时间来排查问题,但终究还是没能独立定位到问题,更别说是独立解决问题了。在老王的指导下,小菜总算搞清楚了个人文库系统的核心需求和业务,也明白了系统性能瓶颈所在。同时,小菜也大概知晓了如何初步来优化个人文库系统。于是,小菜便迅速优化了一版。但是在提交测试后,又出现了服务器资源占用率高导致系统性能很差的问题,重启服务后偶尔会出现部分数据丢失的问题。
此时的小菜不得不从一个问题跳出来,又转而去处理另一个新的问题。对于服务器资源占用率高的问题,小菜自然是没有什么经验来解决。于是,他便开始从网上搜索各种案例,但是找来找去,还是跟自己写的代码涉及到的场景有所差异,“到底该怎么解决呢?”,小菜在心里想,“不行还是问问老大吧,哎”,小菜心里确实挺郁闷的,但是公司项目不等人呀,问老王的话自己也还能学习到新的知识,于是,小菜便决定再次寻求老王的帮助。
# 二、寻求帮助
小菜来到老王的工位旁,说道:“老大,我按照你之前给我讲的尝试优化了下系统,但是现在有个新的问题了,就是在提交测试后,模拟大量用户使用的时候,系统会大量占用服务器的资源而导致性能低下,测试说偶尔也会出现数据丢失,你可以帮我看看具体是啥问题吗?我确实找不到是什么问题了,可以再帮我看看吗?”。
“好,我大概知道你遇到什么问题,我给你看看”,老王回应到。
于是,老王跟着小菜一起来到了小菜的工位,老王看起了小菜写的代码。
也就几十秒的时间,老王说道:“我之前就预料到了你会这么写代码,这么写代码确实会占用大量的服务器资源导致性能问题”。
“那怎么写呢?我确实想不到其他的方案了”,小菜一脸疑惑的说到。
“没事,我再给你讲讲,你就明白了”,老王继续说到。
“好的”。
“那我们还是去会议室说吧”。
“好的”。
于是,小菜跟着老王又走进了会议室。。。
# 三、问题分析
优化前的个人文库系统主要是由于将与用户操作无关的、并且比较耗时的生成文档索引的业务逻辑,放到了用户操作的业务逻辑中,导致用户操作后,系统的响应非常慢导致的卡顿问题,如图25-1。
可以看到,个人文库系统系统由于建立文档索引比较耗时,直接影响到了用户的操作体验。那小菜是怎么优化的呢?其实小菜的优化方案就是将建立文档索引的操作放到了另一个线程中,并且也使用了线程池,但是小菜对线程池的使用也不太恰当,来看看小菜的做法。
于是,老王按照小菜的思路写了一个小菜优化后的案例代码PCWrongTest2类,PCWrongTest2类的源码详见:concurrent-design-patterns-producer-comsumer工程下的io.binghe.concurrent.design.pc.wrong.PCWrongTest2。
public class PCWrongTest2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("总体任务开始");
ExecutorService threadPool = Executors.newCachedThreadPool();
long startTime = System.currentTimeMillis();
DBService dbService = new DBServiceImpl();
dbService.save("bingheSaveData");
UploadService uploadService = new UploadServiceImpl();
uploadService.upload("bingheUploadFile001", "bingheUploadFile002");
Future<?> future = threadPool.submit(() -> {
IndexService indexService = new IndexServiceImpl();
indexService.index("bingheIndexFile001", "bingheIndexFile002");
});
System.out.println("返回用户结果耗时:" + (System.currentTimeMillis() - startTime) + "ms");
future.get();
System.out.println("总体任务结束,耗时:" + (System.currentTimeMillis() - startTime) + "ms");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
先来运行下这段代码,输出的结果信息如下所示。
总体任务开始
保存业务数据开始
保存业务数据成功:bingheSaveData
保存业务数据结束,耗时:0ms
上传文件开始
上传文件成功,上传的文件如下所示:
bingheUploadFile001
bingheUploadFile002
上传文件结束,耗时:46ms
返回用户结果耗时:62ms
索引文件数据开始
索引文件数据成功
索引文件数据结束,耗时:5000ms
总体任务结束,耗时:5051ms
2
3
4
5
6
7
8
9
10
11
12
13
14
# 查看全文
加入冰河技术 (opens new window)知识星球,解锁完整技术文章与完整代码