看我如何用多线程,帮助运营小姐姐解决数据校对系统变慢!(代码片段)

华为云开发者联盟 华为云开发者联盟     2022-11-30     245

关键词:

摘要:找到系统性能瓶颈所在,找出在逻辑上不相干,并且没有先后顺序的业务逻辑,将其放到不同的线程中执行,能够大大提供系统的性能。

本文分享自华为云社区《【高并发】我用多线程优化了亿级流量电商业务下的海量数据校对系统,性能直接提升了200%!!》,作者: 冰 河 。

最近不少运营同事找到我说:咱们的数据校对系统越来越慢了,要过很久才会显示出校对结果,你能不能快速优化一下呢?

优化背景

由于这个数据校对系统最初不是我开发的,我了解了下数据校对系统的业务,整体来说,数据校对系统的业务还是比较简单的。用户通过商城提交订单后,会在订单微服务中生成订单信息,保存在订单数据库中。

订单微服务会调用库存微服务的接口,扣减商品的库存数量,并且会将每笔订单扣减库存的记录保存在库存数据库中。为了防止用户提交订单后没有扣减库存,或者重复扣减库存,数据校对系统每天会校验订单中提交的商品数量与扣减的库存数量是否一致,并且会将校对的结果信息保存到数据校对信息表中。

数据校对系统的总体流程为:先查询订单记录,然后在查询库存的扣减记录,然后对比订单和库存扣减记录,然后将校对的结果信息保存到数据校对信息表中,整体流程如下所示。

为了能够让大家更好的了解数据校对系统对于订单和库存的校对业务,我将代码精简了下,核心业务逻辑代码如下所示。

//检测是否存在未对账订单
checkOrders = checkOrders();
while(checkOrders != null)
 //查询未校对的订单信息
 hasNoOrders = getHasNoOrders();
 //查询未校对的库存记录
 hasNoStock = getHasNoStock();
 //校对数据并返回结果
 checkResult = checkData(hasNoOrders, hasNoStock);
 //将结果信息保存到数据校对信息表中
 saveCheckResult(checkResult);
 //检测是否存在未对账订单
 checkOrders = checkOrders();

好了,上述就是系统优化的背景,想必看到这里,很多小伙伴应该知道问题出在哪里了。我们继续往下看。

问题分析

虽然很多小伙伴应该已经知道系统性能低下的问题所在了,这里,我们就一起详细分析下校对系统性能低下的原因。

既然运营的同事说数据校对系统越来越慢了,我们首先要做的就是找到系统的性能瓶颈所在。据了解,目前的数据对账系统,由于订单记录和库存扣减记录数据量巨大,所以查询未校对的订单信息的方法getHasNoOrders()和查询为校对的库存记录的方法getHasNoStock()相对来说比较慢。并且在数据校对系统中,校对订单和库存记录的方法是单线程执行的,我们可以简单画一个时间抽线图,如下所示。

由图可以看出,以单线程的方式getHasNoOrders()方法和getHasNoStock()方法耗费了大量的时间,这两个方法本身在逻辑上就是两个独立的方法,并且这两个方法没有先后的执行的顺序依赖。那这两个方法能不能并行执行呢?很显然是可以的。那我们把getHasNoOrders()方法和getHasNoStock()方法分别放到两个不同的线程中,优化下系统的性能,整体流程如下所示。

优化后,我们将getHasNoOrders()方法放到线程1中执行,getHasNoStock()方法放到线程2中执行,checkData()方法和saveCheckResult()方法发放到线程3中执行,优化后的系统性能相比优化前的系统性能几乎提升了一倍,优化效果相对来说还是比较明显的。

说到这里,大家应该应该知道具体怎么优化了吧?好,我们继续往下看!

解决方案

解决问题的思路有了,接下来,我们看看如何使用代码实现我们上面分析的解决问题的思路。这里,我们可以分别开启两个线程执行getHasNoOrders()方法和getHasNoStock()方法,在主线程中执行checkData()方法和saveCheckResult()方法。这里需要注意的是:主线程需要等待两个子线程执行完毕之后再执行checkData()方法和saveCheckResult()方法。

为了实现这个功能,我们可以使用Thread类中join()方法。这里,具体的逻辑就是在主线程中调用两个子线程的join()方法实现阻塞等待,当两个子线程执行完毕退出时,调用两个子线程join()方法的主线程会被唤醒,从而执行主线程中的checkData()方法和saveCheckResult()方法。大体代码如下所示。

//检测是否存在未对账订单
checkOrders = checkOrders();
while(checkOrders != null)
 Thread t1 = new Thread(()->
 //查询未校对的订单信息
 hasNoOrders = getHasNoOrders();
 );
    t1.start();
 Thread t2 = new Thread(()->
 //查询未校对的库存记录
 hasNoStock = getHasNoStock();
 );
     t2.start();
 //阻塞主线程,等待线程t1和线程t2执行完毕
    t1.join();
    t2.join();
 //校对数据并返回结果
 checkResult = checkData(hasNoOrders, hasNoStock);
 //将结果信息保存到数据校对信息表中
 saveCheckResult(checkResult);
 //检测是否存在未对账订单
 checkOrders = checkOrders();

至此,我们基本上能够解决问题了。但是,还有没有进一步优化的空间呢?我们进一步往下看。

进一步优化

通过上面对系统优化,基本能够达成我们的优化目标,但是上面的解决方案存在着不足的地方,那就是在while循环里每次都要新建两个线程分别执行getHasNoOrders()方法和getHasNoStock()方法,了解Java多线程的小伙伴们应该都知道,在Java中创建线程可是个非常耗时的操作。所以,最好是能够将创建出来的线程反复使用。这里,估计很多小伙伴都会想到使用线程池,没错,我们可以使用线程池进一步优化上面的代码。

遇到新的问题

不过在使用线程池进一步优化时,我们会遇到一个问题,就是主线程如何等待子线程中的结果数据呢?说直白点就是:主线程如何知道子线程中的getHasNoOrders()方法和getHasNoStock()方法执行完了? 由于在之前的代码中我们是在主线程中调用子线程的join()方法等待子线程执行完毕,获取到子线程执行的结果后,继续执行主线程的逻辑。但是如果使用了线程池的话,线程池中的线程根本不会退出,此时,我们无法使用线程的join()方法等待线程执行完毕。

所以,主线程如何知道子线程中的getHasNoOrders()方法和getHasNoStock()方法执行完了? 这个问题就成了关键的突破点。这里,我们使用线程池进一步优化的代码如下所示。

//检测是否存在未对账订单
checkOrders = checkOrders();
//创建线程池
Executor executor = Executors.newFixedThreadPool(2);
while(checkOrders != null)
 executor.execute(()->
 //查询未校对的订单信息
 hasNoOrders = getHasNoOrders();
 );
 executor.execute(()->
 //查询未校对的库存记录
 hasNoStock = getHasNoStock();
 );
 /**如何知道子线程中的getHasNoOrders()方法和getHasNoStock()方法执行完了成为关键**/
 //校对数据并返回结果
 checkResult = checkData(hasNoOrders, hasNoStock);
 //将结果信息保存到数据校对信息表中
 saveCheckResult(checkResult);
 //检测是否存在未对账订单
 checkOrders = checkOrders();

那么,如何解决这个问题呢?我们继续往下看。

新的解决方案

相信细心的小伙伴们能够看出,整个业务的场景就是:一个线程需要等待其他两个线程的逻辑执行完毕后再执行。在Java的并发类库中,为我们提供了一个能够在这种场景下使用的类库,那就是CountDownLatch类。

使用CountDownLatch类优化我们程序的具体做法就是:在程序的while()循环中首先创建一个CountDownLatch对象,计数器的值初始化为2。分别在hasNoOrders = getHasNoOrders();代码和hasNoStock = getHasNoStock();代码的后面调用latch.countDown()方法使得计数器的值分别减1。在主线程中调用latch.await()方法,等待计数器的值变为0,继续往下执行。这样,就能够完美解决我们遇到的问题了。优化后的代码如下所示。

//检测是否存在未对账订单
checkOrders = checkOrders();
//创建线程池
Executor executor = Executors.newFixedThreadPool(2);
while(checkOrders != null)
 CountDownLatch latch = new CountDownLatch(2);
 executor.execute(()->
 //查询未校对的订单信息
 hasNoOrders = getHasNoOrders();
 latch.countDown();
 );
 executor.execute(()->
 //查询未校对的库存记录
 hasNoStock = getHasNoStock();
 latch.countDown();
 );
 //等待子线程的逻辑执行完毕
 latch.await();
 //校对数据并返回结果
 checkResult = checkData(hasNoOrders, hasNoStock);
 //将结果信息保存到数据校对信息表中
 saveCheckResult(checkResult);
 //检测是否存在未对账订单
 checkOrders = checkOrders();

至此,我们就完成了系统的优化工作。

总结与思考

这次系统性能的优化,主要是将单线程执行的数据校对业务,优化成使用多线程执行。在平时的工作过程中,我们需要认真思考,找到系统性能瓶颈所在,找出在逻辑上不相干,并且没有先后顺序的业务逻辑,将其放到不同的线程中执行,能够大大提供系统的性能。

这次,对于系统的优化,我们最终使用线程池来执行比较耗时的查询订单与查询库存记录的操作,并且在主线程中等待线程池中的线程逻辑执行完毕后再执行主线程的后续业务逻辑。这种场景,使用Java中提供的CountDownLatch类再合适不过了。这里,再强调一下:CountDownLatch主要的使用场景就是一个线程等待多个线程执行完毕后再执行。如下图所示。

这里,也进一步提醒了我们:如果想学好并发编程,熟练的掌握Java中提供的并发类库是我们必须要做到的。

点击关注,第一时间了解华为云新鲜技术~

我用多线程优化了亿级流量电商业务下的海量数据校对系统,性能直接提升了200%!!(全程干货,建议收藏)(代码片段)

大家好,我是冰河~~最近不少运营同事找到我说:咱们的数据校对系统越来越慢了,要过很久才会显示出校对结果,你能不能快速优化一下呢?我:好的,我先了解下业务啊。注:全程干货,文... 查看详情

我用多线程优化了亿级流量电商业务下的海量数据校对系统,性能直接提升了200%!!(全程干货,建议收藏)(代码片段)

大家好,我是冰河~~最近不少运营同事找到我说:咱们的数据校对系统越来越慢了,要过很久才会显示出校对结果,你能不能快速优化一下呢?我:好的,我先了解下业务啊。注:全程干货,文... 查看详情

自从教了公司新来的小姐姐tomcat部署及优化,小姐姐看我的眼神都不一样了!(代码片段)

一、概述简单介绍Tomcat免费的、开放源代码的Web应用服务器,属于轻量级应用服务器。在中小型系统和并发访问用户不是很多的场合下被普遍使用是开发和调试JSP程序的首选。Apache软件基金会(ApacheSoftwareFoundation)Jakarta项目中的一... 查看详情

我用多线程进一步优化了亿级流量电商业务下的海量数据校对系统,性能再次提升了200%!!(全程干货,建议收藏)(代码片段)

...家好,我是冰河~~在【精通高并发系列】的《我用多线程优化了亿级流量电商业务下的海量数据校对系统,性能直接提升了200%!!(全程干货,建议收藏)》一文中,我们主要使用了CountDownLatch这个... 查看详情

我用多线程进一步优化了亿级流量电商业务下的海量数据校对系统,性能再次提升了200%!!(全程干货,建议收藏)(代码片段)

...家好,我是冰河~~在【精通高并发系列】的《我用多线程优化了亿级流量电商业务下的海量数据校对系统,性能直接提升了200%!!(全程干货,建议收藏)》一文中,我们主要使用了CountDownLatch这个... 查看详情

看我如何用定值cookie实现反爬(代码片段)

摘要:本次案例,用定值Cookie实现反爬。本文分享自华为云社区《我是怎么用一个特殊Cookie,限制住别人的爬虫的》,作者:梦想橡皮擦。Cookie生成由于本案例需要用到一个特定的Cookie,所以我们需要提前将其生成,你可以直接... 查看详情

如何用matlab为小姐姐跳舞视频增添另一个小姐姐跳舞进度条(代码片段)

注:本人非常清楚这项任务使用pr会简单很多,此篇文章只是为了探究matlab能干啥的边界。并作为一个熟悉matlab矩阵操作和视频音频操作的例子。效果如下:(怕侵权就只放个动图)可以看到小姐姐的舞蹈非常... 查看详情

如何用matlab为小姐姐跳舞视频增添另一个小姐姐跳舞进度条(代码片段)

注:本人非常清楚这项任务使用pr会简单很多,此篇文章只是为了探究matlab能干啥的边界。并作为一个熟悉matlab矩阵操作和视频音频操作的例子。效果如下:(怕侵权就只放个动图)可以看到小姐姐的舞蹈非常... 查看详情

python爬虫:运用多线程ip代理模块爬取百度图片上小姐姐的图片(代码片段)

Python爬虫:运用多线程、IP代理模块爬取百度图片上小姐姐的图片1.爬取输入类型的图片数量(用于给用户提示)使用过百度图片的读者会发现,在搜索栏上输入关键词之后,会显示出搜索的结果,小编想大多数... 查看详情

谁能告诉我如何用相同的按钮解决 python gui tkinter 但在不同的 MySQL 表中单独工作

】谁能告诉我如何用相同的按钮解决pythonguitkinter但在不同的MySQL表中单独工作【英文标题】:CananyoneshowmehowtosolvethepythonguitkinterwithsamebuttonsbutworkingseparatelyindifferentMySQLtables【发布时间】:2020-12-2821:56:33【问题描述】:当我在MySQL... 查看详情

小伙,多线程(gcd)看我就够了,骗你没好处!

   多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括... 查看详情

数据库的字符集和校对集

1.概念介绍字符集(CHARACTER)和校对规则(COLLATION)。字符集是用来定义mysql存储字符串的方式,校对规则则是定义了比较字符串的方式。字符集和校对规则是一对多的关系,MySQL支持30多种字符集的70多种校对规则。2.如何选择?... 查看详情

手把手带你爬取小姐姐私房照,准备好你的纸!(代码片段)

如何用Python搞到小姐姐私房照本文纯技术角度出发,教你如何用Python爬虫获取百度图库海量照片——技术无罪。学会获取小姐姐私房照同理可得也能获取其他的照片,技术原理是一致的。目标站点百度图片使用关键字搜... 查看详情

如何用singlethreadmodel解决多线程安全问题

2.用SingleThreadModel解决多线程安全问题:(视频下载) (全部书籍)前面介绍的都是普通的Servlet。对于每一个用户请求,那些Servlet都会用线程的方式给予应答。这样比较节省系统的资源。Sun公司也给出了另外一种方法,就是... 查看详情

kettle教程[2]程序员小姐姐的第二次邂逅——job(作业)开发

程序员小姐姐的第二次邂逅——JOB前几天帮助程序员小姐姐小花解决了使用Kettle从Excel中抽取数据到MySQL问题,小姐姐特别高兴,请你吃了一顿饭,好一顿魂牵梦绕。小姐姐好几天都没有联系了,今天小姐姐又抱着... 查看详情

kettle教程[2]程序员小姐姐的第二次邂逅——job(作业)开发

程序员小姐姐的第二次邂逅——JOB前几天帮助程序员小姐姐小花解决了使用Kettle从Excel中抽取数据到MySQL问题,小姐姐特别高兴,请你吃了一顿饭,好一顿魂牵梦绕。小姐姐好几天都没有联系了,今天小姐姐又抱着... 查看详情

色字当头一把刀,看我如何用python针对裸聊渗透测试(代码片段)

本篇文章由知柯™️信息安全&CSDN博主鸿渐之翼联合发布,转载请标明出处!深圳市狩猎者网络安全技术有限公司旗下安全团队CSDN:@知柯信息安全知柯信息安全,用心呵护能的安全!ProfessionalinSoftwareSecu... 查看详情

python爬虫应用实战-如何爬取好看的小姐姐照片?(代码片段)

线程锁Threading模块为我们提供了一个类,Threading.Lock锁。我们创建该类的对象,在线程函数执行之前,“抢占”该锁,执行完成之后,“释放”该锁,则我们确保了每次只有一个线程占有该锁。这时对一个公共对象进行操作,则... 查看详情